diff --git a/.gitignore b/.gitignore index f4efc68..646fdf6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,22 @@ -# Empty extraction directories -docs/diseqc/images/ - -# Third-party dependencies -firmware/fx2lib/ - -# Build artifacts -firmware/build/ -tools/__pycache__/ - -# TUI -tui/.venv/ -tui/__pycache__/ - -# MCP server -mcp/skywalker-mcp/.venv/ -mcp/skywalker-mcp/__pycache__/ - -# Documentation site -site/node_modules/ -site/dist/ -site/.astro/ +# Empty extraction directories +docs/diseqc/images/ + +# Third-party dependencies +firmware/fx2lib/ + +# Build artifacts +firmware/build/ +tools/__pycache__/ + +# TUI +tui/.venv/ +tui/__pycache__/ + +# MCP server +mcp/skywalker-mcp/.venv/ +mcp/skywalker-mcp/__pycache__/ + +# Documentation site +site/node_modules/ +site/dist/ +site/.astro/ diff --git a/README.md b/README.md index 23b246e..d970995 100644 --- a/README.md +++ b/README.md @@ -1,179 +1,179 @@ -
- -# SkyWalker-1 - -**Reverse-engineered documentation, custom firmware, and Python tooling for the Genpix SkyWalker-1 DVB-S USB 2.0 satellite receiver.** - -[![Docs](https://img.shields.io/badge/docs-skywalker--1.warehack.ing-0a7e8c?style=flat-square)](https://skywalker-1.warehack.ing) -[![Firmware](https://img.shields.io/badge/firmware-SDCC%20%2B%20fx2lib-1a1a2e?style=flat-square)](https://skywalker-1.warehack.ing/firmware/custom-v301/) -[![License](https://img.shields.io/badge/license-open--source-3d5a80?style=flat-square)](#license) - -
- ---- - -The SkyWalker-1 is a standalone USB 2.0 DVB-S receiver built around a **Cypress FX2LP** (CY7C68013A) microcontroller and **Broadcom BCM4500** satellite demodulator. It was designed by [Genpix Electronics](https://www.genpix-electronics.com/index.php?act=viewDoc&docId=9) for DVB-S, Turbo-coded, Digicipher II, and DSS reception. - -This project documents the device's complete internal architecture -- every vendor command, register, GPIO pin, and data path -- built up from Linux kernel driver analysis, Ghidra firmware disassembly of five firmware images, Windows BDA driver source review, and original custom firmware development. - -**[Browse the full documentation at skywalker-1.warehack.ing](https://skywalker-1.warehack.ing)** - -## What's Here - -``` -skywalker-1/ - firmware/ Custom FX2 firmware (SDCC + fx2lib, 1441 lines of C) - skywalker1.c Full replacement firmware with 10 custom vendor commands - Makefile Build rules targeting CY7C68013A - dscr.a51 USB descriptors (VID 0x09C0, PID 0x0203) - firmware-dump/ Extracted stock firmware binaries (v2.06, v2.10, v2.13 x3) - firmware-driver/ Genpix Windows BDA driver source (reference material) - tools/ Python utilities for direct hardware interaction - skywalker.py Multi-mode RF tool: spectrum, scan, monitor, lband, track - skywalker_lib.py Shared library: USB transport, BCM4500 register access - fw_load.py FX2 RAM firmware loader via 0xA0 vendor request - tune.py Transponder tuner with lock detection and BER readout - ts_analyze.py MPEG-2 transport stream parser and PID analyzer - eeprom_dump.py Full EEPROM image extraction - eeprom_write.py EEPROM flash tool with C2 format support - wine_memdump.py Extract firmware from Genpix Windows updater EXEs - test_*.py Boot sequence and I2C debug/isolation harnesses - site/ Astro Starlight documentation (37 pages, 9 sections) - docs/ Raw analysis documents and master hardware reference -``` - -## Hardware at a Glance - -| | | -|---|---| -| **MCU** | Cypress CY7C68013A (FX2LP) -- 8051 core at 48 MHz, USB 2.0 Hi-Speed | -| **Demodulator** | Broadcom BCM4500 -- DVB-S / Turbo / DCII / DSS, 128-pin MQFP | -| **IF Range** | 950 -- 2150 MHz | -| **Symbol Rate** | 256 Ksps -- 30 Msps | -| **LNB Control** | 13/18V, 22 kHz tone, DiSEqC 1.0/1.2, legacy switches | -| **Connector** | IEC F-type female | -| **Transport** | 8-bit parallel bus via GPIF into USB bulk endpoint EP2 | - -``` - +--[ I2C EEPROM 0x51 ] - | -USB 2.0 HS | I2C Bus (400 kHz) -Host PC <----> [ CY7C68013A FX2LP ] <-----> [ BCM4500 Demod 0x08 ] - | 8051 @ 48 MHz | | - | GPIF Engine |<-----------+ 8-bit parallel TS - | EP2 Bulk IN | - | GPIO (P0/P3) |---> [ 22 kHz Osc ] ---> LNB/Coax - | |---> [ LNB Voltage Ctrl ] - +-----------------+ - | - +--[ Tuner/LNB IC 0x10 ] -``` - -**Supported modulations:** DVB-S (QPSK), DVB-S (BPSK), Turbo QPSK, Turbo 8PSK, Turbo 16QAM, Digicipher II (Combo, Split I/Q, Offset QPSK), DSS (QPSK). - -> DVB-S2 is **not supported**. The BCM4500 predates DVB-S2 and contains no LDPC or BCH decoder hardware. This is a silicon limitation -- no firmware update can change it. See the [DVB-S2 investigation](https://skywalker-1.warehack.ing/driver/dvb-s2/). - -## Custom Firmware - -The stock EEPROM firmware was replaced with an open-source implementation built from scratch using **SDCC** and **fx2lib**. It implements all stock vendor commands (kernel driver compatible) plus 10 new diagnostic and analysis commands: - -| Command | ID | Purpose | -|---------|-----|---------| -| Spectrum Sweep | `0xB0` | AGC-based power sweep across IF range | -| Raw Demod Read | `0xB1` | Direct BCM4500 indirect register read | -| Raw Demod Write | `0xB2` | Direct BCM4500 indirect register write | -| Blind Scan | `0xB3` | Carrier detection at arbitrary frequency | -| Signal Monitor | `0xB7` | Combined SNR + AGC + lock in single transfer | -| Tune Monitor | `0xB8` | Tune + dwell + measure in one round-trip | -| Multi Reg Read | `0xB9` | Batch read of contiguous indirect registers | - -Build and load: - -```bash -cd firmware -make # requires SDCC + fx2lib -make load # RAM-loads via fw_load.py (does not touch EEPROM) -``` - -## Python Tools - -All tools communicate directly with the SkyWalker-1 over USB using `pyusb`. No kernel driver required. - -```bash -# Load custom firmware into FX2 RAM -python tools/fw_load.py firmware/build/skywalker1.bix - -# Tune to a transponder and check lock -python tools/tune.py --freq 12224 --sr 20000 --pol H --mod dvb-s - -# Sweep the IF band and render ASCII spectrum -python tools/skywalker.py spectrum --start 950 --stop 2150 --step 5 - -# Real-time signal strength (dish alignment) -python tools/skywalker.py monitor --freq 1175 - -# Dump full EEPROM contents -python tools/eeprom_dump.py --output eeprom.bin -``` - -The `skywalker.py` multi-mode tool provides five operating modes: `spectrum` (sweep analyzer), `scan` (transponder finder), `monitor` (signal strength meter), `lband` (direct L-band input), and `track` (carrier/beacon tracker with CSV logging). - -## Documentation Site - -The full documentation is published at **[skywalker-1.warehack.ing](https://skywalker-1.warehack.ing)** and covers: - -| Section | Pages | Covers | -|---------|-------|--------| -| **Hardware** | 4 | Board architecture, GPIO pin map, RF specifications | -| **USB Interface** | 4 | 30 vendor commands, boot sequence, endpoint layout | -| **BCM4500** | 5 | Register map, indirect access protocol, tuning sequence, GPIF streaming, signal monitoring | -| **LNB & DiSEqC** | 3 | Voltage/tone control, DiSEqC 1.0/1.2, legacy Dish switches | -| **I2C Bus** | 2 | Bus architecture, STOP corruption bug root cause analysis | -| **Firmware** | 7 | 5 stock versions compared, custom v3.01 and v3.02, storage formats | -| **Driver** | 2 | Linux gp8psk kernel driver, DVB-S2 incompatibility investigation | -| **Tools** | 7 | Every Python utility documented with usage examples | -| **Reference** | 1 | Consolidated master reference (registers, commands, GPIO, I2C) | - -To run the docs site locally: - -```bash -cd site -npm install -npm run dev # http://localhost:4321 -``` - -
-Docker deployment - -The docs site includes a multi-stage Dockerfile with dev and prod targets. Production serves static files through Caddy. - -```bash -cd site - -# Development (HMR via volume mounts) -APP_ENV=dev docker compose up --build - -# Production (static build served by Caddy) -APP_ENV=prod docker compose up --build -d -``` - -
- -## Project History - -This project started with USB packet captures and `lsusb` output, then progressed through increasingly deep layers of the hardware: - -1. **EEPROM extraction** -- dumped raw firmware bytes over I2C -2. **Ghidra disassembly** -- decompiled five 8051 firmware images, mapped all functions and vendor commands -3. **Windows driver analysis** -- cross-referenced Ghidra findings against Genpix BDA driver source -4. **Linux driver analysis** -- mapped kernel `gp8psk` driver to decoded vendor commands -5. **Custom firmware** -- wrote a full replacement in C, discovered and fixed the [I2C STOP corruption bug](https://skywalker-1.warehack.ing/i2c/stop-corruption-bug/) -6. **RF tooling** -- built spectrum analyzer, blind scanner, and signal monitor on top of the custom command set - -## Contributing - -This is a niche reverse-engineering project for a specific piece of satellite hardware. If you have a SkyWalker-1 (or other Genpix device using the BCM4500), contributions are welcome -- particularly additional firmware dumps, register documentation, or corrections to the analysis. - -## License - -The custom firmware source, Python tools, and documentation are open source. Stock firmware binaries in `firmware-dump/` are proprietary Genpix Electronics images retained for research and interoperability purposes. +
+ +# SkyWalker-1 + +**Reverse-engineered documentation, custom firmware, and Python tooling for the Genpix SkyWalker-1 DVB-S USB 2.0 satellite receiver.** + +[![Docs](https://img.shields.io/badge/docs-skywalker--1.warehack.ing-0a7e8c?style=flat-square)](https://skywalker-1.warehack.ing) +[![Firmware](https://img.shields.io/badge/firmware-SDCC%20%2B%20fx2lib-1a1a2e?style=flat-square)](https://skywalker-1.warehack.ing/firmware/custom-v301/) +[![License](https://img.shields.io/badge/license-open--source-3d5a80?style=flat-square)](#license) + +
+ +--- + +The SkyWalker-1 is a standalone USB 2.0 DVB-S receiver built around a **Cypress FX2LP** (CY7C68013A) microcontroller and **Broadcom BCM4500** satellite demodulator. It was designed by [Genpix Electronics](https://www.genpix-electronics.com/index.php?act=viewDoc&docId=9) for DVB-S, Turbo-coded, Digicipher II, and DSS reception. + +This project documents the device's complete internal architecture -- every vendor command, register, GPIO pin, and data path -- built up from Linux kernel driver analysis, Ghidra firmware disassembly of five firmware images, Windows BDA driver source review, and original custom firmware development. + +**[Browse the full documentation at skywalker-1.warehack.ing](https://skywalker-1.warehack.ing)** + +## What's Here + +``` +skywalker-1/ + firmware/ Custom FX2 firmware (SDCC + fx2lib, 1441 lines of C) + skywalker1.c Full replacement firmware with 10 custom vendor commands + Makefile Build rules targeting CY7C68013A + dscr.a51 USB descriptors (VID 0x09C0, PID 0x0203) + firmware-dump/ Extracted stock firmware binaries (v2.06, v2.10, v2.13 x3) + firmware-driver/ Genpix Windows BDA driver source (reference material) + tools/ Python utilities for direct hardware interaction + skywalker.py Multi-mode RF tool: spectrum, scan, monitor, lband, track + skywalker_lib.py Shared library: USB transport, BCM4500 register access + fw_load.py FX2 RAM firmware loader via 0xA0 vendor request + tune.py Transponder tuner with lock detection and BER readout + ts_analyze.py MPEG-2 transport stream parser and PID analyzer + eeprom_dump.py Full EEPROM image extraction + eeprom_write.py EEPROM flash tool with C2 format support + wine_memdump.py Extract firmware from Genpix Windows updater EXEs + test_*.py Boot sequence and I2C debug/isolation harnesses + site/ Astro Starlight documentation (37 pages, 9 sections) + docs/ Raw analysis documents and master hardware reference +``` + +## Hardware at a Glance + +| | | +|---|---| +| **MCU** | Cypress CY7C68013A (FX2LP) -- 8051 core at 48 MHz, USB 2.0 Hi-Speed | +| **Demodulator** | Broadcom BCM4500 -- DVB-S / Turbo / DCII / DSS, 128-pin MQFP | +| **IF Range** | 950 -- 2150 MHz | +| **Symbol Rate** | 256 Ksps -- 30 Msps | +| **LNB Control** | 13/18V, 22 kHz tone, DiSEqC 1.0/1.2, legacy switches | +| **Connector** | IEC F-type female | +| **Transport** | 8-bit parallel bus via GPIF into USB bulk endpoint EP2 | + +``` + +--[ I2C EEPROM 0x51 ] + | +USB 2.0 HS | I2C Bus (400 kHz) +Host PC <----> [ CY7C68013A FX2LP ] <-----> [ BCM4500 Demod 0x08 ] + | 8051 @ 48 MHz | | + | GPIF Engine |<-----------+ 8-bit parallel TS + | EP2 Bulk IN | + | GPIO (P0/P3) |---> [ 22 kHz Osc ] ---> LNB/Coax + | |---> [ LNB Voltage Ctrl ] + +-----------------+ + | + +--[ Tuner/LNB IC 0x10 ] +``` + +**Supported modulations:** DVB-S (QPSK), DVB-S (BPSK), Turbo QPSK, Turbo 8PSK, Turbo 16QAM, Digicipher II (Combo, Split I/Q, Offset QPSK), DSS (QPSK). + +> DVB-S2 is **not supported**. The BCM4500 predates DVB-S2 and contains no LDPC or BCH decoder hardware. This is a silicon limitation -- no firmware update can change it. See the [DVB-S2 investigation](https://skywalker-1.warehack.ing/driver/dvb-s2/). + +## Custom Firmware + +The stock EEPROM firmware was replaced with an open-source implementation built from scratch using **SDCC** and **fx2lib**. It implements all stock vendor commands (kernel driver compatible) plus 10 new diagnostic and analysis commands: + +| Command | ID | Purpose | +|---------|-----|---------| +| Spectrum Sweep | `0xB0` | AGC-based power sweep across IF range | +| Raw Demod Read | `0xB1` | Direct BCM4500 indirect register read | +| Raw Demod Write | `0xB2` | Direct BCM4500 indirect register write | +| Blind Scan | `0xB3` | Carrier detection at arbitrary frequency | +| Signal Monitor | `0xB7` | Combined SNR + AGC + lock in single transfer | +| Tune Monitor | `0xB8` | Tune + dwell + measure in one round-trip | +| Multi Reg Read | `0xB9` | Batch read of contiguous indirect registers | + +Build and load: + +```bash +cd firmware +make # requires SDCC + fx2lib +make load # RAM-loads via fw_load.py (does not touch EEPROM) +``` + +## Python Tools + +All tools communicate directly with the SkyWalker-1 over USB using `pyusb`. No kernel driver required. + +```bash +# Load custom firmware into FX2 RAM +python tools/fw_load.py firmware/build/skywalker1.bix + +# Tune to a transponder and check lock +python tools/tune.py --freq 12224 --sr 20000 --pol H --mod dvb-s + +# Sweep the IF band and render ASCII spectrum +python tools/skywalker.py spectrum --start 950 --stop 2150 --step 5 + +# Real-time signal strength (dish alignment) +python tools/skywalker.py monitor --freq 1175 + +# Dump full EEPROM contents +python tools/eeprom_dump.py --output eeprom.bin +``` + +The `skywalker.py` multi-mode tool provides five operating modes: `spectrum` (sweep analyzer), `scan` (transponder finder), `monitor` (signal strength meter), `lband` (direct L-band input), and `track` (carrier/beacon tracker with CSV logging). + +## Documentation Site + +The full documentation is published at **[skywalker-1.warehack.ing](https://skywalker-1.warehack.ing)** and covers: + +| Section | Pages | Covers | +|---------|-------|--------| +| **Hardware** | 4 | Board architecture, GPIO pin map, RF specifications | +| **USB Interface** | 4 | 30 vendor commands, boot sequence, endpoint layout | +| **BCM4500** | 5 | Register map, indirect access protocol, tuning sequence, GPIF streaming, signal monitoring | +| **LNB & DiSEqC** | 3 | Voltage/tone control, DiSEqC 1.0/1.2, legacy Dish switches | +| **I2C Bus** | 2 | Bus architecture, STOP corruption bug root cause analysis | +| **Firmware** | 7 | 5 stock versions compared, custom v3.01 and v3.02, storage formats | +| **Driver** | 2 | Linux gp8psk kernel driver, DVB-S2 incompatibility investigation | +| **Tools** | 7 | Every Python utility documented with usage examples | +| **Reference** | 1 | Consolidated master reference (registers, commands, GPIO, I2C) | + +To run the docs site locally: + +```bash +cd site +npm install +npm run dev # http://localhost:4321 +``` + +
+Docker deployment + +The docs site includes a multi-stage Dockerfile with dev and prod targets. Production serves static files through Caddy. + +```bash +cd site + +# Development (HMR via volume mounts) +APP_ENV=dev docker compose up --build + +# Production (static build served by Caddy) +APP_ENV=prod docker compose up --build -d +``` + +
+ +## Project History + +This project started with USB packet captures and `lsusb` output, then progressed through increasingly deep layers of the hardware: + +1. **EEPROM extraction** -- dumped raw firmware bytes over I2C +2. **Ghidra disassembly** -- decompiled five 8051 firmware images, mapped all functions and vendor commands +3. **Windows driver analysis** -- cross-referenced Ghidra findings against Genpix BDA driver source +4. **Linux driver analysis** -- mapped kernel `gp8psk` driver to decoded vendor commands +5. **Custom firmware** -- wrote a full replacement in C, discovered and fixed the [I2C STOP corruption bug](https://skywalker-1.warehack.ing/i2c/stop-corruption-bug/) +6. **RF tooling** -- built spectrum analyzer, blind scanner, and signal monitor on top of the custom command set + +## Contributing + +This is a niche reverse-engineering project for a specific piece of satellite hardware. If you have a SkyWalker-1 (or other Genpix device using the BCM4500), contributions are welcome -- particularly additional firmware dumps, register documentation, or corrections to the analysis. + +## License + +The custom firmware source, Python tools, and documentation are open source. Stock firmware binaries in `firmware-dump/` are proprietary Genpix Electronics images retained for research and interoperability purposes. diff --git a/docs/boot-debug-findings.md b/docs/boot-debug-findings.md index 713898d..a30943e 100644 --- a/docs/boot-debug-findings.md +++ b/docs/boot-debug-findings.md @@ -1,257 +1,257 @@ -# BOOT_8PSK Debugging Findings - -Technical reference for the BCM4500 demodulator boot sequence on the Genpix SkyWalker-1 (Cypress FX2 CY7C68013A + Broadcom BCM4500), firmware v3.01.0. Documents the root cause analysis of a firmware hang during I2C initialization and the fixes applied. - -**Hardware:** Genpix SkyWalker-1 USB 2.0 DVB-S receiver -**MCU:** Cypress CY7C68013A (FX2LP), 8051 core at 48MHz -**Demodulator:** Broadcom BCM4500 -**Firmware:** Custom v3.01.0 (SDCC + fx2lib) -**I2C bus speed:** 400kHz - ---- - -## The Problem - -Custom firmware v3.01.0 implements vendor command `BOOT_8PSK` (bRequest=0x89, wValue=1), which powers on the BCM4500 demodulator and initializes it via I2C. When first tested, this command caused the FX2 firmware to hang for over 10 seconds, making the USB device completely unresponsive -- no vendor command would return, and the host-side USB stack would report timeout errors. - -The initial suspicion was infinite I2C loops. The fx2lib I2C library uses bare `while` loops that poll hardware status bits with no timeout: - -```c -// fx2lib/lib/i2c.c -- original code -while ( !(I2CS & bmDONE) && !cancel_i2c_trans); -``` - -The `cancel_i2c_trans` variable is intended as an external abort mechanism, but nothing in the firmware sets it during normal operation. If the I2C controller never asserts `bmDONE` (for example, because a slave is holding SCL low), the firmware spins indefinitely in this loop. - -Adding I2C timeout protection (described below) eliminated the infinite-hang symptom, but the boot sequence still failed: the BCM4500 probe read returned NACK, and all three register initialization blocks failed. - -## Root Cause: Spurious I2C STOP Condition - -The boot function originally included a so-called "I2C bus reset" step before any I2C communication: - -```c -I2CS |= bmSTOP; -i2c_wait_stop(); -``` - -This pattern appears in various FX2 example code and seems reasonable on its face -- send a STOP condition to ensure the I2C bus is in a known idle state before starting fresh. On the FX2's I2C controller hardware, this is incorrect. - -### Incremental Debug Modes - -The root cause was discovered through a series of incremental debug modes added to the `BOOT_8PSK` vendor command handler. Each mode executes a subset of the full boot sequence, isolating which step introduces the failure: - -| wValue | Action | Result | -|--------|--------|--------| -| `0x80` | No-op: return `config_status` and `boot_stage` only | Works | -| `0x81` | GPIO + power + delays only (no I2C at all) | Works | -| `0x82` | GPIO + power + `bmSTOP` + I2C probe read | **Fails** | -| `0x83` | GPIO + power + `bmSTOP` + probe + init block 0 | **Fails** (same root cause) | -| `0x84` | `bcm_direct_read` only (no GPIO, chip already powered) | Works | -| `0x85` | GPIO + power + reset, **no** `bmSTOP`, then probe | Works | - -Three observations clinch the diagnosis: - -1. **Mode 0x82 fails but mode 0x85 succeeds.** These two modes are identical except that 0x82 issues `I2CS |= bmSTOP` before the probe read and 0x85 does not. The `bmSTOP` is the only difference, and it is the only thing that breaks I2C. - -2. **Mode 0x84 succeeds immediately after 0x82 fails.** Mode 0x84 calls `bcm_direct_read` with no GPIO manipulation or bus reset -- just a plain I2C combined read. If called after a failed 0x82, it succeeds. This proves two things: the BCM4500 is alive and responding on I2C, and the `i2c_combined_read` function itself is correct. The failure in 0x82 is not a timing or power issue. - -3. **Raw I2C reads via vendor command 0xB5 succeed after 0x82 fails.** Command 0xB5 uses the same `i2c_combined_read` function as `bcm_direct_read`. Running it from the host side after a failed 0x82 returns valid data from the BCM4500. This confirms the chip was alive the whole time -- the FX2's I2C controller was in a bad state, not the bus or the slave. - -The test scripts that drove this investigation are in the `tools/` directory: - -- `test_boot_debug.py` -- sends debug modes 0x80 through 0x83 sequentially -- `test_i2c_debug.py` -- powers on via 0x81, runs bus scans, tests probe timing -- `test_i2c_isolate.py` -- tests whether re-reset or insufficient delay causes failure -- `test_i2c_pinpoint.py` -- the definitive test: compares 0x84, 0x85, and 0x82 - -### What Happens Inside the FX2 I2C Controller - -The FX2's I2C master controller is a hardware peripheral accessed through the `I2CS`, `I2DAT`, and `I2CTL` SFRs. The controller implements an I2C state machine in silicon. Writing `bmSTOP` to `I2CS` instructs the hardware to generate a STOP condition (SDA rising while SCL is high). - -When no I2C transaction is active -- no prior START has been issued, and the bus is idle -- writing `bmSTOP` puts the controller into an inconsistent internal state. The `bmSTOP` bit may not clear properly (it is supposed to self-clear when the STOP condition completes on the bus), and subsequent START conditions fail to generate proper clock sequences or detect ACK from slaves. - -The Cypress TRM (EZ-USB Technical Reference Manual) does not explicitly warn against this, but the I2C chapter describes STOP as a step that follows a completed read or write transaction. It is not documented as a standalone bus-reset mechanism. - -The correct way to ensure a clean I2C bus state on the FX2 is to simply proceed with a new START condition. If the bus is idle (which it will be after power-on or after the previous transaction completed normally), the START succeeds and the controller enters its normal operating state. The hardware handles bus arbitration automatically on START. - -## The Fix - -The fix is a single deletion. Remove the spurious STOP from the boot sequence: - -```c -/* BEFORE (broken): */ -I2CS |= bmSTOP; -i2c_wait_stop(); - -/* AFTER (correct): */ -/* NOTE: Do NOT send I2CS bmSTOP here. Sending STOP when no transaction - * is active corrupts the FX2 I2C controller state, causing subsequent - * START+ACK detection to fail. The I2C bus will be in a clean state - * when we reach the probe step -- any prior transaction ended with STOP. */ -``` - -The corrected `bcm4500_boot()` function proceeds directly from GPIO/power setup to the I2C probe read without any bus-reset step: - -```c -static BOOL bcm4500_boot(void) { - boot_stage = 1; - cancel_i2c_trans = FALSE; - - /* P3.7, P3.6, P3.5 HIGH (idle state for control lines) */ - IOD |= 0xE0; - - /* Assert BCM4500 hardware RESET (P0.5 LOW) */ - OEA |= PIN_BCM_RESET; - IOA &= ~PIN_BCM_RESET; - - /* No I2CS bmSTOP here -- see note above */ - - /* Power on: P0.1 HIGH (enable), P0.2 LOW (disable off) */ - OEA |= (PIN_PWR_EN | PIN_PWR_DIS); - IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN; - - boot_stage = 2; - delay(30); /* power settle */ - - IOA |= PIN_BCM_RESET; /* release reset */ - delay(50); /* BCM4500 POR + mask ROM boot */ - - boot_stage = 3; - /* I2C probe -- if this fails, the chip didn't come out of reset */ - if (!bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) - return FALSE; - - /* ... register init blocks follow ... */ -} -``` - -## I2C Timeout Protection - -Even with the `bmSTOP` fix, timeout protection on all I2C operations is essential. The FX2's I2C controller has no hardware timeout -- if a slave device holds SCL low (clock stretching), or if an electrical fault prevents `bmDONE` from asserting, the firmware will spin forever in a polling loop. - -### The Problem with fx2lib - -The fx2lib `i2c_write()` and `i2c_read()` functions poll `bmDONE` and `bmSTOP` with loops like: - -```c -while ( !(I2CS & bmDONE) && !cancel_i2c_trans); -``` - -The `cancel_i2c_trans` flag is declared as `volatile __xdata BOOL` and is set to `FALSE` at the start of each transaction. The library documentation says firmware can set it to `TRUE` from an interrupt to abort a stuck transaction. In practice, nothing in the firmware sets it, so these loops are effectively: - -```c -while (!(I2CS & bmDONE)); // infinite if bmDONE never asserts -``` - -### Timeout-Protected Replacements - -The custom firmware replaces all fx2lib I2C functions with timeout-protected wrappers: - -```c -#define I2C_TIMEOUT 6000 - -static BOOL i2c_wait_done(void) { - WORD timeout = I2C_TIMEOUT; - while (!(I2CS & bmDONE)) { - if (--timeout == 0) - return FALSE; - } - return TRUE; -} - -static BOOL i2c_wait_stop(void) { - WORD timeout = I2C_TIMEOUT; - while (I2CS & bmSTOP) { - if (--timeout == 0) - return FALSE; - } - return TRUE; -} -``` - -A `WORD` counter of 6000, decremented in a tight SDCC-compiled loop at 48MHz (4 clocks per 8051 machine cycle, ~12 MIPS), gives approximately 5-10ms per wait. At 400kHz I2C, a single byte transfer (9 clock pulses) takes 22.5 microseconds, so this timeout provides well over 200x margin for normal operations while still bounding the worst case. - -All BCM4500 I2C operations -- `i2c_combined_read`, `i2c_write_timeout`, `i2c_write_multi_timeout` -- use these timeout-protected waits and return `FALSE` on timeout, allowing the caller to report failure rather than hanging the firmware. - -## Kernel Driver Race Condition - -The `dvb_usb_gp8psk` kernel module auto-loads via udev when VID:PID `09C0:0203` appears on the USB bus. This happens every time the FX2 re-enumerates after firmware load. The kernel driver races with the test tools and sends its own `BOOT_8PSK` command (along with other initialization), which interferes with debugging. - -Symptoms of this race condition: -- Test scripts report "resource busy" or "entity not found" errors -- The BCM4500 enters an unexpected state because the kernel driver partially initialized it -- The kernel driver detaches from the device mid-test - -The fix is to blacklist the module: - -``` -# /etc/modprobe.d/blacklist-gp8psk.conf -blacklist dvb_usb_gp8psk -blacklist gp8psk_fe -``` - -After creating this file, run `sudo modprobe -r dvb_usb_gp8psk gp8psk_fe` to unload any currently-loaded instances. The blacklist prevents udev from auto-loading the module on device insertion, giving test tools exclusive access. - -## I2C Bus Scan Results - -Vendor command `0xB4` performs a full 7-bit I2C bus scan by attempting a START + address + WRITE to every address from 0x01 to 0x77 and checking for ACK. Three devices were found: - -| Address | Identity | -|---------|----------| -| `0x08` | BCM4500 demodulator. Status register `0xA2` returns valid data. This is the primary device for all demodulator operations. | -| `0x10` | Likely the tuner or LNB controller. The SkyWalker-1 uses a separate tuner IC (accessed through the BCM4500 in normal operation, but also directly addressable on the shared I2C bus). | -| `0x51` | Likely a configuration EEPROM. Many DVB-S receivers store tuner calibration data or device serial numbers in a small I2C EEPROM at addresses in the 0x50-0x57 range. | - -The BCM4500's 7-bit I2C address of `0x08` corresponds to 8-bit wire addresses of `0x10` (write) and `0x11` (read). - -## BCM4500 Boot Results After Fix - -With the `bmSTOP` removed, the full boot sequence completes reliably: - -- **Boot time:** ~90ms total (30ms power settle + 50ms post-reset delay + ~10ms I2C init) -- **config_status:** `0x03` (STARTED | FW_LOADED) -- **boot_stage:** `0xFF` (COMPLETE) -- **Direct registers 0xA2-0xA8:** All return `0x02` (powered, not locked -- expected without a satellite signal) -- **Signal lock:** `0x00` (no lock -- dish not aimed at satellite) -- **Signal strength:** All zeros (same reason) -- **USB responsiveness:** No hang. The firmware remains fully responsive to vendor commands throughout boot and afterward. - -## Firmware v3.01.0 Boot Sequence (Corrected) - -The complete boot sequence as implemented in `bcm4500_boot()`: - -1. **Assert BCM4500 RESET** -- Drive P0.5 LOW. This holds the BCM4500's digital logic in reset while power is applied. - -2. **Power on** -- Set P0.1 HIGH (power enable), P0.2 LOW (power disable off). The SkyWalker-1 has complementary power control pins. - -3. **delay(30ms)** -- Allow the power supply to settle and reach regulation. The stock firmware uses the same delay. - -4. **Release RESET** -- Drive P0.5 HIGH. The BCM4500 begins its internal power-on reset (POR) and mask ROM boot sequence. - -5. **delay(50ms)** -- Wait for the BCM4500's POR and internal initialization to complete. The chip needs time for its internal oscillator to stabilize and mask ROM to execute. - -6. **I2C probe** -- Read direct register `0xA2` (status) to verify the chip is alive and responding on I2C. If this fails, the boot aborts. - -7. **Write init block 0** -- 7 bytes to BCM4500 indirect page 0, starting at register `0x06`. Written via the `0xA6`/`0xA7`/`0xA8` indirect register protocol. Data: `{0x06, 0x0b, 0x17, 0x38, 0x9f, 0xd9, 0x80}`. - -8. **Write init block 1** -- 8 bytes to page 0, starting at register `0x07`. Data: `{0x07, 0x09, 0x39, 0x4f, 0x00, 0x65, 0xb7, 0x10}`. - -9. **Write init block 2** -- 3 bytes to page 0, starting at register `0x0F`. Data: `{0x0f, 0x0c, 0x09}`. - -10. **Set config_status** -- OR in `BM_STARTED | BM_FW_LOADED` (`0x03`). Subsequent vendor commands (tuning, signal strength readout, etc.) check this flag before operating. - -The three initialization blocks were extracted from disassembly of the stock v2.06 firmware's `FUN_CODE_0ddd` routine, which performs the same indirect register writes. - -## FX2 Hardware Recovery Note - -The FX2's CPUCS register at address `0xE600` controls the 8051 CPU's run/halt state. It is accessible via the standard vendor request bRequest=0xA0 (RAM read/write) even when the user firmware is completely hung in an infinite loop. - -This works because bRequest=0xA0 is handled by the FX2 silicon's boot ROM, not by firmware. The boot ROM's USB handler runs in a hardware-priority context that preempts the 8051's main loop. Writing `0x01` to CPUCS halts the CPU, new firmware can be loaded into RAM, and writing `0x00` starts it again. - -This means `fw_load.py` can reload firmware over a hung device without requiring a physical USB unplug/replug or power cycle. For iterative firmware development, this is significant -- a failed boot attempt that hangs the firmware can be recovered from the host side in seconds: - -```bash -sudo python3 tools/fw_load.py load firmware/build/skywalker1.ihx --wait 3 -``` - -The load sequence halts the CPU (CPUCS=0x01), writes new code into RAM, then restarts the CPU (CPUCS=0x00). The device re-enumerates with the new firmware. +# BOOT_8PSK Debugging Findings + +Technical reference for the BCM4500 demodulator boot sequence on the Genpix SkyWalker-1 (Cypress FX2 CY7C68013A + Broadcom BCM4500), firmware v3.01.0. Documents the root cause analysis of a firmware hang during I2C initialization and the fixes applied. + +**Hardware:** Genpix SkyWalker-1 USB 2.0 DVB-S receiver +**MCU:** Cypress CY7C68013A (FX2LP), 8051 core at 48MHz +**Demodulator:** Broadcom BCM4500 +**Firmware:** Custom v3.01.0 (SDCC + fx2lib) +**I2C bus speed:** 400kHz + +--- + +## The Problem + +Custom firmware v3.01.0 implements vendor command `BOOT_8PSK` (bRequest=0x89, wValue=1), which powers on the BCM4500 demodulator and initializes it via I2C. When first tested, this command caused the FX2 firmware to hang for over 10 seconds, making the USB device completely unresponsive -- no vendor command would return, and the host-side USB stack would report timeout errors. + +The initial suspicion was infinite I2C loops. The fx2lib I2C library uses bare `while` loops that poll hardware status bits with no timeout: + +```c +// fx2lib/lib/i2c.c -- original code +while ( !(I2CS & bmDONE) && !cancel_i2c_trans); +``` + +The `cancel_i2c_trans` variable is intended as an external abort mechanism, but nothing in the firmware sets it during normal operation. If the I2C controller never asserts `bmDONE` (for example, because a slave is holding SCL low), the firmware spins indefinitely in this loop. + +Adding I2C timeout protection (described below) eliminated the infinite-hang symptom, but the boot sequence still failed: the BCM4500 probe read returned NACK, and all three register initialization blocks failed. + +## Root Cause: Spurious I2C STOP Condition + +The boot function originally included a so-called "I2C bus reset" step before any I2C communication: + +```c +I2CS |= bmSTOP; +i2c_wait_stop(); +``` + +This pattern appears in various FX2 example code and seems reasonable on its face -- send a STOP condition to ensure the I2C bus is in a known idle state before starting fresh. On the FX2's I2C controller hardware, this is incorrect. + +### Incremental Debug Modes + +The root cause was discovered through a series of incremental debug modes added to the `BOOT_8PSK` vendor command handler. Each mode executes a subset of the full boot sequence, isolating which step introduces the failure: + +| wValue | Action | Result | +|--------|--------|--------| +| `0x80` | No-op: return `config_status` and `boot_stage` only | Works | +| `0x81` | GPIO + power + delays only (no I2C at all) | Works | +| `0x82` | GPIO + power + `bmSTOP` + I2C probe read | **Fails** | +| `0x83` | GPIO + power + `bmSTOP` + probe + init block 0 | **Fails** (same root cause) | +| `0x84` | `bcm_direct_read` only (no GPIO, chip already powered) | Works | +| `0x85` | GPIO + power + reset, **no** `bmSTOP`, then probe | Works | + +Three observations clinch the diagnosis: + +1. **Mode 0x82 fails but mode 0x85 succeeds.** These two modes are identical except that 0x82 issues `I2CS |= bmSTOP` before the probe read and 0x85 does not. The `bmSTOP` is the only difference, and it is the only thing that breaks I2C. + +2. **Mode 0x84 succeeds immediately after 0x82 fails.** Mode 0x84 calls `bcm_direct_read` with no GPIO manipulation or bus reset -- just a plain I2C combined read. If called after a failed 0x82, it succeeds. This proves two things: the BCM4500 is alive and responding on I2C, and the `i2c_combined_read` function itself is correct. The failure in 0x82 is not a timing or power issue. + +3. **Raw I2C reads via vendor command 0xB5 succeed after 0x82 fails.** Command 0xB5 uses the same `i2c_combined_read` function as `bcm_direct_read`. Running it from the host side after a failed 0x82 returns valid data from the BCM4500. This confirms the chip was alive the whole time -- the FX2's I2C controller was in a bad state, not the bus or the slave. + +The test scripts that drove this investigation are in the `tools/` directory: + +- `test_boot_debug.py` -- sends debug modes 0x80 through 0x83 sequentially +- `test_i2c_debug.py` -- powers on via 0x81, runs bus scans, tests probe timing +- `test_i2c_isolate.py` -- tests whether re-reset or insufficient delay causes failure +- `test_i2c_pinpoint.py` -- the definitive test: compares 0x84, 0x85, and 0x82 + +### What Happens Inside the FX2 I2C Controller + +The FX2's I2C master controller is a hardware peripheral accessed through the `I2CS`, `I2DAT`, and `I2CTL` SFRs. The controller implements an I2C state machine in silicon. Writing `bmSTOP` to `I2CS` instructs the hardware to generate a STOP condition (SDA rising while SCL is high). + +When no I2C transaction is active -- no prior START has been issued, and the bus is idle -- writing `bmSTOP` puts the controller into an inconsistent internal state. The `bmSTOP` bit may not clear properly (it is supposed to self-clear when the STOP condition completes on the bus), and subsequent START conditions fail to generate proper clock sequences or detect ACK from slaves. + +The Cypress TRM (EZ-USB Technical Reference Manual) does not explicitly warn against this, but the I2C chapter describes STOP as a step that follows a completed read or write transaction. It is not documented as a standalone bus-reset mechanism. + +The correct way to ensure a clean I2C bus state on the FX2 is to simply proceed with a new START condition. If the bus is idle (which it will be after power-on or after the previous transaction completed normally), the START succeeds and the controller enters its normal operating state. The hardware handles bus arbitration automatically on START. + +## The Fix + +The fix is a single deletion. Remove the spurious STOP from the boot sequence: + +```c +/* BEFORE (broken): */ +I2CS |= bmSTOP; +i2c_wait_stop(); + +/* AFTER (correct): */ +/* NOTE: Do NOT send I2CS bmSTOP here. Sending STOP when no transaction + * is active corrupts the FX2 I2C controller state, causing subsequent + * START+ACK detection to fail. The I2C bus will be in a clean state + * when we reach the probe step -- any prior transaction ended with STOP. */ +``` + +The corrected `bcm4500_boot()` function proceeds directly from GPIO/power setup to the I2C probe read without any bus-reset step: + +```c +static BOOL bcm4500_boot(void) { + boot_stage = 1; + cancel_i2c_trans = FALSE; + + /* P3.7, P3.6, P3.5 HIGH (idle state for control lines) */ + IOD |= 0xE0; + + /* Assert BCM4500 hardware RESET (P0.5 LOW) */ + OEA |= PIN_BCM_RESET; + IOA &= ~PIN_BCM_RESET; + + /* No I2CS bmSTOP here -- see note above */ + + /* Power on: P0.1 HIGH (enable), P0.2 LOW (disable off) */ + OEA |= (PIN_PWR_EN | PIN_PWR_DIS); + IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN; + + boot_stage = 2; + delay(30); /* power settle */ + + IOA |= PIN_BCM_RESET; /* release reset */ + delay(50); /* BCM4500 POR + mask ROM boot */ + + boot_stage = 3; + /* I2C probe -- if this fails, the chip didn't come out of reset */ + if (!bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) + return FALSE; + + /* ... register init blocks follow ... */ +} +``` + +## I2C Timeout Protection + +Even with the `bmSTOP` fix, timeout protection on all I2C operations is essential. The FX2's I2C controller has no hardware timeout -- if a slave device holds SCL low (clock stretching), or if an electrical fault prevents `bmDONE` from asserting, the firmware will spin forever in a polling loop. + +### The Problem with fx2lib + +The fx2lib `i2c_write()` and `i2c_read()` functions poll `bmDONE` and `bmSTOP` with loops like: + +```c +while ( !(I2CS & bmDONE) && !cancel_i2c_trans); +``` + +The `cancel_i2c_trans` flag is declared as `volatile __xdata BOOL` and is set to `FALSE` at the start of each transaction. The library documentation says firmware can set it to `TRUE` from an interrupt to abort a stuck transaction. In practice, nothing in the firmware sets it, so these loops are effectively: + +```c +while (!(I2CS & bmDONE)); // infinite if bmDONE never asserts +``` + +### Timeout-Protected Replacements + +The custom firmware replaces all fx2lib I2C functions with timeout-protected wrappers: + +```c +#define I2C_TIMEOUT 6000 + +static BOOL i2c_wait_done(void) { + WORD timeout = I2C_TIMEOUT; + while (!(I2CS & bmDONE)) { + if (--timeout == 0) + return FALSE; + } + return TRUE; +} + +static BOOL i2c_wait_stop(void) { + WORD timeout = I2C_TIMEOUT; + while (I2CS & bmSTOP) { + if (--timeout == 0) + return FALSE; + } + return TRUE; +} +``` + +A `WORD` counter of 6000, decremented in a tight SDCC-compiled loop at 48MHz (4 clocks per 8051 machine cycle, ~12 MIPS), gives approximately 5-10ms per wait. At 400kHz I2C, a single byte transfer (9 clock pulses) takes 22.5 microseconds, so this timeout provides well over 200x margin for normal operations while still bounding the worst case. + +All BCM4500 I2C operations -- `i2c_combined_read`, `i2c_write_timeout`, `i2c_write_multi_timeout` -- use these timeout-protected waits and return `FALSE` on timeout, allowing the caller to report failure rather than hanging the firmware. + +## Kernel Driver Race Condition + +The `dvb_usb_gp8psk` kernel module auto-loads via udev when VID:PID `09C0:0203` appears on the USB bus. This happens every time the FX2 re-enumerates after firmware load. The kernel driver races with the test tools and sends its own `BOOT_8PSK` command (along with other initialization), which interferes with debugging. + +Symptoms of this race condition: +- Test scripts report "resource busy" or "entity not found" errors +- The BCM4500 enters an unexpected state because the kernel driver partially initialized it +- The kernel driver detaches from the device mid-test + +The fix is to blacklist the module: + +``` +# /etc/modprobe.d/blacklist-gp8psk.conf +blacklist dvb_usb_gp8psk +blacklist gp8psk_fe +``` + +After creating this file, run `sudo modprobe -r dvb_usb_gp8psk gp8psk_fe` to unload any currently-loaded instances. The blacklist prevents udev from auto-loading the module on device insertion, giving test tools exclusive access. + +## I2C Bus Scan Results + +Vendor command `0xB4` performs a full 7-bit I2C bus scan by attempting a START + address + WRITE to every address from 0x01 to 0x77 and checking for ACK. Three devices were found: + +| Address | Identity | +|---------|----------| +| `0x08` | BCM4500 demodulator. Status register `0xA2` returns valid data. This is the primary device for all demodulator operations. | +| `0x10` | Likely the tuner or LNB controller. The SkyWalker-1 uses a separate tuner IC (accessed through the BCM4500 in normal operation, but also directly addressable on the shared I2C bus). | +| `0x51` | Likely a configuration EEPROM. Many DVB-S receivers store tuner calibration data or device serial numbers in a small I2C EEPROM at addresses in the 0x50-0x57 range. | + +The BCM4500's 7-bit I2C address of `0x08` corresponds to 8-bit wire addresses of `0x10` (write) and `0x11` (read). + +## BCM4500 Boot Results After Fix + +With the `bmSTOP` removed, the full boot sequence completes reliably: + +- **Boot time:** ~90ms total (30ms power settle + 50ms post-reset delay + ~10ms I2C init) +- **config_status:** `0x03` (STARTED | FW_LOADED) +- **boot_stage:** `0xFF` (COMPLETE) +- **Direct registers 0xA2-0xA8:** All return `0x02` (powered, not locked -- expected without a satellite signal) +- **Signal lock:** `0x00` (no lock -- dish not aimed at satellite) +- **Signal strength:** All zeros (same reason) +- **USB responsiveness:** No hang. The firmware remains fully responsive to vendor commands throughout boot and afterward. + +## Firmware v3.01.0 Boot Sequence (Corrected) + +The complete boot sequence as implemented in `bcm4500_boot()`: + +1. **Assert BCM4500 RESET** -- Drive P0.5 LOW. This holds the BCM4500's digital logic in reset while power is applied. + +2. **Power on** -- Set P0.1 HIGH (power enable), P0.2 LOW (power disable off). The SkyWalker-1 has complementary power control pins. + +3. **delay(30ms)** -- Allow the power supply to settle and reach regulation. The stock firmware uses the same delay. + +4. **Release RESET** -- Drive P0.5 HIGH. The BCM4500 begins its internal power-on reset (POR) and mask ROM boot sequence. + +5. **delay(50ms)** -- Wait for the BCM4500's POR and internal initialization to complete. The chip needs time for its internal oscillator to stabilize and mask ROM to execute. + +6. **I2C probe** -- Read direct register `0xA2` (status) to verify the chip is alive and responding on I2C. If this fails, the boot aborts. + +7. **Write init block 0** -- 7 bytes to BCM4500 indirect page 0, starting at register `0x06`. Written via the `0xA6`/`0xA7`/`0xA8` indirect register protocol. Data: `{0x06, 0x0b, 0x17, 0x38, 0x9f, 0xd9, 0x80}`. + +8. **Write init block 1** -- 8 bytes to page 0, starting at register `0x07`. Data: `{0x07, 0x09, 0x39, 0x4f, 0x00, 0x65, 0xb7, 0x10}`. + +9. **Write init block 2** -- 3 bytes to page 0, starting at register `0x0F`. Data: `{0x0f, 0x0c, 0x09}`. + +10. **Set config_status** -- OR in `BM_STARTED | BM_FW_LOADED` (`0x03`). Subsequent vendor commands (tuning, signal strength readout, etc.) check this flag before operating. + +The three initialization blocks were extracted from disassembly of the stock v2.06 firmware's `FUN_CODE_0ddd` routine, which performs the same indirect register writes. + +## FX2 Hardware Recovery Note + +The FX2's CPUCS register at address `0xE600` controls the 8051 CPU's run/halt state. It is accessible via the standard vendor request bRequest=0xA0 (RAM read/write) even when the user firmware is completely hung in an infinite loop. + +This works because bRequest=0xA0 is handled by the FX2 silicon's boot ROM, not by firmware. The boot ROM's USB handler runs in a hardware-priority context that preempts the 8051's main loop. Writing `0x01` to CPUCS halts the CPU, new firmware can be loaded into RAM, and writing `0x00` starts it again. + +This means `fw_load.py` can reload firmware over a hung device without requiring a physical USB unplug/replug or power cycle. For iterative firmware development, this is significant -- a failed boot attempt that hangs the firmware can be recovered from the host side in seconds: + +```bash +sudo python3 tools/fw_load.py load firmware/build/skywalker1.ihx --wait 3 +``` + +The load sequence halts the CPU (CPUCS=0x01), writes new code into RAM, then restarts the CPU (CPUCS=0x00). The device re-enumerates with the new firmware. diff --git a/docs/diseqc/diseqc-skywalker-1.md b/docs/diseqc/diseqc-skywalker-1.md index 9bb74c1..58fa5d6 100644 --- a/docs/diseqc/diseqc-skywalker-1.md +++ b/docs/diseqc/diseqc-skywalker-1.md @@ -1,79 +1,79 @@ -# DiSEqC for the GenPix Skywalker-1 BDA Driver (Extended) - -**Implementation Guidelines for Applications** - -*Author: Devendra | Created: 2009-07-09 | Source: Microsoft Office Word 2007* - ---- - -## I. GUID for the SkyWalker1 Extended property - -```c -//Used to extend the feature of the BDA -//{0B5221EB-F4C4-4976-B959-EF74427464D9} -#define STATIC_KSPROPSETID_BdaExtendedProperty \ - 0x0B5221EB, 0xF4C4, 0x4976, 0xB9, 0x59, 0xEF, 0x74, 0x42, 0x74, 0x64, 0xD9 - -DEFINE_GUIDSTRUCT("0B5221EB-F4C4-4976-B959-EF74427464D9", KSPROPSETID_BdaExtendedProperty); -#define KSPROPSETID_BdaExtendedProperty DEFINE_GUIDNAMED(KSPROPSETID_BdaExtendedProperty) -``` - -## II. Extended Property List (Only DiSEqC support is extended) - -```c -//Extended Property List -typedef enum __KSPROPERTY_EXTENDED -{ - /* DiSEqC Command */ - //Used to send the Digital Satellite Equipment Control (DiSEqC) - //Commands by application - KSPROPERTY_BDA_DISEQC = 0, //Extension Property 1 -} KSPROPERTY_EXTENDED; -``` - -## III. Enumeration for the Simple Tone Burst - -```c -typedef enum enSimpleToneBurst -{ - SEC_MINI_A, - SEC_MINI_B -} SIMPLE_TONE_BURST; -``` - -## IV. DiSEqC Command Structure - -```c -typedef struct __DISEQC_COMMAND -{ - UCHAR ucMessage[MAX_DISEQC_COMMAND_LENGTH]; - /* Byte - 0 : Framing, - Byte - 1 : Address, - Byte - 2 : Command, - Byte - 3 : Data[0], - Byte - 4 : Data[1], - Byte - 5 : Data[2] */ - - UCHAR ucMessageLength; - /* The Valid values for DiSEqC Command are 3...6 - If this value is 1 then Byte 0 is taken as Simple "Tone Burst" - Control Command */ -} DISEQC_COMMAND, *PDISEQC_COMMAND; -``` - -## V. Operation - -### i. To send the Simple Burst command - -1. Create the `DISEQC_COMMAND` Structure -2. Set `ucMessage[0]` to either `SEC_MINI_A` or `SEC_MINI_B` -3. Set `ucMessageLength` as `1` - -### ii. To send DiSEqC commands - -1. Create the `DISEQC_COMMAND` Structure -2. Set the Framing value to `ucMessage[0]` (e.g. `0xE0`) -3. Set the Device Address to `ucMessage[1]` (e.g. `0x01`) -4. Send the Command for the Device to `ucMessage[2]` -5. If required, set the Data bytes `ucMessage[3]`, `ucMessage[4]`, `ucMessage[5]` -6. Set `ucMessageLength` accordingly. Valid values are 3 to 6. +# DiSEqC for the GenPix Skywalker-1 BDA Driver (Extended) + +**Implementation Guidelines for Applications** + +*Author: Devendra | Created: 2009-07-09 | Source: Microsoft Office Word 2007* + +--- + +## I. GUID for the SkyWalker1 Extended property + +```c +//Used to extend the feature of the BDA +//{0B5221EB-F4C4-4976-B959-EF74427464D9} +#define STATIC_KSPROPSETID_BdaExtendedProperty \ + 0x0B5221EB, 0xF4C4, 0x4976, 0xB9, 0x59, 0xEF, 0x74, 0x42, 0x74, 0x64, 0xD9 + +DEFINE_GUIDSTRUCT("0B5221EB-F4C4-4976-B959-EF74427464D9", KSPROPSETID_BdaExtendedProperty); +#define KSPROPSETID_BdaExtendedProperty DEFINE_GUIDNAMED(KSPROPSETID_BdaExtendedProperty) +``` + +## II. Extended Property List (Only DiSEqC support is extended) + +```c +//Extended Property List +typedef enum __KSPROPERTY_EXTENDED +{ + /* DiSEqC Command */ + //Used to send the Digital Satellite Equipment Control (DiSEqC) + //Commands by application + KSPROPERTY_BDA_DISEQC = 0, //Extension Property 1 +} KSPROPERTY_EXTENDED; +``` + +## III. Enumeration for the Simple Tone Burst + +```c +typedef enum enSimpleToneBurst +{ + SEC_MINI_A, + SEC_MINI_B +} SIMPLE_TONE_BURST; +``` + +## IV. DiSEqC Command Structure + +```c +typedef struct __DISEQC_COMMAND +{ + UCHAR ucMessage[MAX_DISEQC_COMMAND_LENGTH]; + /* Byte - 0 : Framing, + Byte - 1 : Address, + Byte - 2 : Command, + Byte - 3 : Data[0], + Byte - 4 : Data[1], + Byte - 5 : Data[2] */ + + UCHAR ucMessageLength; + /* The Valid values for DiSEqC Command are 3...6 + If this value is 1 then Byte 0 is taken as Simple "Tone Burst" + Control Command */ +} DISEQC_COMMAND, *PDISEQC_COMMAND; +``` + +## V. Operation + +### i. To send the Simple Burst command + +1. Create the `DISEQC_COMMAND` Structure +2. Set `ucMessage[0]` to either `SEC_MINI_A` or `SEC_MINI_B` +3. Set `ucMessageLength` as `1` + +### ii. To send DiSEqC commands + +1. Create the `DISEQC_COMMAND` Structure +2. Set the Framing value to `ucMessage[0]` (e.g. `0xE0`) +3. Set the Device Address to `ucMessage[1]` (e.g. `0x01`) +4. Send the Command for the Device to `ucMessage[2]` +5. If required, set the Data bytes `ucMessage[3]`, `ucMessage[4]`, `ucMessage[5]` +6. Set `ucMessageLength` accordingly. Valid values are 3 to 6. diff --git a/dvb-s2-investigation.md b/dvb-s2-investigation.md index 474c54d..1d0f221 100644 --- a/dvb-s2-investigation.md +++ b/dvb-s2-investigation.md @@ -1,255 +1,255 @@ -# DVB-S2 Incompatibility Investigation: Genpix SkyWalker-1 - -## Definitive Conclusion - -**The SkyWalker-1's inability to receive DVB-S2 is a fundamental hardware limitation of the Broadcom BCM4500 demodulator silicon, not a firmware limitation.** The BCM4500 was designed and fabricated before the DVB-S2 standard was ratified (2005) and contains no LDPC or BCH decoder hardware. DVB-S2 requires LDPC (Low-Density Parity-Check) and BCH (Bose-Chaudhuri-Hocquenghem) forward error correction -- entirely different decoder architectures from the Viterbi/turbo/Reed-Solomon decoders present in the BCM4500. No firmware update could add DVB-S2 support to this hardware. - -Genpix eventually addressed this by releasing the SkyWalker-3, which replaced the entire demodulator subsystem (likely switching from Broadcom BCM4500 to STMicroelectronics STV0903), trading turbo-FEC support for DVB-S2 LDPC/BCH capability. - ---- - -## 1. Does the BCM4500 Silicon Support DVB-S2? - -**No. The BCM4500 has no LDPC or BCH decoder hardware.** - -### BCM4500 FEC Architecture (from datasheet) - -The BCM4500 contains exactly two FEC decoder paths: - -1. **Advanced Modulation Turbo FEC Decoder** -- an iterative turbo code decoder supporting: - - QPSK: rates 1/4, 1/2, 3/4 - - 8PSK: rates 2/3, 3/4, 5/6, 8/9 - - 16QAM: rate 3/4 - - Reed-Solomon outer code (t=10) after turbo decoding - -2. **Legacy DVB/DIRECTV/DCII-Compliant FEC Decoder** -- a concatenated coding chain: - - Inner: Viterbi decoder (convolutional code, rates 1/2 through 7/8) - - Outer: Reed-Solomon decoder - -The datasheet describes the signal path explicitly: "Optimized soft decisions are then fed into either a DVB/DIRECTV/DCII-compliant FEC decoder, or an advanced modulation turbo decoder." These are the only two paths. There is no third path for LDPC/BCH. - -### DVB-S2 FEC Architecture (for comparison) - -DVB-S2 (EN 302 307, ratified March 2005) mandates: - -- **Inner code**: LDPC (Low-Density Parity-Check) -- block lengths of 64,800 or 16,200 bits -- **Outer code**: BCH (Bose-Chaudhuri-Hocquenghem) -- **Code rates**: 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 5/6, 8/9, 9/10 - -LDPC decoding requires dedicated hardware: large block RAM for message passing (the LDPC block size is 64,800 bits, requiring significant on-chip storage), iterative belief propagation logic, and a fundamentally different decoder architecture from both Viterbi and turbo decoders. This cannot be emulated in firmware on the BCM4500's simple 8-bit on-chip microcontroller (used only for configuration, acquisition, and monitoring -- not data-path processing). - -### Evidence from the BCM4500 Datasheet - -Source: [BCM4500 Datasheet (DatasheetQ)](https://html.datasheetq.com/pdf-html/885700/Broadcom/2page/BCM4500.html), [BCM4500 Datasheet (Elcodis)](https://elcodis.com/parts/5786421/BCM4500.html) - -Key specifications confirming no DVB-S2 capability: -- Modulation: BPSK, QPSK, 8PSK, 16QAM (no mention of DVB-S2-specific constellations) -- FEC: "advanced modulation turbo FEC decoder" and "DVB/DIRECTV/DCII-compliant FEC decoder" -- Symbol rate: 256 Ksps to 30 Msps -- Package: 128-pin MQFP -- Supply: 3.3V I/O, 1.8V digital -- No mention of LDPC, BCH, or DVB-S2 anywhere in the datasheet - ---- - -## 2. What FEC Types Does the BCM4500 Actually Support? - -The BCM4500 supports three distinct FEC coding families, none of which are DVB-S2 compatible: - -### 2.1 Viterbi + Reed-Solomon (Legacy DVB-S / DSS / DCII) - -Used for standard DVB-S QPSK, DSS QPSK, DVB-S BPSK, and Digicipher II modes. - -**Firmware evidence** (from `skywalker1-hardware-reference.md`, Section 6.3): -- FEC lookup table at XRAM 0xE0F9, maximum index 7 -- Modulation dispatch sets XRAM 0xE0F6 = 0x00 (turbo flag OFF) -- XRAM 0xE0F5 = 0x10 (standard demod mode) -- The firmware FEC table supports rates: 1/2, 2/3, 3/4, 5/6, 7/8, auto, none - -**Windows driver evidence** (`SkyWalker1Control.h`, line 59): -```c -m_CurResource.ulInnerFecType = BDA_FEC_VITERBI; -``` - -**Windows driver evidence** (`SkyWalker1TunerFilter.cpp`, lines 1069-1070): -```c -//Only supported FEC VITERBI Type Error Correction -else if(ulNewInnerFecType == BDA_FEC_VITERBI) -``` - -The Windows BDA driver explicitly rejects any FEC type other than `BDA_FEC_VITERBI` and restricts code rates to 1/2, 2/3, 3/4, 5/6, 7/8 (lines 1112-1116). There is no `BDA_FEC_LDPC` handling. - -### 2.2 Turbo Codes (Proprietary 8PSK/QPSK/16QAM) - -Used for Turbo QPSK, Turbo 8PSK, and Turbo 16QAM -- the proprietary "advanced modulation" modes developed by Broadcom for EchoStar/Dish Network. - -**Firmware evidence** (from `tuning-protocol-analysis.md`, Section 3): -- Turbo QPSK: FEC table at XRAM 0xE0B7, max index 5 -- Turbo 8PSK: FEC table at XRAM 0xE0B1, max index 5 -- Turbo 16QAM: FEC table at XRAM 0xE0BC, max index 1 -- All turbo modes set XRAM 0xE0F6 = 0x01 (turbo flag ON) - -These turbo codes are proprietary to EchoStar/Broadcom. They are NOT the same as DVB-S2's LDPC codes, despite both being "advanced" coding schemes. The turbo decoder uses a fundamentally different iterative decoding algorithm (parallel concatenated convolutional codes) compared to LDPC (sparse parity-check matrix belief propagation). - -### 2.3 Digicipher II (Motorola/GI Proprietary) - -Used for DCII combo, split I/Q, and offset QPSK modes. - -**Firmware evidence**: FEC table at XRAM 0xE0BD, max index 9, with a fixed FEC code of 0xFC written to XRAM 0xE0EB. - -### Summary: FEC Architecture Comparison - -| Feature | BCM4500 (SkyWalker-1) | DVB-S2 Requirement | -|---------|----------------------|-------------------| -| Inner FEC | Viterbi (DVB-S) or Turbo (proprietary) | LDPC | -| Outer FEC | Reed-Solomon (t=10) | BCH | -| Block size | Convolutional (streaming) / Turbo (short blocks) | 64,800 or 16,200 bits | -| Decoder type | Trellis-based (Viterbi) or iterative turbo | Iterative belief propagation | -| Hardware IP | Hardwired Viterbi + turbo silicon | Requires dedicated LDPC engine | -| Standardization | DVB-S (ETSI EN 300 421) + proprietary turbo | DVB-S2 (ETSI EN 302 307) | - ---- - -## 3. What Would a DVB-S2-Capable Replacement Look Like? - -### Broadcom's Own DVB-S2 Chip Timeline - -Broadcom addressed DVB-S2 by designing entirely new silicon: - -| Chip | Year | DVB-S2? | Key Feature | Source | -|------|------|---------|-------------|--------| -| **BCM4500** | ~2003 | No | Turbo FEC + legacy Viterbi/RS | [Datasheet](https://elcodis.com/parts/5786421/BCM4500.html) | -| **BCM4501** | 2006 | **Yes** | First dual-tuner DVB-S2 receiver; LDPC/BCH decoder | [Broadcom](https://www.broadcom.com/products/broadband/set-top-box/bcm4501), [EDN](https://www.edn.com/bcm4501-dual-dvb-s2-advanced-modulation-satellite-receiver/) | -| **BCM4505** | 2007 | **Yes** | Single-channel, 65nm, LDPC/BCH + legacy | [Broadcom](https://www.broadcom.com/products/broadband/set-top-box/bcm4505) | -| **BCM4506** | 2007 | **Yes** | Dual-channel, 65nm, LDPC/BCH + legacy | [Broadcom](https://www.broadcom.com/products/broadband/set-top-box/bcm4506) | - -The BCM4501 datasheet explicitly states it includes "four 8-bit ADCs, all-digital variable rate QPSK/8PSK receivers, advanced modulation LDPC/BCH, and DVB-S-compliant forward error correction decoder." The addition of LDPC/BCH required new silicon -- it was not a firmware upgrade to the BCM4500. - -However, Broadcom restricted sales of these chips to set-top box manufacturers (EchoStar, DIRECTV) and did not sell to PC peripheral makers. This is why Genpix could not simply drop in a BCM4501. - -### What Genpix Actually Did: The SkyWalker-3 - -Genpix released the SkyWalker-3 as a DVB-S2-capable successor, using a completely different demodulator: - -| Feature | SkyWalker-1 (BCM4500) | SkyWalker-3 (likely STV0903) | -|---------|----------------------|---------------------------| -| DVB-S QPSK | Yes | Yes | -| DVB-S2 QPSK | No | Yes (rates 1/2 through 9/10) | -| DVB-S2 8PSK | No | Yes (rates 3/5 through 9/10) | -| Turbo QPSK | Yes | **No** | -| Turbo 8PSK | Yes | **No** | -| Turbo 16QAM | Yes | **No** | -| DCII | Yes | Yes | -| DSS | Yes | Yes | -| Symbol rate (DVB-S) | 256 Ksps - 30 Msps | 1 - 45 Msps | -| Symbol rate (DVB-S2) | N/A | 5 - 33 Msps | -| FEC inner (DVB-S) | Viterbi | Viterbi | -| FEC inner (DVB-S2) | N/A | LDPC | -| FEC outer (DVB-S2) | N/A | BCH | -| Demodulator | Broadcom BCM4500 | STMicroelectronics STV0903 (probable) | -| Tuner | Broadcom BCM3440 | STMicroelectronics STV6110 (probable) | - -Source: [Genpix SkyWalker-3 specifications](https://www.genpix-electronics.com/what-is-skywalker-3.html) - -The trade-off is visible: the SkyWalker-3 gained DVB-S2 but lost turbo-FEC support entirely. The turbo codes were proprietary to Broadcom/EchoStar, and the STMicroelectronics STV0903 demodulator does not implement them. This means the SkyWalker-3 cannot receive Dish Network's legacy turbo-coded 8PSK transmissions. - ---- - -## 4. Are There Any Hints of DVB-S2 Awareness in the Firmware? - -**No. There are zero references to DVB-S2, LDPC, or BCH in any firmware version or in the Windows driver source.** - -### Firmware Search Results - -Searched all three firmware versions (v2.06, Rev.2 v2.10, v2.13) via Ghidra disassembly and the following source files: - -- `SkyWalker1Control.h` -- defines modulation constants 0-9, none related to DVB-S2 -- `SkyWalker1CommonDef.h` -- device parameter structure uses `BDA_FEC_VITERBI` only -- `SkyWalker1TunerFilter.cpp` -- explicitly rejects non-QPSK modulation types and non-Viterbi FEC -- `SkyWalker1Control.cpp` -- hardcodes `ADV_MOD_DVB_QPSK` (value 0) in tune command byte 8 - -**Specific evidence of no DVB-S2 awareness:** - -1. **Modulation enum caps at 9** (`SkyWalker1Control.h`, lines 64-74): The modulation constants are `ADV_MOD_DVB_QPSK` (0) through `ADV_MOD_DVB_BPSK` (9). No value 10+ exists for DVB-S2 modes. - -2. **Firmware dispatch table has exactly 10 entries** (`tuning-protocol-analysis.md`, Section 3.1): The jump table at CODE:0873 contains 20 bytes (10 entries x 2 bytes). Modulation values >= 10 are rejected by the bounds check at CODE:0866. - -3. **FEC type is hardcoded to Viterbi** (`SkyWalker1TunerFilter.cpp`, line 1070): `else if(ulNewInnerFecType == BDA_FEC_VITERBI)` -- only Viterbi is accepted; any other FEC type returns `STATUS_INVALID_PARAMETER`. - -4. **Tune command hardcodes DVB-S QPSK** (`SkyWalker1Control.cpp`, line 292): `ucCommand[8] = ADV_MOD_DVB_QPSK;` -- the Windows driver always sends modulation type 0 (DVB-S QPSK) regardless of what the application requests. - -5. **No LDPC/BCH code rate values** exist in any FEC lookup table. The firmware's XRAM tables at 0xE0B1, 0xE0B7, 0xE0BC, 0xE0BD, and 0xE0F9 contain only Viterbi rates (1/2 through 7/8), turbo rates, and DCII combined codes. - -6. **No DVB-S2-specific register addresses** appear in the I2C traffic. The BCM4500 is programmed exclusively through indirect registers 0xA6/0xA7/0xA8 with page 0x00 -- a protocol specific to the BCM4500. DVB-S2 demodulators like the STV0903 use entirely different register maps. - ---- - -## 5. Could the GPIF Streaming Path Handle DVB-S2 Data Rates? - -**Yes -- the USB data path is not the bottleneck. The GPIF/USB 2.0 streaming architecture could handle DVB-S2 data rates if the demodulator supported them.** - -### Data Rate Analysis - -**DVB-S2 maximum useful bit rate** (from ETSI EN 302 307): -- Highest configuration: 8PSK, rate 9/10, 30 Msps = ~72 Mbps raw, ~58 Mbps net after FEC -- Typical HD transponder: 8PSK, rate 3/4, 27.5 Msps = ~44 Mbps net - -**GPIF/USB 2.0 throughput capacity:** -- USB 2.0 High Speed bulk: 480 Mbps theoretical, ~35 MB/s (~280 Mbps) practical -- GPIF engine: 48 MHz clock, 8-bit data path = 48 MB/s (384 Mbps) theoretical -- EP2 FIFO: 4x buffer with AUTOIN, 7 URBs x 8KB on host side - -**Current DVB-S usage** (from `gpif-streaming-analysis.md`, Section 15): -- "Transport stream rate: BCM4500 outputs at the satellite symbol rate (up to 30 Msps), but the effective byte rate depends on modulation and coding. USB 2.0 High Speed bulk bandwidth (480 Mbps theoretical, ~35 MB/s practical) is more than sufficient for DVB-S transport streams (typically 1-5 MB/s)." - -**Assessment**: Even at the theoretical maximum DVB-S2 data rate of ~58 Mbps (~7.25 MB/s), the USB 2.0 bulk streaming path has approximately 5x headroom. The GPIF engine configuration (IFCONFIG=0xEE, EP2FIFOCFG=0x0C, FLOWSTATEA with FSEN enabled) is identical across all firmware versions and provides a fully hardware-managed pipeline that would not require modification. - -The 8-bit parallel transport stream interface between the demodulator and FX2 is also sufficient -- DVB-S2 uses the same MPEG-TS output format (188-byte packets) as DVB-S. The GPIF waveform and AUTOIN configuration would work unchanged. - -**However**, this is a moot point. The bottleneck is the demodulator silicon, not the data path. Even if you physically replaced the BCM4500 with a DVB-S2-capable chip, you would need to rewrite the entire FX2 firmware (I2C register protocol, tuning sequence, modulation dispatch, FEC configuration) since every DVB-S2 demodulator uses a completely different register interface. - ---- - -## Summary - -| Question | Answer | -|----------|--------| -| Is DVB-S2 a hardware or firmware limitation? | **Hardware** -- the BCM4500 has no LDPC/BCH decoder logic | -| Could a firmware update add DVB-S2? | **No** -- LDPC decoding requires dedicated silicon | -| Which Broadcom chip first added LDPC? | **BCM4501** (2006), followed by BCM4505/BCM4506 (2007) | -| Any DVB-S2 hints in firmware/driver? | **None** -- zero references to LDPC, BCH, or DVB-S2 | -| Is the USB data path a bottleneck? | **No** -- USB 2.0 bulk has ~5x headroom for DVB-S2 rates | -| What did Genpix do for DVB-S2? | Released SkyWalker-3 with a different demodulator (likely STV0903) | -| What was lost in the transition? | Turbo-FEC support (proprietary to Broadcom/EchoStar) | - ---- - -## Sources - -### Datasheets and Product Pages -- [BCM4500 Datasheet (DatasheetQ)](https://html.datasheetq.com/pdf-html/885700/Broadcom/2page/BCM4500.html) -- [BCM4500 Datasheet (Elcodis)](https://elcodis.com/parts/5786421/BCM4500.html) -- [BCM4500 Datasheet (AllDatasheet)](https://www.alldatasheet.com/datasheet-pdf/pdf/85246/BOARDCOM/BCM4500.html) -- [BCM4501 Product Page (Broadcom)](https://www.broadcom.com/products/broadband/set-top-box/bcm4501) -- [BCM4501 (EDN)](https://www.edn.com/bcm4501-dual-dvb-s2-advanced-modulation-satellite-receiver/) -- [BCM4505 Product Page (Broadcom)](https://www.broadcom.com/products/broadband/set-top-box/bcm4505) -- [BCM4506 Product Page (Broadcom)](https://www.broadcom.com/products/broadband/set-top-box/bcm4506) -- [BCM4505/BCM4506 Announcement (RTTNews)](https://www.rttnews.com/380136/broadcom-launches-bcm4505-and-bcm4506-two-fully-integrated-single-chip-single-and-dual-channel-multi-format-satellite-receivers-quick-facts.aspx) - -### Genpix Products -- [Genpix SkyWalker-3 Specifications](https://www.genpix-electronics.com/what-is-skywalker-3.html) -- [Genpix Official Site](https://www.genpix-electronics.com/index.php?act=viewDoc&docId=9) - -### Community and Technical Discussions -- [LinuxTV mailing list: BCM4500 and DVB-S2 distinction](https://www.mail-archive.com/linux-dvb@linuxtv.org/msg24808.html) -- [SatelliteGuys: Turbo 8PSK card reverse engineering](https://www.satelliteguys.us/xen/threads/another-turbo-8psk-card.246879/) -- [SatelliteGuys: Genpix SkyWalker-1 discussion](https://www.satelliteguys.us/xen/threads/genpix-skywalker-1.214196/) - -### Reverse Engineering Analysis (This Project) -- `skywalker1-hardware-reference.md` -- Sections 1, 6, 7: Overview, tuning protocol, BCM4500 interface -- `tuning-protocol-analysis.md` -- Section 3: Modulation dispatch table, FEC lookup tables -- `gpif-streaming-analysis.md` -- Sections 13-15: GPIF throughput and data path analysis -- `rev2-deep-analysis.md` -- Complete Rev.2 function inventory -- `SkyWalker1Control.h` -- Modulation mode constants (lines 63-74), FEC/command definitions -- `SkyWalker1TunerFilter.cpp` -- SetInnerFecType() Viterbi-only restriction (lines 1058-1086) -- `SkyWalker1Control.cpp` -- TuneDevice() hardcoded ADV_MOD_DVB_QPSK (line 292) +# DVB-S2 Incompatibility Investigation: Genpix SkyWalker-1 + +## Definitive Conclusion + +**The SkyWalker-1's inability to receive DVB-S2 is a fundamental hardware limitation of the Broadcom BCM4500 demodulator silicon, not a firmware limitation.** The BCM4500 was designed and fabricated before the DVB-S2 standard was ratified (2005) and contains no LDPC or BCH decoder hardware. DVB-S2 requires LDPC (Low-Density Parity-Check) and BCH (Bose-Chaudhuri-Hocquenghem) forward error correction -- entirely different decoder architectures from the Viterbi/turbo/Reed-Solomon decoders present in the BCM4500. No firmware update could add DVB-S2 support to this hardware. + +Genpix eventually addressed this by releasing the SkyWalker-3, which replaced the entire demodulator subsystem (likely switching from Broadcom BCM4500 to STMicroelectronics STV0903), trading turbo-FEC support for DVB-S2 LDPC/BCH capability. + +--- + +## 1. Does the BCM4500 Silicon Support DVB-S2? + +**No. The BCM4500 has no LDPC or BCH decoder hardware.** + +### BCM4500 FEC Architecture (from datasheet) + +The BCM4500 contains exactly two FEC decoder paths: + +1. **Advanced Modulation Turbo FEC Decoder** -- an iterative turbo code decoder supporting: + - QPSK: rates 1/4, 1/2, 3/4 + - 8PSK: rates 2/3, 3/4, 5/6, 8/9 + - 16QAM: rate 3/4 + - Reed-Solomon outer code (t=10) after turbo decoding + +2. **Legacy DVB/DIRECTV/DCII-Compliant FEC Decoder** -- a concatenated coding chain: + - Inner: Viterbi decoder (convolutional code, rates 1/2 through 7/8) + - Outer: Reed-Solomon decoder + +The datasheet describes the signal path explicitly: "Optimized soft decisions are then fed into either a DVB/DIRECTV/DCII-compliant FEC decoder, or an advanced modulation turbo decoder." These are the only two paths. There is no third path for LDPC/BCH. + +### DVB-S2 FEC Architecture (for comparison) + +DVB-S2 (EN 302 307, ratified March 2005) mandates: + +- **Inner code**: LDPC (Low-Density Parity-Check) -- block lengths of 64,800 or 16,200 bits +- **Outer code**: BCH (Bose-Chaudhuri-Hocquenghem) +- **Code rates**: 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 5/6, 8/9, 9/10 + +LDPC decoding requires dedicated hardware: large block RAM for message passing (the LDPC block size is 64,800 bits, requiring significant on-chip storage), iterative belief propagation logic, and a fundamentally different decoder architecture from both Viterbi and turbo decoders. This cannot be emulated in firmware on the BCM4500's simple 8-bit on-chip microcontroller (used only for configuration, acquisition, and monitoring -- not data-path processing). + +### Evidence from the BCM4500 Datasheet + +Source: [BCM4500 Datasheet (DatasheetQ)](https://html.datasheetq.com/pdf-html/885700/Broadcom/2page/BCM4500.html), [BCM4500 Datasheet (Elcodis)](https://elcodis.com/parts/5786421/BCM4500.html) + +Key specifications confirming no DVB-S2 capability: +- Modulation: BPSK, QPSK, 8PSK, 16QAM (no mention of DVB-S2-specific constellations) +- FEC: "advanced modulation turbo FEC decoder" and "DVB/DIRECTV/DCII-compliant FEC decoder" +- Symbol rate: 256 Ksps to 30 Msps +- Package: 128-pin MQFP +- Supply: 3.3V I/O, 1.8V digital +- No mention of LDPC, BCH, or DVB-S2 anywhere in the datasheet + +--- + +## 2. What FEC Types Does the BCM4500 Actually Support? + +The BCM4500 supports three distinct FEC coding families, none of which are DVB-S2 compatible: + +### 2.1 Viterbi + Reed-Solomon (Legacy DVB-S / DSS / DCII) + +Used for standard DVB-S QPSK, DSS QPSK, DVB-S BPSK, and Digicipher II modes. + +**Firmware evidence** (from `skywalker1-hardware-reference.md`, Section 6.3): +- FEC lookup table at XRAM 0xE0F9, maximum index 7 +- Modulation dispatch sets XRAM 0xE0F6 = 0x00 (turbo flag OFF) +- XRAM 0xE0F5 = 0x10 (standard demod mode) +- The firmware FEC table supports rates: 1/2, 2/3, 3/4, 5/6, 7/8, auto, none + +**Windows driver evidence** (`SkyWalker1Control.h`, line 59): +```c +m_CurResource.ulInnerFecType = BDA_FEC_VITERBI; +``` + +**Windows driver evidence** (`SkyWalker1TunerFilter.cpp`, lines 1069-1070): +```c +//Only supported FEC VITERBI Type Error Correction +else if(ulNewInnerFecType == BDA_FEC_VITERBI) +``` + +The Windows BDA driver explicitly rejects any FEC type other than `BDA_FEC_VITERBI` and restricts code rates to 1/2, 2/3, 3/4, 5/6, 7/8 (lines 1112-1116). There is no `BDA_FEC_LDPC` handling. + +### 2.2 Turbo Codes (Proprietary 8PSK/QPSK/16QAM) + +Used for Turbo QPSK, Turbo 8PSK, and Turbo 16QAM -- the proprietary "advanced modulation" modes developed by Broadcom for EchoStar/Dish Network. + +**Firmware evidence** (from `tuning-protocol-analysis.md`, Section 3): +- Turbo QPSK: FEC table at XRAM 0xE0B7, max index 5 +- Turbo 8PSK: FEC table at XRAM 0xE0B1, max index 5 +- Turbo 16QAM: FEC table at XRAM 0xE0BC, max index 1 +- All turbo modes set XRAM 0xE0F6 = 0x01 (turbo flag ON) + +These turbo codes are proprietary to EchoStar/Broadcom. They are NOT the same as DVB-S2's LDPC codes, despite both being "advanced" coding schemes. The turbo decoder uses a fundamentally different iterative decoding algorithm (parallel concatenated convolutional codes) compared to LDPC (sparse parity-check matrix belief propagation). + +### 2.3 Digicipher II (Motorola/GI Proprietary) + +Used for DCII combo, split I/Q, and offset QPSK modes. + +**Firmware evidence**: FEC table at XRAM 0xE0BD, max index 9, with a fixed FEC code of 0xFC written to XRAM 0xE0EB. + +### Summary: FEC Architecture Comparison + +| Feature | BCM4500 (SkyWalker-1) | DVB-S2 Requirement | +|---------|----------------------|-------------------| +| Inner FEC | Viterbi (DVB-S) or Turbo (proprietary) | LDPC | +| Outer FEC | Reed-Solomon (t=10) | BCH | +| Block size | Convolutional (streaming) / Turbo (short blocks) | 64,800 or 16,200 bits | +| Decoder type | Trellis-based (Viterbi) or iterative turbo | Iterative belief propagation | +| Hardware IP | Hardwired Viterbi + turbo silicon | Requires dedicated LDPC engine | +| Standardization | DVB-S (ETSI EN 300 421) + proprietary turbo | DVB-S2 (ETSI EN 302 307) | + +--- + +## 3. What Would a DVB-S2-Capable Replacement Look Like? + +### Broadcom's Own DVB-S2 Chip Timeline + +Broadcom addressed DVB-S2 by designing entirely new silicon: + +| Chip | Year | DVB-S2? | Key Feature | Source | +|------|------|---------|-------------|--------| +| **BCM4500** | ~2003 | No | Turbo FEC + legacy Viterbi/RS | [Datasheet](https://elcodis.com/parts/5786421/BCM4500.html) | +| **BCM4501** | 2006 | **Yes** | First dual-tuner DVB-S2 receiver; LDPC/BCH decoder | [Broadcom](https://www.broadcom.com/products/broadband/set-top-box/bcm4501), [EDN](https://www.edn.com/bcm4501-dual-dvb-s2-advanced-modulation-satellite-receiver/) | +| **BCM4505** | 2007 | **Yes** | Single-channel, 65nm, LDPC/BCH + legacy | [Broadcom](https://www.broadcom.com/products/broadband/set-top-box/bcm4505) | +| **BCM4506** | 2007 | **Yes** | Dual-channel, 65nm, LDPC/BCH + legacy | [Broadcom](https://www.broadcom.com/products/broadband/set-top-box/bcm4506) | + +The BCM4501 datasheet explicitly states it includes "four 8-bit ADCs, all-digital variable rate QPSK/8PSK receivers, advanced modulation LDPC/BCH, and DVB-S-compliant forward error correction decoder." The addition of LDPC/BCH required new silicon -- it was not a firmware upgrade to the BCM4500. + +However, Broadcom restricted sales of these chips to set-top box manufacturers (EchoStar, DIRECTV) and did not sell to PC peripheral makers. This is why Genpix could not simply drop in a BCM4501. + +### What Genpix Actually Did: The SkyWalker-3 + +Genpix released the SkyWalker-3 as a DVB-S2-capable successor, using a completely different demodulator: + +| Feature | SkyWalker-1 (BCM4500) | SkyWalker-3 (likely STV0903) | +|---------|----------------------|---------------------------| +| DVB-S QPSK | Yes | Yes | +| DVB-S2 QPSK | No | Yes (rates 1/2 through 9/10) | +| DVB-S2 8PSK | No | Yes (rates 3/5 through 9/10) | +| Turbo QPSK | Yes | **No** | +| Turbo 8PSK | Yes | **No** | +| Turbo 16QAM | Yes | **No** | +| DCII | Yes | Yes | +| DSS | Yes | Yes | +| Symbol rate (DVB-S) | 256 Ksps - 30 Msps | 1 - 45 Msps | +| Symbol rate (DVB-S2) | N/A | 5 - 33 Msps | +| FEC inner (DVB-S) | Viterbi | Viterbi | +| FEC inner (DVB-S2) | N/A | LDPC | +| FEC outer (DVB-S2) | N/A | BCH | +| Demodulator | Broadcom BCM4500 | STMicroelectronics STV0903 (probable) | +| Tuner | Broadcom BCM3440 | STMicroelectronics STV6110 (probable) | + +Source: [Genpix SkyWalker-3 specifications](https://www.genpix-electronics.com/what-is-skywalker-3.html) + +The trade-off is visible: the SkyWalker-3 gained DVB-S2 but lost turbo-FEC support entirely. The turbo codes were proprietary to Broadcom/EchoStar, and the STMicroelectronics STV0903 demodulator does not implement them. This means the SkyWalker-3 cannot receive Dish Network's legacy turbo-coded 8PSK transmissions. + +--- + +## 4. Are There Any Hints of DVB-S2 Awareness in the Firmware? + +**No. There are zero references to DVB-S2, LDPC, or BCH in any firmware version or in the Windows driver source.** + +### Firmware Search Results + +Searched all three firmware versions (v2.06, Rev.2 v2.10, v2.13) via Ghidra disassembly and the following source files: + +- `SkyWalker1Control.h` -- defines modulation constants 0-9, none related to DVB-S2 +- `SkyWalker1CommonDef.h` -- device parameter structure uses `BDA_FEC_VITERBI` only +- `SkyWalker1TunerFilter.cpp` -- explicitly rejects non-QPSK modulation types and non-Viterbi FEC +- `SkyWalker1Control.cpp` -- hardcodes `ADV_MOD_DVB_QPSK` (value 0) in tune command byte 8 + +**Specific evidence of no DVB-S2 awareness:** + +1. **Modulation enum caps at 9** (`SkyWalker1Control.h`, lines 64-74): The modulation constants are `ADV_MOD_DVB_QPSK` (0) through `ADV_MOD_DVB_BPSK` (9). No value 10+ exists for DVB-S2 modes. + +2. **Firmware dispatch table has exactly 10 entries** (`tuning-protocol-analysis.md`, Section 3.1): The jump table at CODE:0873 contains 20 bytes (10 entries x 2 bytes). Modulation values >= 10 are rejected by the bounds check at CODE:0866. + +3. **FEC type is hardcoded to Viterbi** (`SkyWalker1TunerFilter.cpp`, line 1070): `else if(ulNewInnerFecType == BDA_FEC_VITERBI)` -- only Viterbi is accepted; any other FEC type returns `STATUS_INVALID_PARAMETER`. + +4. **Tune command hardcodes DVB-S QPSK** (`SkyWalker1Control.cpp`, line 292): `ucCommand[8] = ADV_MOD_DVB_QPSK;` -- the Windows driver always sends modulation type 0 (DVB-S QPSK) regardless of what the application requests. + +5. **No LDPC/BCH code rate values** exist in any FEC lookup table. The firmware's XRAM tables at 0xE0B1, 0xE0B7, 0xE0BC, 0xE0BD, and 0xE0F9 contain only Viterbi rates (1/2 through 7/8), turbo rates, and DCII combined codes. + +6. **No DVB-S2-specific register addresses** appear in the I2C traffic. The BCM4500 is programmed exclusively through indirect registers 0xA6/0xA7/0xA8 with page 0x00 -- a protocol specific to the BCM4500. DVB-S2 demodulators like the STV0903 use entirely different register maps. + +--- + +## 5. Could the GPIF Streaming Path Handle DVB-S2 Data Rates? + +**Yes -- the USB data path is not the bottleneck. The GPIF/USB 2.0 streaming architecture could handle DVB-S2 data rates if the demodulator supported them.** + +### Data Rate Analysis + +**DVB-S2 maximum useful bit rate** (from ETSI EN 302 307): +- Highest configuration: 8PSK, rate 9/10, 30 Msps = ~72 Mbps raw, ~58 Mbps net after FEC +- Typical HD transponder: 8PSK, rate 3/4, 27.5 Msps = ~44 Mbps net + +**GPIF/USB 2.0 throughput capacity:** +- USB 2.0 High Speed bulk: 480 Mbps theoretical, ~35 MB/s (~280 Mbps) practical +- GPIF engine: 48 MHz clock, 8-bit data path = 48 MB/s (384 Mbps) theoretical +- EP2 FIFO: 4x buffer with AUTOIN, 7 URBs x 8KB on host side + +**Current DVB-S usage** (from `gpif-streaming-analysis.md`, Section 15): +- "Transport stream rate: BCM4500 outputs at the satellite symbol rate (up to 30 Msps), but the effective byte rate depends on modulation and coding. USB 2.0 High Speed bulk bandwidth (480 Mbps theoretical, ~35 MB/s practical) is more than sufficient for DVB-S transport streams (typically 1-5 MB/s)." + +**Assessment**: Even at the theoretical maximum DVB-S2 data rate of ~58 Mbps (~7.25 MB/s), the USB 2.0 bulk streaming path has approximately 5x headroom. The GPIF engine configuration (IFCONFIG=0xEE, EP2FIFOCFG=0x0C, FLOWSTATEA with FSEN enabled) is identical across all firmware versions and provides a fully hardware-managed pipeline that would not require modification. + +The 8-bit parallel transport stream interface between the demodulator and FX2 is also sufficient -- DVB-S2 uses the same MPEG-TS output format (188-byte packets) as DVB-S. The GPIF waveform and AUTOIN configuration would work unchanged. + +**However**, this is a moot point. The bottleneck is the demodulator silicon, not the data path. Even if you physically replaced the BCM4500 with a DVB-S2-capable chip, you would need to rewrite the entire FX2 firmware (I2C register protocol, tuning sequence, modulation dispatch, FEC configuration) since every DVB-S2 demodulator uses a completely different register interface. + +--- + +## Summary + +| Question | Answer | +|----------|--------| +| Is DVB-S2 a hardware or firmware limitation? | **Hardware** -- the BCM4500 has no LDPC/BCH decoder logic | +| Could a firmware update add DVB-S2? | **No** -- LDPC decoding requires dedicated silicon | +| Which Broadcom chip first added LDPC? | **BCM4501** (2006), followed by BCM4505/BCM4506 (2007) | +| Any DVB-S2 hints in firmware/driver? | **None** -- zero references to LDPC, BCH, or DVB-S2 | +| Is the USB data path a bottleneck? | **No** -- USB 2.0 bulk has ~5x headroom for DVB-S2 rates | +| What did Genpix do for DVB-S2? | Released SkyWalker-3 with a different demodulator (likely STV0903) | +| What was lost in the transition? | Turbo-FEC support (proprietary to Broadcom/EchoStar) | + +--- + +## Sources + +### Datasheets and Product Pages +- [BCM4500 Datasheet (DatasheetQ)](https://html.datasheetq.com/pdf-html/885700/Broadcom/2page/BCM4500.html) +- [BCM4500 Datasheet (Elcodis)](https://elcodis.com/parts/5786421/BCM4500.html) +- [BCM4500 Datasheet (AllDatasheet)](https://www.alldatasheet.com/datasheet-pdf/pdf/85246/BOARDCOM/BCM4500.html) +- [BCM4501 Product Page (Broadcom)](https://www.broadcom.com/products/broadband/set-top-box/bcm4501) +- [BCM4501 (EDN)](https://www.edn.com/bcm4501-dual-dvb-s2-advanced-modulation-satellite-receiver/) +- [BCM4505 Product Page (Broadcom)](https://www.broadcom.com/products/broadband/set-top-box/bcm4505) +- [BCM4506 Product Page (Broadcom)](https://www.broadcom.com/products/broadband/set-top-box/bcm4506) +- [BCM4505/BCM4506 Announcement (RTTNews)](https://www.rttnews.com/380136/broadcom-launches-bcm4505-and-bcm4506-two-fully-integrated-single-chip-single-and-dual-channel-multi-format-satellite-receivers-quick-facts.aspx) + +### Genpix Products +- [Genpix SkyWalker-3 Specifications](https://www.genpix-electronics.com/what-is-skywalker-3.html) +- [Genpix Official Site](https://www.genpix-electronics.com/index.php?act=viewDoc&docId=9) + +### Community and Technical Discussions +- [LinuxTV mailing list: BCM4500 and DVB-S2 distinction](https://www.mail-archive.com/linux-dvb@linuxtv.org/msg24808.html) +- [SatelliteGuys: Turbo 8PSK card reverse engineering](https://www.satelliteguys.us/xen/threads/another-turbo-8psk-card.246879/) +- [SatelliteGuys: Genpix SkyWalker-1 discussion](https://www.satelliteguys.us/xen/threads/genpix-skywalker-1.214196/) + +### Reverse Engineering Analysis (This Project) +- `skywalker1-hardware-reference.md` -- Sections 1, 6, 7: Overview, tuning protocol, BCM4500 interface +- `tuning-protocol-analysis.md` -- Section 3: Modulation dispatch table, FEC lookup tables +- `gpif-streaming-analysis.md` -- Sections 13-15: GPIF throughput and data path analysis +- `rev2-deep-analysis.md` -- Complete Rev.2 function inventory +- `SkyWalker1Control.h` -- Modulation mode constants (lines 63-74), FEC/command definitions +- `SkyWalker1TunerFilter.cpp` -- SetInnerFecType() Viterbi-only restriction (lines 1058-1086) +- `SkyWalker1Control.cpp` -- TuneDevice() hardcoded ADV_MOD_DVB_QPSK (line 292) diff --git a/firmware-analysis-v206-vs-v213.md b/firmware-analysis-v206-vs-v213.md index 69b5ead..7e5b271 100644 --- a/firmware-analysis-v206-vs-v213.md +++ b/firmware-analysis-v206-vs-v213.md @@ -1,571 +1,571 @@ -# Genpix SkyWalker-1 FX2 Firmware Comparative Analysis: v2.06 vs v2.13 FW1 - -## Executive Summary - -v2.13 is a significant evolution of the v2.06 firmware with 21 additional functions (82 vs 61). The key changes are: - -1. **Three new vendor commands** (0x99, 0x9A, 0x9C) for LNB/tuner control -2. **Restructured INT0 handler** with active satellite front-end polling -3. **I2C-based initialization** with retry logic for the satellite demodulator -4. **Version-aware code paths** (hardware revision detection via descriptor byte) -5. **Refactored DiSEqC GPIO pin assignment** (data pin moved from P0.7 to P0.0, same bit-bang algorithm) -6. **Simplified BCM4500 status polling** (consolidated from 3 register reads to 1) - ---- - -## 1. Vendor Command Dispatch Table Comparison - -Both versions use the same two-stage USB control request dispatch: - -- **Stage 1** (`FUN_CODE_032a` / `FUN_CODE_034e`): Handles standard USB requests (bRequest 0x00-0x0B) via a 12-entry jump table at CODE:033B / CODE:035F. These handle GET_STATUS, CLEAR_FEATURE, SET_FEATURE, SET_ADDRESS, GET_DESCRIPTOR, SET_DESCRIPTOR, GET_CONFIGURATION, SET_CONFIGURATION, GET_INTERFACE, SET_INTERFACE, SYNCH_FRAME, and FW_VERSION_READ (0x0B). - -- **Stage 2** (`FUN_CODE_0056`, identical in both versions): Handles vendor requests (bmRequestType bit 6 set, bRequest 0x80-0x9D) via a 30-entry jump table at CODE:0076. Range check: `(bRequest + 0x80) < 0x1E`, dispatching to `JMP @A+DPTR` at `0x76 + (bRequest - 0x80) * 2`. - -### Vendor Command Jump Table (0x80-0x9D) - -| bRequest | Name | v2.06 Target | v2.13 Target | Status | -|----------|------|-------------|-------------|--------| -| 0x80 | GET_8PSK_CONFIG | 0x00B2 | 0x00B2 | **Both**: Read config byte to EP0BUF (v2.06: IRAM 0x6D, v2.13: IRAM 0x4F) | -| 0x81 | SET_8PSK_CONFIG | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** - not implemented in either | -| 0x82 | (reserved) | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** | -| 0x83 | I2C_WRITE | 0x00F1 | 0x00F1 | **Both**: Same I2C write handler | -| 0x84 | I2C_READ | 0x0102 | 0x0102 | **Both**: Same I2C read handler | -| 0x85 | ARM_TRANSFER | 0x0110 | 0x0110 | **Both**: Same ARM transfer handler | -| 0x86 | TUNE_8PSK | 0x012E | 0x012E | **Both**: Same tuning handler | -| 0x87 | GET_SIGNAL_STRENGTH | 0x0140 | 0x0162 | **CHANGED** - see section below | -| 0x88 | LOAD_BCM4500 | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** - BCM4500 loading via different mechanism | -| 0x89 | BOOT_8PSK | 0x00C4 | 0x00C4 | **Both**: Same boot handler | -| 0x8A | START_INTERSIL | 0x019C | 0x01BE | **Relocated** but functionally similar | -| 0x8B | SET_LNB_VOLTAGE | 0x01CB | 0x01ED | **Relocated** but functionally similar | -| 0x8C | SET_22KHZ_TONE | 0x01DD | 0x01FF | **Relocated** but functionally similar | -| 0x8D | SEND_DISEQC_COMMAND | 0x01EF | 0x0211 | **CHANGED** - different DiSEqC implementation | -| 0x8E | SET_DVB_MODE | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** | -| 0x8F | (unknown) | 0x01FC | 0x021E | Both: Similar read-back function | -| 0x90 | GET_SIGNAL_LOCK | 0x020B | 0x022D | **Relocated** but functionally similar | -| 0x91 | (unknown) | 0x022C | 0x024E | Both: I2C read-back | -| 0x92 | (unknown) | 0x024A | 0x026C | Both: I2C read-back | -| 0x93 | GET_SERIAL_NUMBER | 0x026F | 0x0293 | **Relocated** but functionally similar | -| 0x94 | (unknown) | 0x01B9 | 0x01DB | Both: LNB-related | -| 0x95 | (unknown) | 0x02DF | 0x0303 | Both: Read-back function | -| 0x96 | (unknown) | 0x02B4 | 0x02D8 | Both: Similar handler | -| 0x97 | (unknown) | 0x02C1 | 0x02E5 | Both: Similar handler | -| 0x98 | (unknown) | 0x02CB | 0x02EF | Both: Similar handler | -| 0x99 | **NEW: GET_DEMOD_STATUS** | 0x0326 (STALL) | 0x0317 | **ADDED in v2.13** | -| 0x9A | **NEW: INIT_DEMOD** | 0x0326 (STALL) | 0x0140 | **ADDED in v2.13** | -| 0x9B | (reserved) | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** | -| 0x9C | **NEW: DELAY_COMMAND** | 0x0326 (STALL) | 0x032B | **ADDED in v2.13** | -| 0x9D | SET_MODE_FLAG | 0x02FA | 0x033A | **CHANGED** - different implementation | - -### Commands Added in v2.13 - -**0x99 - GET_DEMOD_STATUS (new read command)** -``` -LCALL FUN_CODE_2421 ; calls FUN_CODE_2239(0x3F, 0xF9) - ; -> I2C read from device 0x3F, register 0xF9 -MOV EP0BUF[0], R7 ; return I2C read result -EP0BCL = 1 ; send 1 byte back -``` -Reads demodulator register 0xF9 via I2C (device address 0x3F) and returns the value to the host. This is a status/diagnostics register read for the satellite demodulator IC. - -**0x9A - INIT_DEMOD (new control command)** -``` -LCALL 0x231E ; EP0 flush/prepare -if (config_flags.0 == 1) { ; check if demodulator is present - counter = 0; - while (counter < 3) { - LCALL FUN_CODE_1977 ; initialization step - if (success) break; - counter++; - } -} -EP0BCL = 0 ; no data returned -``` -Performs up to 3 attempts to initialize the demodulator, but only if the demodulator-present flag (bit 0 of DAT_INTMEM_4F) is set. This provides host-triggered re-initialization capability. - -**0x9C - DELAY_COMMAND (new control command)** -``` -R7 = wValue ; delay parameter from USB SETUP packet -LCALL FUN_CODE_1ac6 ; tuning/acquisition delay function -EP0BCL = 0 ; no data returned -``` -Calls FUN_CODE_1ac6 with the wValue parameter from the USB SETUP packet. FUN_CODE_1ac6 performs an I2C-based tuning acquisition sequence: it reads demod register 0xF9, writes control values to registers 0xF8 and 0xF9, then polls register 0xF9 up to 40 times (0x28) waiting for bit 0 to be set (lock acquired). If lock fails, it calls FUN_CODE_1e3c which performs a full demodulator reset sequence. - -### Commands Removed from v2.13 -None were removed -- all commands that were STALL in v2.06 remain STALL in v2.13. - -### Changed Command Implementations - -**0x87 - GET_SIGNAL_STRENGTH (modified)** -- v2.06 at 0x0140: Checks DAT_INTMEM_6D bit 0 (demod active), reads three I2C status registers (0xA2, 0xA8, 0xA4) to compute signal quality, loops up to 6 iterations polling for demod readiness -- v2.13 at 0x0162: Checks DAT_INTMEM_4F bit 0 (demod active), reads I2C but uses different function call chain (FUN_CODE_1278 vs FUN_CODE_0c97). Same overall logic with relocated internal variables. - -**0x8D - SEND_DISEQC_COMMAND (GPIO pin reassignment)** -- v2.06: `LCALL 0x23e0; LCALL 0x1e41` -- GPIO bit-bang DiSEqC via FUN_CODE_2098, data on **P0.7**, carrier on P0.3 -- v2.13: `LCALL 0x231e; LCALL FUN_CODE_0dbc` -- GPIO bit-bang DiSEqC via FUN_CODE_2060, data on **P0.0**, carrier on P0.3 - -Both versions use the identical algorithm: - 1. Read wLength (0xE6BE) as message byte count - 2. Clear P0.3 (disable 22kHz carrier) - 3. Delay 15 ticks via delay function (7.5ms settling time) - 4. If message bytes present: iterate through EP0BUF, sending each byte via Manchester-encoded bit-bang (8 data bits + odd parity, 3 Timer2 ticks per bit) - 5. If wValue == 0 and no bytes: tone burst A (25 Timer2 ticks = 12.5ms) - 6. If wValue != 0 and no bytes: tone burst B via byte transmit with 0xFF pattern - -**CORRECTION**: Earlier analysis incorrectly identified v2.13 as using "I2C-based DiSEqC." Deep decompilation of the sub-functions (FUN_CODE_2060, FUN_CODE_22f3, FUN_CODE_22b0) reveals they are GPIO bit-bang implementations identical in algorithm to v2.06's FUN_CODE_2098 and FUN_CODE_2372. The only change is the data pin assignment (P0.7 -> P0.0), reflecting a different PCB layout. - -**0x9D - SET_MODE_FLAG (different logic)** -- v2.06: Reads byte at descriptor_base + 10, checks if value is 4, 5, or 6, and conditionally sets bit flag 0x06 based on wValue - 1 -- v2.13: Simply checks if wValue != 0, and if so calls FUN_CODE_21d1 which performs a conditional demodulator reset (calls FUN_CODE_1e3c if `_1_4` flag isn't already set, then writes I2C control registers 0xFC on both device 0x7F and 0x3F) - ---- - -## 2. Key Function Correspondence - -| v2.06 Function | v2.13 Function | Role | -|---------------|---------------|------| -| `main` (0x188D) | `main_entry` (0x170D) | RESET vector - clears IRAM, processes init table, jumps to init | -| `FUN_CODE_09a7` (0x09A7) | `FUN_CODE_0800` (0x0800) | Main init + main loop | -| `FUN_CODE_13c3` (0x13C3) | `FUN_CODE_11ab` (0x11AB) | USB/peripheral descriptor setup | -| `FUN_CODE_032a` (0x032A) | `FUN_CODE_034e` (0x034E) | Standard USB request handler | -| `FUN_CODE_0056` (0x0056) | `FUN_CODE_0056` (0x0056) | Vendor request dispatcher (identical code) | -| `FUN_CODE_2297` (0x2297) | `FUN_CODE_21ec` (0x21EC) | Main loop poll (USB IRQ processing) | -| `FUN_CODE_21ed` (0x21ED) | `FUN_CODE_2189` (0x2189) | EP2CS setup + PCON idle | -| `FUN_CODE_211d` (0x211D) | `FUN_CODE_20b9` (0x20B9) | CPUCS reset pulse (EP2 management) | -| `FUN_CODE_2174` (0x2174) | `FUN_CODE_2110` (0x2110) | USB descriptor type walker (identical code) | -| `FUN_CODE_1919` (0x1919) | `FUN_CODE_1800` (0x1800) | GPIF/FIFO management (identical logic) | -| `FUN_CODE_1d4f` (0x1D4F) | -- | v2.06 demod init (GPIO-based, complex) | -| -- | `FUN_CODE_1d4b` (0x1D4B) | v2.13 demod init (I2C write 4 bytes to 0x7F/0xF0) | -| `FUN_CODE_1da8` (0x1DA8) | -- | v2.06 I2C read with timeout (uses FUN_CODE_1556) | -| -- | `FUN_CODE_0eea` (0x0EEA) | v2.13 I2C read with retry (20 attempts) | -| `FUN_CODE_1dfb` (0x1DFB) | `FUN_CODE_14b9` (0x14B9) | Delay loop (clock-dependent timing) | -| `FUN_CODE_1cf3` (0x1CF3) | `FUN_CODE_1c44` (0x1C44) | Configuration update function | -| `FUN_CODE_12ea` (0x12EA) | `FUN_CODE_1000` (0x1000) | USB endpoint configuration | -| `FUN_CODE_0ddd` (0x0DDD) | `FUN_CODE_0ca4` (0x0CA4) | BCM4500 firmware loader | -| `FUN_CODE_2000` (0x2000) | `FUN_CODE_208d` (0x208D) | BCM4500 status polling | -| `FUN_CODE_1556` (0x1556) | `FUN_CODE_0eea` (0x0EEA) | I2C multi-byte transfer | -| `FUN_CODE_24d2` (0x24D2) | `FUN_CODE_243d` (0x243D) | SET_DVB_MODE config store | -| `FUN_CODE_2419` (0x2419) | `FUN_CODE_2357` (0x2357) | GET config byte to EP0BUF | -| `FUN_CODE_23cb` (0x23CB) | `FUN_CODE_2309` (0x2309) | Read descriptor byte to EP0BUF | -| `FUN_CODE_1a0e` (0x1A0E) | -- | v2.06 serial number reader (EEPROM) | -| `INT0_vec` (0x0003) | `INT0_vector` (0x0003) | INT0 interrupt handler (**significantly different**) | -| -- | `FUN_CODE_2239` (0x2239) | v2.13 I2C single-byte read helper | -| -- | `FUN_CODE_2031` (0x2031) | v2.13 USB reconnect function | -| -- | `FUN_CODE_1799` (0x1799) | v2.13 demod checksum/signature verify | -| -- | `FUN_CODE_1ca0` (0x1CA0) | v2.13 descriptor checksum verify | -| -- | `FUN_CODE_1ac6` (0x1AC6) | v2.13 tuning acquisition sequence | -| -- | `FUN_CODE_1e3c` (0x1E3C) | v2.13 demodulator full reset | -| -- | `FUN_CODE_10d9` (0x10D9) | v2.13 demod status polling/init | -| -- | `FUN_CODE_0dbc` (0x0DBC) | v2.13 DiSEqC GPIO bit-bang wrapper (data on P0.0) | - -### USB Descriptor Setup (FUN_CODE_13c3 vs FUN_CODE_11ab) - -Both functions are structurally identical: -1. Disable USB disconnect (0xE605 bit 1 clear) -2. Configure IFCONFIG (0xE600) for internal clock, 48MHz -3. Set REVCTL (0xE601) to 0xCA -4. Configure GPIFIDLECTL, PORTACFG -5. Set PORT registers (P0, P3, IPL1) -6. Configure GPIFCTLCFG, FIFORESET, FIFOPINPOLAR -7. Configure Timer2 (RCAP2H=0xF8, RCAP2L=0x2F -> ~2ms period at 48MHz/12) -8. Initialize subsystem modules - -Key difference: v2.13 calls `INT0_vector()` (the INT0 handler) during initialization as a probing step. This runs the demodulator availability check during USB setup, before enabling interrupts. v2.06 does not do this. - -Descriptor pointer offsets: -- v2.06: BANK1_R4:R5 = 0x1200 (descriptor base) -- v2.13: BANK1_R4:R5 = 0x0E00 (descriptor base, lower due to code restructuring) - ---- - -## 3. Structural Differences - -### 3.1 v2.13 Retry Loops (FUN_CODE_1799 and FUN_CODE_1ca0) - -In the main init function `FUN_CODE_0800`, v2.13 has: - -```c -// Retry loop 1: FUN_CODE_1799 - demodulator signature verification -DAT_INTMEM_36 = 0x14; // 20 attempts -while (DAT_INTMEM_36 != 0 && FUN_CODE_1799() fails) { - DAT_INTMEM_36--; -} -if (DAT_INTMEM_36 == 0) { - FUN_CODE_1ac6(100); // tuning acquisition with 100ms delay -} - -// Retry loop 2: FUN_CODE_1ca0 - descriptor checksum verification -DAT_INTMEM_36 = 0x14; // 20 attempts -while (DAT_INTMEM_36 != 0 && FUN_CODE_1ca0() fails) { - DAT_INTMEM_36--; -} -if (DAT_INTMEM_36 == 0) { - FUN_CODE_1ac6(100); // tuning acquisition with 100ms delay -} -``` - -**FUN_CODE_1799 - Demodulator Signature Verification:** -1. Calls FUN_CODE_1d4b() which writes 4 bytes via I2C to device 0x7F, register 0xF0 (demodulator control) -2. Saves parameters DAT_INTMEM_39:3A -3. Checks if parameters match 0x021C (known good value) - returns early if match -4. Reads 5 I2C bytes via FUN_CODE_0718, each at register 0x0A offset (stepping by 2) -5. Subtracts 0x30 ('0') from each byte (ASCII to binary conversion) -6. Sums the values and compares sum to the saved parameters -7. Returns success (carry set) if checksum matches - -This verifies the demodulator responds correctly with the expected identification pattern. The ASCII-to-binary conversion suggests the demod returns a readable version string at register 0x0A. - -**FUN_CODE_1ca0 - Descriptor Checksum Verification:** -1. Iterates bytes 6 through 0x29 (36 bytes) of the BANK2_R6:R7 descriptor block -2. Computes running sum, compares against expected value 0x0706 -3. If first block passes, iterates bytes 0x2C through 0x4F (36 bytes) of same block -4. Computes second running sum, compares against expected value 0x0686 -5. Returns success only if both checksums match - -This validates the integrity of a 2-block descriptor/configuration structure (possibly EEPROM-loaded calibration data). - -**v2.06 equivalent:** v2.06 does NOT have these retry loops. It calls `FUN_CODE_1a0e` (serial number/EEPROM reader) directly without verification, then proceeds immediately. There is no signature check or checksum validation. - -### 3.2 Version Byte Check (Hardware Revision Detection) - -After the retry loops, v2.13 performs: - -```c -// Read byte at descriptor_base + 10 -byte version_byte = *(BANK1_R4:R5 + 10); // 0x0E0A -if (version_byte == 0x03) { - bVar4 = 0x80; // flag = set -} else { - bVar4 = 0; // flag = clear -} -_1_3 = bVar4 >> 7; // store as bit flag _1_3 -``` - -This reads byte offset 10 from the USB descriptor base address. Offset 10 in a USB device descriptor is `bMaxPacketSize0` in standard USB, but since this is a custom descriptor area, it likely encodes a hardware revision. The value 0x03 sets bit flag `_1_3`, creating a hardware-revision-aware code path. - -**Impact:** The `_1_3` flag is used elsewhere in v2.13 to conditionally execute different initialization sequences, supporting multiple hardware revisions of the SkyWalker-1 board. - -### 3.3 FUN_CODE_2031 - USB Reconnect Before Main Loop - -```c -void FUN_CODE_2031(void) { - if (_0_0 == 0) { - CPUCS |= 0x08; // Set CPUCS.3 (8051 reset bit? Or re-enumerate) - } else { - CPUCS |= 0x0A; // Set CPUCS.3 + CPUCS.1 - } - FUN_CODE_14b9(5, 0xDC); // Delay ~1500 cycles - EPIRQ = 0xFF; // Clear all endpoint interrupts - USBIRQ = 0xFF; // Clear all USB interrupts - DAT_SFR_91 &= 0xEF; // Clear EXIF.4 (USB interrupt flag) - CPUCS &= 0xF7; // Clear CPUCS.3 -} -``` - -This performs a controlled USB re-enumeration by pulsing CPUCS.3, then clearing all pending USB/endpoint interrupts. The conditional on `_0_0` adds CPUCS.1 when the flag is set (possibly switching between 12MHz and 48MHz operation). - -**v2.06 equivalent:** In v2.06, this exact same logic exists as `INT0_vec` (the INT0 interrupt handler at 0x0003). The critical difference is that in v2.06 this code runs as an interrupt handler, while in v2.13 it's called as a normal function (`FUN_CODE_2031`) before the main loop starts, AND the INT0 vector is repurposed for demodulator polling (see section 4). - ---- - -## 4. INT0 Handler Difference - -### v2.06 INT0 (CODE:0003) - USB Re-enumeration - -```c -void INT0_vec(void) { - if (_0_7 == 0) { - CPUCS |= 0x08; // CPUCS bit 3 - } else { - CPUCS |= 0x0A; // CPUCS bits 3+1 - } - FUN_CODE_1dfb(5, 0xDC); // Delay - EPIRQ = 0xFF; // Clear endpoint IRQs - USBIRQ = 0xFF; // Clear USB IRQs - DAT_SFR_91 &= 0xEF; // Clear external interrupt flag - CPUCS &= 0xF7; // Clear CPUCS bit 3 -} -``` - -Simple USB reconnect/re-enumeration handler. Pulses CPUCS.3, clears all pending interrupts. - -### v2.13 INT0 (CODE:0003) - Demodulator Availability Polling - -```c -void INT0_vector(void) { - for (DAT_INTMEM_37 = 0x28; DAT_INTMEM_37 != 0; DAT_INTMEM_37--) { - // Read I2C device 0x7F (demod A), checking for response - byte result = FUN_CODE_2239(0x7F); // I2C read from 0x7F - if (result != 0x01) { - // Try I2C device 0x3F (demod B) - result = FUN_CODE_2239(0x3F); // I2C read from 0x3F - if (result != 0x01) break; // Neither responded normally - } - } - _1_4 = (DAT_INTMEM_37 == 0); // Set flag if loop exhausted (no demod found) -} -``` - -This is a complete replacement of INT0's purpose. Instead of USB re-enumeration, INT0 now polls two I2C devices (0x7F and 0x3F) up to 40 times (0x28). These are two possible addresses for the satellite demodulator IC. - -**FUN_CODE_2239 decompiled:** -```c -undefined1 FUN_CODE_2239(byte device_addr) { - DAT_INTMEM_48 = 0xE1; // buffer address high - DAT_INTMEM_49 = 0; // buffer address low - FUN_CODE_0eea(1, device_addr, 0x51); // I2C read 1 byte from device - return DAT_EXTMEM_e100; // return the read byte -} -``` - -The function performs an I2C single-byte read from the specified device address, using address 0x51 as a parameter (likely selecting a specific I2C bus or mode via the FX2's auxiliary I2C controller at 0xE678). It stores the result at 0xE100 (XRAM buffer). - -**Behavioral meaning:** The flag `_1_4` is set to 1 if neither demodulator responded after 40 attempts - indicating no demodulator hardware is present. This flag is later checked by: -- `FUN_CODE_21d1` (command 0x9D handler) - skips demodulator reset if `_1_4 != 1` -- Various initialization paths to avoid hanging on missing hardware - -**Why this matters:** v2.06 assumes the demodulator is always present. v2.13 can detect and gracefully handle boards where the demodulator is absent or unresponsive, making it more robust for manufacturing QC and field failures. - ---- - -## 5. What Can v2.13 Do That v2.06 Cannot? - -### 5.1 Demodulator Hardware Detection -v2.13 probes two I2C addresses (0x7F, 0x3F) at startup to determine which demodulator variant is installed, or if none is present. v2.06 blindly assumes the hardware configuration. - -### 5.2 Host-Initiated Demodulator Re-initialization (Command 0x9A) -The host can trigger a demodulator re-initialization via USB vendor command 0x9A, with up to 3 retry attempts. v2.06 has no mechanism for the host to request re-initialization. - -### 5.3 Demodulator Status Read (Command 0x99) -Direct I2C register read of demodulator register 0xF9, returned to host. This enables diagnostic/monitoring software to check demodulator status without going through the full signal quality pipeline. - -### 5.4 Host-Controlled Tuning Delay (Command 0x9C) -Allows the host to invoke the tuning acquisition sequence with a configurable delay parameter. In v2.06, the tuning timing is entirely firmware-controlled with no host influence. - -### 5.5 DiSEqC GPIO Pin Reassignment -All firmware versions use the same GPIO bit-bang algorithm for DiSEqC signaling. The only change is the data pin assignment per PCB revision: - -| Version | Data Pin | Carrier Pin | Byte Transmit | Bit Symbol | Timer Tick | -|---------|----------|-------------|--------------|------------|------------| -| v2.06 | **P0.7** | P0.3 | 0x2098 | 0x23B5 | 0x24C6 | -| Rev.2 v2.10 | **P0.4** | P0.3 | FUN_CODE_07d1 | FUN_CODE_213c | FUN_CODE_225f | -| v2.13 FW1 | **P0.0** | P0.3 | FUN_CODE_2060 | FUN_CODE_22f3 | func_0x2431 | - -The algorithm is identical across all versions: Manchester-encoded bit-bang with Timer2-based timing, odd parity per byte, and 25-tick tone bursts for mini-commands. - -### 5.6 Firmware/Descriptor Integrity Verification -v2.13 validates demodulator identification (ASCII version string checksum) and descriptor block integrity (two 36-byte checksums) before proceeding. If verification fails after 20 attempts, it falls back to a recovery sequence. v2.06 does no integrity checking. - -### 5.7 Hardware Revision Awareness -The version byte check (descriptor offset 10, value 0x03) creates conditional code paths allowing a single firmware image to support multiple SkyWalker-1 hardware revisions. v2.06 has a single code path for one hardware revision. - -### 5.8 Simplified BCM4500 Status Polling -v2.06's `FUN_CODE_2000` polls three separate BCM4500 registers (0xA2, 0xA8, 0xA4 via I2C) to determine demodulator readiness. v2.13's `FUN_CODE_208d` polls only one register (0xA4), suggesting either the demodulator firmware was updated to consolidate status, or the additional checks were found to be redundant. - -### 5.9 Conditional Demodulator Reset (Command 0x9D) -v2.13's 0x9D handler can trigger a full demodulator reset sequence (FUN_CODE_1e3c -> register writes to 0x18 bus) controlled by the host via wValue. This is useful for error recovery without full device re-enumeration. - ---- - -## 6. Architecture Summary - -| Aspect | v2.06 | v2.13 | -|--------|-------|-------| -| Total functions | 61 | 82 (+21) | -| RESET vector | 0x188D | 0x170D | -| Stack pointer | 0x72 | 0x50 | -| Init data table | CODE:0B46 | CODE:0B88 | -| Descriptor base | 0x1200 | 0x0E00 | -| Config byte (IRAM) | 0x6D | 0x4F | -| INT0 purpose | USB re-enumerate | Demod probe | -| DiSEqC data pin | P0.7 (GPIO bit-bang) | P0.0 (GPIO bit-bang) | -| Demod init | Direct, no retry | 20-attempt retry | -| Integrity checks | None | Checksum verification | -| HW revision support | Single | Multi-revision (flag _1_3) | -| New vendor cmds | -- | 0x99, 0x9A, 0x9C | - ---- - -## 7. DiSEqC Timing Chain Analysis - -### 7.1 Timer2 Configuration (Identical Across All Versions) - -All firmware versions configure Timer2 identically during USB descriptor setup: - -``` -T2CON = 0x04 ; Auto-reload mode, internal clock, TR2=1 (running) -RCAP2H = 0xF8 ; Reload high byte -RCAP2L = 0x2F ; Reload low byte -> RCAP2 = 0xF82F = 63535 -CKCON = 0x00 ; Default (T2M=0 -> Timer2 clock = CLKOUT/12) -``` - -**Timer2 Clock Derivation:** -``` -FX2 master clock = 48 MHz -CKCON.T2M = 0 -> Timer2 clock = 48 MHz / 12 = 4 MHz -Count per overflow = 65536 - 63535 = 2001 -Tick period = 2001 / 4,000,000 = 500.25 us ~ 500 us -Tick frequency ~ 2.0 kHz -``` - -Timer2 runs continuously from power-on and is never stopped or reconfigured. It serves as a stable 500 us timebase for all DiSEqC operations. - -### 7.2 DiSEqC Signal Architecture - -``` -FX2 Firmware External Hardware Coax Cable -+------------------+ +--------------------+ +------------------+ -| | | | | | -| P0.3 (carrier) |---->| 22 kHz oscillator |---->| LNB power line | -| (enable/disable) | | (gated by P0.3) | | (13V/18V + tone) | -| | | | | | -| P0.x (data bit) | | (internal to FX2 | | | -| (firmware only) | | firmware logic) | | | -+------------------+ +--------------------+ +------------------+ -``` - -The firmware does NOT generate the 22 kHz carrier directly. P0.3 gates an external -22 kHz oscillator circuit on the PCB. The data pin (P0.7/P0.4/P0.0 depending on -version) is used only internally by the firmware to control the Manchester encoding -logic -- it tells the bit-symbol function whether to cut the carrier short or leave -it on for the full period. - -### 7.3 Manchester Encoding (DiSEqC Bit Symbol) - -Each DiSEqC bit consists of 3 Timer2 ticks (3 x 500 us = 1.5 ms): - -**Data '0' (2/3 tone, 1/3 silence):** -``` - Tick 1 Tick 2 Tick 3 - (500 us) (500 us) (500 us) -P0.3: _____|========|========|________| - ^tone ON ^tone OFF - (setup gap) (1.0 ms carrier) (0.5 ms silence) -``` - -**Data '1' (1/3 tone, 2/3 silence):** -``` - Tick 1 Tick 2 Tick 3 - (500 us) (500 us) (500 us) -P0.3: _____|========|________|________| - ^tone ON ^tone OFF early - (setup gap) (0.5 ms carrier) (1.0 ms silence) -``` - -**Implementation (decompiled from Rev.2 FUN_CODE_213c):** -```c -void diseqc_bit_symbol(void) { - wait_TF2(); // Tick 1: inter-bit gap (500 us) - P0 |= 0x08; // P0.3 = 1 -> 22 kHz carrier ON - wait_TF2(); // Tick 2: carrier period (500 us) - if (data_pin != 0) { // If data = '1': - P0 &= 0xF7; // P0.3 = 0 -> carrier OFF (short pulse) - } - wait_TF2(); // Tick 3: final period (500 us) - P0 &= 0xF7; // P0.3 = 0 -> carrier always OFF at end -} -``` - -### 7.4 Byte Transmission (8 Data Bits + Odd Parity) - -Each DiSEqC byte is 9 bits: 8 data (MSB first) + 1 parity (odd). - -**Implementation (decompiled from Rev.2 FUN_CODE_07d1):** -```c -void diseqc_send_byte(char first_byte, byte data) { - byte ones_count = 0; - if (first_byte == 0) TF2 = 0; // Sync timer on first byte - - for (char i = 8; i > 0; i--) { // 8 bits, MSB first - if (data & 0x80) { // Test MSB - data_pin = 1; // Set data = '1' - diseqc_bit_symbol(); // Transmit '1' symbol - ones_count++; - } else { - data_pin = 0; // Set data = '0' - diseqc_bit_symbol(); // Transmit '0' symbol - } - data <<= 1; // Next bit - } - - data_pin = ~ones_count & 1; // Odd parity: '1' if even count - diseqc_bit_symbol(); // Transmit parity bit -} -``` - -**Timing per byte:** 9 bits x 1.5 ms = 13.5 ms - -### 7.5 Tone Burst (Mini DiSEqC Command) - -For legacy 2-way satellite switches, a simple tone burst is used instead of a -full DiSEqC message. The burst is 25 Timer2 ticks of continuous carrier: - -```c -void tone_burst_A(void) { - TF2 = 0; // Sync timer - wait_TF2(); // One tick gap - P0 |= 0x08; // P0.3 = 1 -> carrier ON - for (char i = 25; i > 0; i--) { - wait_TF2(); // 25 x 500 us = 12.5 ms - } - P0 &= 0xF7; // P0.3 = 0 -> carrier OFF -} -``` - -**Burst duration:** 25 x 500 us = 12.5 ms (matches DiSEqC spec) - -### 7.6 Timer Tick Wait (TF2 Polling) - -The lowest-level timing primitive is a busy-wait on Timer2 overflow: - -```c -void wait_TF2(void) { - while (TF2 == 0) {} // Poll Timer2 overflow flag - TF2 = 0; // Clear flag for next tick -} -``` - -This is identical across all versions (v2.06: 0x24C6, Rev.2: FUN_CODE_225f, -v2.13: func_0x2431). Timer2 overflows every 500.25 us, providing the fundamental -DiSEqC timebase. - -### 7.7 CPU Clock Compensation (Delay Function) - -The delay function used before DiSEqC transmission adjusts for CPU clock speed: - -```c -void delay(byte high, byte low) { - byte clkspd = CPUCS & 0x18; // CPUCS[4:3] = clock speed bits - if (clkspd == 0x00) { // 12 MHz: halve the count - // Adjust high:low /= 2 - } else if (clkspd == 0x10) { // 48 MHz: double the count - // Adjust high:low *= 2 - } - // 24 MHz (0x08): use count as-is - while (high:low > 0) { - wait_TF2(); - high:low--; - } -} -``` - -The pre-DiSEqC delay call is `delay(0, 0x0F)` = 15 ticks x 500 us = 7.5 ms. -This allows the LNB voltage to stabilize before DiSEqC signaling begins. - -### 7.8 Complete DiSEqC Timing Summary - -| Parameter | Value | Source | -|-----------|-------|--------| -| Timer2 clock | 4 MHz (48 MHz / 12) | CKCON default, T2M=0 | -| Timer2 reload | 0xF82F | RCAP2H:RCAP2L | -| Tick period | 500.25 us | (65536 - 63535) / 4 MHz | -| Bit period | 1.5 ms (3 ticks) | DiSEqC Manchester encoding | -| Byte period | 13.5 ms (9 bits) | 8 data + 1 parity | -| Tone burst | 12.5 ms (25 ticks) | Mini-command A/B | -| Pre-TX delay | 7.5 ms (15 ticks) | Voltage settling | -| Data '0' | 1.0 ms tone + 0.5 ms silence | 2/3 duty cycle | -| Data '1' | 0.5 ms tone + 1.0 ms silence | 1/3 duty cycle | -| Carrier frequency | 22 kHz (external oscillator) | Gated by P0.3 | -| Carrier enable | P0.3 | All versions | -| Data pin (v2.06) | P0.7 | PCB revision A | -| Data pin (Rev.2) | P0.4 | PCB revision B | -| Data pin (v2.13) | P0.0 | PCB revision C | +# Genpix SkyWalker-1 FX2 Firmware Comparative Analysis: v2.06 vs v2.13 FW1 + +## Executive Summary + +v2.13 is a significant evolution of the v2.06 firmware with 21 additional functions (82 vs 61). The key changes are: + +1. **Three new vendor commands** (0x99, 0x9A, 0x9C) for LNB/tuner control +2. **Restructured INT0 handler** with active satellite front-end polling +3. **I2C-based initialization** with retry logic for the satellite demodulator +4. **Version-aware code paths** (hardware revision detection via descriptor byte) +5. **Refactored DiSEqC GPIO pin assignment** (data pin moved from P0.7 to P0.0, same bit-bang algorithm) +6. **Simplified BCM4500 status polling** (consolidated from 3 register reads to 1) + +--- + +## 1. Vendor Command Dispatch Table Comparison + +Both versions use the same two-stage USB control request dispatch: + +- **Stage 1** (`FUN_CODE_032a` / `FUN_CODE_034e`): Handles standard USB requests (bRequest 0x00-0x0B) via a 12-entry jump table at CODE:033B / CODE:035F. These handle GET_STATUS, CLEAR_FEATURE, SET_FEATURE, SET_ADDRESS, GET_DESCRIPTOR, SET_DESCRIPTOR, GET_CONFIGURATION, SET_CONFIGURATION, GET_INTERFACE, SET_INTERFACE, SYNCH_FRAME, and FW_VERSION_READ (0x0B). + +- **Stage 2** (`FUN_CODE_0056`, identical in both versions): Handles vendor requests (bmRequestType bit 6 set, bRequest 0x80-0x9D) via a 30-entry jump table at CODE:0076. Range check: `(bRequest + 0x80) < 0x1E`, dispatching to `JMP @A+DPTR` at `0x76 + (bRequest - 0x80) * 2`. + +### Vendor Command Jump Table (0x80-0x9D) + +| bRequest | Name | v2.06 Target | v2.13 Target | Status | +|----------|------|-------------|-------------|--------| +| 0x80 | GET_8PSK_CONFIG | 0x00B2 | 0x00B2 | **Both**: Read config byte to EP0BUF (v2.06: IRAM 0x6D, v2.13: IRAM 0x4F) | +| 0x81 | SET_8PSK_CONFIG | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** - not implemented in either | +| 0x82 | (reserved) | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** | +| 0x83 | I2C_WRITE | 0x00F1 | 0x00F1 | **Both**: Same I2C write handler | +| 0x84 | I2C_READ | 0x0102 | 0x0102 | **Both**: Same I2C read handler | +| 0x85 | ARM_TRANSFER | 0x0110 | 0x0110 | **Both**: Same ARM transfer handler | +| 0x86 | TUNE_8PSK | 0x012E | 0x012E | **Both**: Same tuning handler | +| 0x87 | GET_SIGNAL_STRENGTH | 0x0140 | 0x0162 | **CHANGED** - see section below | +| 0x88 | LOAD_BCM4500 | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** - BCM4500 loading via different mechanism | +| 0x89 | BOOT_8PSK | 0x00C4 | 0x00C4 | **Both**: Same boot handler | +| 0x8A | START_INTERSIL | 0x019C | 0x01BE | **Relocated** but functionally similar | +| 0x8B | SET_LNB_VOLTAGE | 0x01CB | 0x01ED | **Relocated** but functionally similar | +| 0x8C | SET_22KHZ_TONE | 0x01DD | 0x01FF | **Relocated** but functionally similar | +| 0x8D | SEND_DISEQC_COMMAND | 0x01EF | 0x0211 | **CHANGED** - different DiSEqC implementation | +| 0x8E | SET_DVB_MODE | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** | +| 0x8F | (unknown) | 0x01FC | 0x021E | Both: Similar read-back function | +| 0x90 | GET_SIGNAL_LOCK | 0x020B | 0x022D | **Relocated** but functionally similar | +| 0x91 | (unknown) | 0x022C | 0x024E | Both: I2C read-back | +| 0x92 | (unknown) | 0x024A | 0x026C | Both: I2C read-back | +| 0x93 | GET_SERIAL_NUMBER | 0x026F | 0x0293 | **Relocated** but functionally similar | +| 0x94 | (unknown) | 0x01B9 | 0x01DB | Both: LNB-related | +| 0x95 | (unknown) | 0x02DF | 0x0303 | Both: Read-back function | +| 0x96 | (unknown) | 0x02B4 | 0x02D8 | Both: Similar handler | +| 0x97 | (unknown) | 0x02C1 | 0x02E5 | Both: Similar handler | +| 0x98 | (unknown) | 0x02CB | 0x02EF | Both: Similar handler | +| 0x99 | **NEW: GET_DEMOD_STATUS** | 0x0326 (STALL) | 0x0317 | **ADDED in v2.13** | +| 0x9A | **NEW: INIT_DEMOD** | 0x0326 (STALL) | 0x0140 | **ADDED in v2.13** | +| 0x9B | (reserved) | 0x0326 (STALL) | 0x034A (STALL) | **Both STALL** | +| 0x9C | **NEW: DELAY_COMMAND** | 0x0326 (STALL) | 0x032B | **ADDED in v2.13** | +| 0x9D | SET_MODE_FLAG | 0x02FA | 0x033A | **CHANGED** - different implementation | + +### Commands Added in v2.13 + +**0x99 - GET_DEMOD_STATUS (new read command)** +``` +LCALL FUN_CODE_2421 ; calls FUN_CODE_2239(0x3F, 0xF9) + ; -> I2C read from device 0x3F, register 0xF9 +MOV EP0BUF[0], R7 ; return I2C read result +EP0BCL = 1 ; send 1 byte back +``` +Reads demodulator register 0xF9 via I2C (device address 0x3F) and returns the value to the host. This is a status/diagnostics register read for the satellite demodulator IC. + +**0x9A - INIT_DEMOD (new control command)** +``` +LCALL 0x231E ; EP0 flush/prepare +if (config_flags.0 == 1) { ; check if demodulator is present + counter = 0; + while (counter < 3) { + LCALL FUN_CODE_1977 ; initialization step + if (success) break; + counter++; + } +} +EP0BCL = 0 ; no data returned +``` +Performs up to 3 attempts to initialize the demodulator, but only if the demodulator-present flag (bit 0 of DAT_INTMEM_4F) is set. This provides host-triggered re-initialization capability. + +**0x9C - DELAY_COMMAND (new control command)** +``` +R7 = wValue ; delay parameter from USB SETUP packet +LCALL FUN_CODE_1ac6 ; tuning/acquisition delay function +EP0BCL = 0 ; no data returned +``` +Calls FUN_CODE_1ac6 with the wValue parameter from the USB SETUP packet. FUN_CODE_1ac6 performs an I2C-based tuning acquisition sequence: it reads demod register 0xF9, writes control values to registers 0xF8 and 0xF9, then polls register 0xF9 up to 40 times (0x28) waiting for bit 0 to be set (lock acquired). If lock fails, it calls FUN_CODE_1e3c which performs a full demodulator reset sequence. + +### Commands Removed from v2.13 +None were removed -- all commands that were STALL in v2.06 remain STALL in v2.13. + +### Changed Command Implementations + +**0x87 - GET_SIGNAL_STRENGTH (modified)** +- v2.06 at 0x0140: Checks DAT_INTMEM_6D bit 0 (demod active), reads three I2C status registers (0xA2, 0xA8, 0xA4) to compute signal quality, loops up to 6 iterations polling for demod readiness +- v2.13 at 0x0162: Checks DAT_INTMEM_4F bit 0 (demod active), reads I2C but uses different function call chain (FUN_CODE_1278 vs FUN_CODE_0c97). Same overall logic with relocated internal variables. + +**0x8D - SEND_DISEQC_COMMAND (GPIO pin reassignment)** +- v2.06: `LCALL 0x23e0; LCALL 0x1e41` -- GPIO bit-bang DiSEqC via FUN_CODE_2098, data on **P0.7**, carrier on P0.3 +- v2.13: `LCALL 0x231e; LCALL FUN_CODE_0dbc` -- GPIO bit-bang DiSEqC via FUN_CODE_2060, data on **P0.0**, carrier on P0.3 + +Both versions use the identical algorithm: + 1. Read wLength (0xE6BE) as message byte count + 2. Clear P0.3 (disable 22kHz carrier) + 3. Delay 15 ticks via delay function (7.5ms settling time) + 4. If message bytes present: iterate through EP0BUF, sending each byte via Manchester-encoded bit-bang (8 data bits + odd parity, 3 Timer2 ticks per bit) + 5. If wValue == 0 and no bytes: tone burst A (25 Timer2 ticks = 12.5ms) + 6. If wValue != 0 and no bytes: tone burst B via byte transmit with 0xFF pattern + +**CORRECTION**: Earlier analysis incorrectly identified v2.13 as using "I2C-based DiSEqC." Deep decompilation of the sub-functions (FUN_CODE_2060, FUN_CODE_22f3, FUN_CODE_22b0) reveals they are GPIO bit-bang implementations identical in algorithm to v2.06's FUN_CODE_2098 and FUN_CODE_2372. The only change is the data pin assignment (P0.7 -> P0.0), reflecting a different PCB layout. + +**0x9D - SET_MODE_FLAG (different logic)** +- v2.06: Reads byte at descriptor_base + 10, checks if value is 4, 5, or 6, and conditionally sets bit flag 0x06 based on wValue - 1 +- v2.13: Simply checks if wValue != 0, and if so calls FUN_CODE_21d1 which performs a conditional demodulator reset (calls FUN_CODE_1e3c if `_1_4` flag isn't already set, then writes I2C control registers 0xFC on both device 0x7F and 0x3F) + +--- + +## 2. Key Function Correspondence + +| v2.06 Function | v2.13 Function | Role | +|---------------|---------------|------| +| `main` (0x188D) | `main_entry` (0x170D) | RESET vector - clears IRAM, processes init table, jumps to init | +| `FUN_CODE_09a7` (0x09A7) | `FUN_CODE_0800` (0x0800) | Main init + main loop | +| `FUN_CODE_13c3` (0x13C3) | `FUN_CODE_11ab` (0x11AB) | USB/peripheral descriptor setup | +| `FUN_CODE_032a` (0x032A) | `FUN_CODE_034e` (0x034E) | Standard USB request handler | +| `FUN_CODE_0056` (0x0056) | `FUN_CODE_0056` (0x0056) | Vendor request dispatcher (identical code) | +| `FUN_CODE_2297` (0x2297) | `FUN_CODE_21ec` (0x21EC) | Main loop poll (USB IRQ processing) | +| `FUN_CODE_21ed` (0x21ED) | `FUN_CODE_2189` (0x2189) | EP2CS setup + PCON idle | +| `FUN_CODE_211d` (0x211D) | `FUN_CODE_20b9` (0x20B9) | CPUCS reset pulse (EP2 management) | +| `FUN_CODE_2174` (0x2174) | `FUN_CODE_2110` (0x2110) | USB descriptor type walker (identical code) | +| `FUN_CODE_1919` (0x1919) | `FUN_CODE_1800` (0x1800) | GPIF/FIFO management (identical logic) | +| `FUN_CODE_1d4f` (0x1D4F) | -- | v2.06 demod init (GPIO-based, complex) | +| -- | `FUN_CODE_1d4b` (0x1D4B) | v2.13 demod init (I2C write 4 bytes to 0x7F/0xF0) | +| `FUN_CODE_1da8` (0x1DA8) | -- | v2.06 I2C read with timeout (uses FUN_CODE_1556) | +| -- | `FUN_CODE_0eea` (0x0EEA) | v2.13 I2C read with retry (20 attempts) | +| `FUN_CODE_1dfb` (0x1DFB) | `FUN_CODE_14b9` (0x14B9) | Delay loop (clock-dependent timing) | +| `FUN_CODE_1cf3` (0x1CF3) | `FUN_CODE_1c44` (0x1C44) | Configuration update function | +| `FUN_CODE_12ea` (0x12EA) | `FUN_CODE_1000` (0x1000) | USB endpoint configuration | +| `FUN_CODE_0ddd` (0x0DDD) | `FUN_CODE_0ca4` (0x0CA4) | BCM4500 firmware loader | +| `FUN_CODE_2000` (0x2000) | `FUN_CODE_208d` (0x208D) | BCM4500 status polling | +| `FUN_CODE_1556` (0x1556) | `FUN_CODE_0eea` (0x0EEA) | I2C multi-byte transfer | +| `FUN_CODE_24d2` (0x24D2) | `FUN_CODE_243d` (0x243D) | SET_DVB_MODE config store | +| `FUN_CODE_2419` (0x2419) | `FUN_CODE_2357` (0x2357) | GET config byte to EP0BUF | +| `FUN_CODE_23cb` (0x23CB) | `FUN_CODE_2309` (0x2309) | Read descriptor byte to EP0BUF | +| `FUN_CODE_1a0e` (0x1A0E) | -- | v2.06 serial number reader (EEPROM) | +| `INT0_vec` (0x0003) | `INT0_vector` (0x0003) | INT0 interrupt handler (**significantly different**) | +| -- | `FUN_CODE_2239` (0x2239) | v2.13 I2C single-byte read helper | +| -- | `FUN_CODE_2031` (0x2031) | v2.13 USB reconnect function | +| -- | `FUN_CODE_1799` (0x1799) | v2.13 demod checksum/signature verify | +| -- | `FUN_CODE_1ca0` (0x1CA0) | v2.13 descriptor checksum verify | +| -- | `FUN_CODE_1ac6` (0x1AC6) | v2.13 tuning acquisition sequence | +| -- | `FUN_CODE_1e3c` (0x1E3C) | v2.13 demodulator full reset | +| -- | `FUN_CODE_10d9` (0x10D9) | v2.13 demod status polling/init | +| -- | `FUN_CODE_0dbc` (0x0DBC) | v2.13 DiSEqC GPIO bit-bang wrapper (data on P0.0) | + +### USB Descriptor Setup (FUN_CODE_13c3 vs FUN_CODE_11ab) + +Both functions are structurally identical: +1. Disable USB disconnect (0xE605 bit 1 clear) +2. Configure IFCONFIG (0xE600) for internal clock, 48MHz +3. Set REVCTL (0xE601) to 0xCA +4. Configure GPIFIDLECTL, PORTACFG +5. Set PORT registers (P0, P3, IPL1) +6. Configure GPIFCTLCFG, FIFORESET, FIFOPINPOLAR +7. Configure Timer2 (RCAP2H=0xF8, RCAP2L=0x2F -> ~2ms period at 48MHz/12) +8. Initialize subsystem modules + +Key difference: v2.13 calls `INT0_vector()` (the INT0 handler) during initialization as a probing step. This runs the demodulator availability check during USB setup, before enabling interrupts. v2.06 does not do this. + +Descriptor pointer offsets: +- v2.06: BANK1_R4:R5 = 0x1200 (descriptor base) +- v2.13: BANK1_R4:R5 = 0x0E00 (descriptor base, lower due to code restructuring) + +--- + +## 3. Structural Differences + +### 3.1 v2.13 Retry Loops (FUN_CODE_1799 and FUN_CODE_1ca0) + +In the main init function `FUN_CODE_0800`, v2.13 has: + +```c +// Retry loop 1: FUN_CODE_1799 - demodulator signature verification +DAT_INTMEM_36 = 0x14; // 20 attempts +while (DAT_INTMEM_36 != 0 && FUN_CODE_1799() fails) { + DAT_INTMEM_36--; +} +if (DAT_INTMEM_36 == 0) { + FUN_CODE_1ac6(100); // tuning acquisition with 100ms delay +} + +// Retry loop 2: FUN_CODE_1ca0 - descriptor checksum verification +DAT_INTMEM_36 = 0x14; // 20 attempts +while (DAT_INTMEM_36 != 0 && FUN_CODE_1ca0() fails) { + DAT_INTMEM_36--; +} +if (DAT_INTMEM_36 == 0) { + FUN_CODE_1ac6(100); // tuning acquisition with 100ms delay +} +``` + +**FUN_CODE_1799 - Demodulator Signature Verification:** +1. Calls FUN_CODE_1d4b() which writes 4 bytes via I2C to device 0x7F, register 0xF0 (demodulator control) +2. Saves parameters DAT_INTMEM_39:3A +3. Checks if parameters match 0x021C (known good value) - returns early if match +4. Reads 5 I2C bytes via FUN_CODE_0718, each at register 0x0A offset (stepping by 2) +5. Subtracts 0x30 ('0') from each byte (ASCII to binary conversion) +6. Sums the values and compares sum to the saved parameters +7. Returns success (carry set) if checksum matches + +This verifies the demodulator responds correctly with the expected identification pattern. The ASCII-to-binary conversion suggests the demod returns a readable version string at register 0x0A. + +**FUN_CODE_1ca0 - Descriptor Checksum Verification:** +1. Iterates bytes 6 through 0x29 (36 bytes) of the BANK2_R6:R7 descriptor block +2. Computes running sum, compares against expected value 0x0706 +3. If first block passes, iterates bytes 0x2C through 0x4F (36 bytes) of same block +4. Computes second running sum, compares against expected value 0x0686 +5. Returns success only if both checksums match + +This validates the integrity of a 2-block descriptor/configuration structure (possibly EEPROM-loaded calibration data). + +**v2.06 equivalent:** v2.06 does NOT have these retry loops. It calls `FUN_CODE_1a0e` (serial number/EEPROM reader) directly without verification, then proceeds immediately. There is no signature check or checksum validation. + +### 3.2 Version Byte Check (Hardware Revision Detection) + +After the retry loops, v2.13 performs: + +```c +// Read byte at descriptor_base + 10 +byte version_byte = *(BANK1_R4:R5 + 10); // 0x0E0A +if (version_byte == 0x03) { + bVar4 = 0x80; // flag = set +} else { + bVar4 = 0; // flag = clear +} +_1_3 = bVar4 >> 7; // store as bit flag _1_3 +``` + +This reads byte offset 10 from the USB descriptor base address. Offset 10 in a USB device descriptor is `bMaxPacketSize0` in standard USB, but since this is a custom descriptor area, it likely encodes a hardware revision. The value 0x03 sets bit flag `_1_3`, creating a hardware-revision-aware code path. + +**Impact:** The `_1_3` flag is used elsewhere in v2.13 to conditionally execute different initialization sequences, supporting multiple hardware revisions of the SkyWalker-1 board. + +### 3.3 FUN_CODE_2031 - USB Reconnect Before Main Loop + +```c +void FUN_CODE_2031(void) { + if (_0_0 == 0) { + CPUCS |= 0x08; // Set CPUCS.3 (8051 reset bit? Or re-enumerate) + } else { + CPUCS |= 0x0A; // Set CPUCS.3 + CPUCS.1 + } + FUN_CODE_14b9(5, 0xDC); // Delay ~1500 cycles + EPIRQ = 0xFF; // Clear all endpoint interrupts + USBIRQ = 0xFF; // Clear all USB interrupts + DAT_SFR_91 &= 0xEF; // Clear EXIF.4 (USB interrupt flag) + CPUCS &= 0xF7; // Clear CPUCS.3 +} +``` + +This performs a controlled USB re-enumeration by pulsing CPUCS.3, then clearing all pending USB/endpoint interrupts. The conditional on `_0_0` adds CPUCS.1 when the flag is set (possibly switching between 12MHz and 48MHz operation). + +**v2.06 equivalent:** In v2.06, this exact same logic exists as `INT0_vec` (the INT0 interrupt handler at 0x0003). The critical difference is that in v2.06 this code runs as an interrupt handler, while in v2.13 it's called as a normal function (`FUN_CODE_2031`) before the main loop starts, AND the INT0 vector is repurposed for demodulator polling (see section 4). + +--- + +## 4. INT0 Handler Difference + +### v2.06 INT0 (CODE:0003) - USB Re-enumeration + +```c +void INT0_vec(void) { + if (_0_7 == 0) { + CPUCS |= 0x08; // CPUCS bit 3 + } else { + CPUCS |= 0x0A; // CPUCS bits 3+1 + } + FUN_CODE_1dfb(5, 0xDC); // Delay + EPIRQ = 0xFF; // Clear endpoint IRQs + USBIRQ = 0xFF; // Clear USB IRQs + DAT_SFR_91 &= 0xEF; // Clear external interrupt flag + CPUCS &= 0xF7; // Clear CPUCS bit 3 +} +``` + +Simple USB reconnect/re-enumeration handler. Pulses CPUCS.3, clears all pending interrupts. + +### v2.13 INT0 (CODE:0003) - Demodulator Availability Polling + +```c +void INT0_vector(void) { + for (DAT_INTMEM_37 = 0x28; DAT_INTMEM_37 != 0; DAT_INTMEM_37--) { + // Read I2C device 0x7F (demod A), checking for response + byte result = FUN_CODE_2239(0x7F); // I2C read from 0x7F + if (result != 0x01) { + // Try I2C device 0x3F (demod B) + result = FUN_CODE_2239(0x3F); // I2C read from 0x3F + if (result != 0x01) break; // Neither responded normally + } + } + _1_4 = (DAT_INTMEM_37 == 0); // Set flag if loop exhausted (no demod found) +} +``` + +This is a complete replacement of INT0's purpose. Instead of USB re-enumeration, INT0 now polls two I2C devices (0x7F and 0x3F) up to 40 times (0x28). These are two possible addresses for the satellite demodulator IC. + +**FUN_CODE_2239 decompiled:** +```c +undefined1 FUN_CODE_2239(byte device_addr) { + DAT_INTMEM_48 = 0xE1; // buffer address high + DAT_INTMEM_49 = 0; // buffer address low + FUN_CODE_0eea(1, device_addr, 0x51); // I2C read 1 byte from device + return DAT_EXTMEM_e100; // return the read byte +} +``` + +The function performs an I2C single-byte read from the specified device address, using address 0x51 as a parameter (likely selecting a specific I2C bus or mode via the FX2's auxiliary I2C controller at 0xE678). It stores the result at 0xE100 (XRAM buffer). + +**Behavioral meaning:** The flag `_1_4` is set to 1 if neither demodulator responded after 40 attempts - indicating no demodulator hardware is present. This flag is later checked by: +- `FUN_CODE_21d1` (command 0x9D handler) - skips demodulator reset if `_1_4 != 1` +- Various initialization paths to avoid hanging on missing hardware + +**Why this matters:** v2.06 assumes the demodulator is always present. v2.13 can detect and gracefully handle boards where the demodulator is absent or unresponsive, making it more robust for manufacturing QC and field failures. + +--- + +## 5. What Can v2.13 Do That v2.06 Cannot? + +### 5.1 Demodulator Hardware Detection +v2.13 probes two I2C addresses (0x7F, 0x3F) at startup to determine which demodulator variant is installed, or if none is present. v2.06 blindly assumes the hardware configuration. + +### 5.2 Host-Initiated Demodulator Re-initialization (Command 0x9A) +The host can trigger a demodulator re-initialization via USB vendor command 0x9A, with up to 3 retry attempts. v2.06 has no mechanism for the host to request re-initialization. + +### 5.3 Demodulator Status Read (Command 0x99) +Direct I2C register read of demodulator register 0xF9, returned to host. This enables diagnostic/monitoring software to check demodulator status without going through the full signal quality pipeline. + +### 5.4 Host-Controlled Tuning Delay (Command 0x9C) +Allows the host to invoke the tuning acquisition sequence with a configurable delay parameter. In v2.06, the tuning timing is entirely firmware-controlled with no host influence. + +### 5.5 DiSEqC GPIO Pin Reassignment +All firmware versions use the same GPIO bit-bang algorithm for DiSEqC signaling. The only change is the data pin assignment per PCB revision: + +| Version | Data Pin | Carrier Pin | Byte Transmit | Bit Symbol | Timer Tick | +|---------|----------|-------------|--------------|------------|------------| +| v2.06 | **P0.7** | P0.3 | 0x2098 | 0x23B5 | 0x24C6 | +| Rev.2 v2.10 | **P0.4** | P0.3 | FUN_CODE_07d1 | FUN_CODE_213c | FUN_CODE_225f | +| v2.13 FW1 | **P0.0** | P0.3 | FUN_CODE_2060 | FUN_CODE_22f3 | func_0x2431 | + +The algorithm is identical across all versions: Manchester-encoded bit-bang with Timer2-based timing, odd parity per byte, and 25-tick tone bursts for mini-commands. + +### 5.6 Firmware/Descriptor Integrity Verification +v2.13 validates demodulator identification (ASCII version string checksum) and descriptor block integrity (two 36-byte checksums) before proceeding. If verification fails after 20 attempts, it falls back to a recovery sequence. v2.06 does no integrity checking. + +### 5.7 Hardware Revision Awareness +The version byte check (descriptor offset 10, value 0x03) creates conditional code paths allowing a single firmware image to support multiple SkyWalker-1 hardware revisions. v2.06 has a single code path for one hardware revision. + +### 5.8 Simplified BCM4500 Status Polling +v2.06's `FUN_CODE_2000` polls three separate BCM4500 registers (0xA2, 0xA8, 0xA4 via I2C) to determine demodulator readiness. v2.13's `FUN_CODE_208d` polls only one register (0xA4), suggesting either the demodulator firmware was updated to consolidate status, or the additional checks were found to be redundant. + +### 5.9 Conditional Demodulator Reset (Command 0x9D) +v2.13's 0x9D handler can trigger a full demodulator reset sequence (FUN_CODE_1e3c -> register writes to 0x18 bus) controlled by the host via wValue. This is useful for error recovery without full device re-enumeration. + +--- + +## 6. Architecture Summary + +| Aspect | v2.06 | v2.13 | +|--------|-------|-------| +| Total functions | 61 | 82 (+21) | +| RESET vector | 0x188D | 0x170D | +| Stack pointer | 0x72 | 0x50 | +| Init data table | CODE:0B46 | CODE:0B88 | +| Descriptor base | 0x1200 | 0x0E00 | +| Config byte (IRAM) | 0x6D | 0x4F | +| INT0 purpose | USB re-enumerate | Demod probe | +| DiSEqC data pin | P0.7 (GPIO bit-bang) | P0.0 (GPIO bit-bang) | +| Demod init | Direct, no retry | 20-attempt retry | +| Integrity checks | None | Checksum verification | +| HW revision support | Single | Multi-revision (flag _1_3) | +| New vendor cmds | -- | 0x99, 0x9A, 0x9C | + +--- + +## 7. DiSEqC Timing Chain Analysis + +### 7.1 Timer2 Configuration (Identical Across All Versions) + +All firmware versions configure Timer2 identically during USB descriptor setup: + +``` +T2CON = 0x04 ; Auto-reload mode, internal clock, TR2=1 (running) +RCAP2H = 0xF8 ; Reload high byte +RCAP2L = 0x2F ; Reload low byte -> RCAP2 = 0xF82F = 63535 +CKCON = 0x00 ; Default (T2M=0 -> Timer2 clock = CLKOUT/12) +``` + +**Timer2 Clock Derivation:** +``` +FX2 master clock = 48 MHz +CKCON.T2M = 0 -> Timer2 clock = 48 MHz / 12 = 4 MHz +Count per overflow = 65536 - 63535 = 2001 +Tick period = 2001 / 4,000,000 = 500.25 us ~ 500 us +Tick frequency ~ 2.0 kHz +``` + +Timer2 runs continuously from power-on and is never stopped or reconfigured. It serves as a stable 500 us timebase for all DiSEqC operations. + +### 7.2 DiSEqC Signal Architecture + +``` +FX2 Firmware External Hardware Coax Cable ++------------------+ +--------------------+ +------------------+ +| | | | | | +| P0.3 (carrier) |---->| 22 kHz oscillator |---->| LNB power line | +| (enable/disable) | | (gated by P0.3) | | (13V/18V + tone) | +| | | | | | +| P0.x (data bit) | | (internal to FX2 | | | +| (firmware only) | | firmware logic) | | | ++------------------+ +--------------------+ +------------------+ +``` + +The firmware does NOT generate the 22 kHz carrier directly. P0.3 gates an external +22 kHz oscillator circuit on the PCB. The data pin (P0.7/P0.4/P0.0 depending on +version) is used only internally by the firmware to control the Manchester encoding +logic -- it tells the bit-symbol function whether to cut the carrier short or leave +it on for the full period. + +### 7.3 Manchester Encoding (DiSEqC Bit Symbol) + +Each DiSEqC bit consists of 3 Timer2 ticks (3 x 500 us = 1.5 ms): + +**Data '0' (2/3 tone, 1/3 silence):** +``` + Tick 1 Tick 2 Tick 3 + (500 us) (500 us) (500 us) +P0.3: _____|========|========|________| + ^tone ON ^tone OFF + (setup gap) (1.0 ms carrier) (0.5 ms silence) +``` + +**Data '1' (1/3 tone, 2/3 silence):** +``` + Tick 1 Tick 2 Tick 3 + (500 us) (500 us) (500 us) +P0.3: _____|========|________|________| + ^tone ON ^tone OFF early + (setup gap) (0.5 ms carrier) (1.0 ms silence) +``` + +**Implementation (decompiled from Rev.2 FUN_CODE_213c):** +```c +void diseqc_bit_symbol(void) { + wait_TF2(); // Tick 1: inter-bit gap (500 us) + P0 |= 0x08; // P0.3 = 1 -> 22 kHz carrier ON + wait_TF2(); // Tick 2: carrier period (500 us) + if (data_pin != 0) { // If data = '1': + P0 &= 0xF7; // P0.3 = 0 -> carrier OFF (short pulse) + } + wait_TF2(); // Tick 3: final period (500 us) + P0 &= 0xF7; // P0.3 = 0 -> carrier always OFF at end +} +``` + +### 7.4 Byte Transmission (8 Data Bits + Odd Parity) + +Each DiSEqC byte is 9 bits: 8 data (MSB first) + 1 parity (odd). + +**Implementation (decompiled from Rev.2 FUN_CODE_07d1):** +```c +void diseqc_send_byte(char first_byte, byte data) { + byte ones_count = 0; + if (first_byte == 0) TF2 = 0; // Sync timer on first byte + + for (char i = 8; i > 0; i--) { // 8 bits, MSB first + if (data & 0x80) { // Test MSB + data_pin = 1; // Set data = '1' + diseqc_bit_symbol(); // Transmit '1' symbol + ones_count++; + } else { + data_pin = 0; // Set data = '0' + diseqc_bit_symbol(); // Transmit '0' symbol + } + data <<= 1; // Next bit + } + + data_pin = ~ones_count & 1; // Odd parity: '1' if even count + diseqc_bit_symbol(); // Transmit parity bit +} +``` + +**Timing per byte:** 9 bits x 1.5 ms = 13.5 ms + +### 7.5 Tone Burst (Mini DiSEqC Command) + +For legacy 2-way satellite switches, a simple tone burst is used instead of a +full DiSEqC message. The burst is 25 Timer2 ticks of continuous carrier: + +```c +void tone_burst_A(void) { + TF2 = 0; // Sync timer + wait_TF2(); // One tick gap + P0 |= 0x08; // P0.3 = 1 -> carrier ON + for (char i = 25; i > 0; i--) { + wait_TF2(); // 25 x 500 us = 12.5 ms + } + P0 &= 0xF7; // P0.3 = 0 -> carrier OFF +} +``` + +**Burst duration:** 25 x 500 us = 12.5 ms (matches DiSEqC spec) + +### 7.6 Timer Tick Wait (TF2 Polling) + +The lowest-level timing primitive is a busy-wait on Timer2 overflow: + +```c +void wait_TF2(void) { + while (TF2 == 0) {} // Poll Timer2 overflow flag + TF2 = 0; // Clear flag for next tick +} +``` + +This is identical across all versions (v2.06: 0x24C6, Rev.2: FUN_CODE_225f, +v2.13: func_0x2431). Timer2 overflows every 500.25 us, providing the fundamental +DiSEqC timebase. + +### 7.7 CPU Clock Compensation (Delay Function) + +The delay function used before DiSEqC transmission adjusts for CPU clock speed: + +```c +void delay(byte high, byte low) { + byte clkspd = CPUCS & 0x18; // CPUCS[4:3] = clock speed bits + if (clkspd == 0x00) { // 12 MHz: halve the count + // Adjust high:low /= 2 + } else if (clkspd == 0x10) { // 48 MHz: double the count + // Adjust high:low *= 2 + } + // 24 MHz (0x08): use count as-is + while (high:low > 0) { + wait_TF2(); + high:low--; + } +} +``` + +The pre-DiSEqC delay call is `delay(0, 0x0F)` = 15 ticks x 500 us = 7.5 ms. +This allows the LNB voltage to stabilize before DiSEqC signaling begins. + +### 7.8 Complete DiSEqC Timing Summary + +| Parameter | Value | Source | +|-----------|-------|--------| +| Timer2 clock | 4 MHz (48 MHz / 12) | CKCON default, T2M=0 | +| Timer2 reload | 0xF82F | RCAP2H:RCAP2L | +| Tick period | 500.25 us | (65536 - 63535) / 4 MHz | +| Bit period | 1.5 ms (3 ticks) | DiSEqC Manchester encoding | +| Byte period | 13.5 ms (9 bits) | 8 data + 1 parity | +| Tone burst | 12.5 ms (25 ticks) | Mini-command A/B | +| Pre-TX delay | 7.5 ms (15 ticks) | Voltage settling | +| Data '0' | 1.0 ms tone + 0.5 ms silence | 2/3 duty cycle | +| Data '1' | 0.5 ms tone + 1.0 ms silence | 1/3 duty cycle | +| Carrier frequency | 22 kHz (external oscillator) | Gated by P0.3 | +| Carrier enable | P0.3 | All versions | +| Data pin (v2.06) | P0.7 | PCB revision A | +| Data pin (Rev.2) | P0.4 | PCB revision B | +| Data pin (v2.13) | P0.0 | PCB revision C | diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1AntennaPin.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1AntennaPin.h index fa36639..c0ca6bd 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1AntennaPin.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1AntennaPin.h @@ -1,102 +1,102 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1AntennaPin.h - Author : - Date : - Purpose : This File Holds the Antenna Pin related declarations - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -#ifndef __SKYWALKER1_ANTENNA_PIN_H -#define __SKYWALKER1_ANTENNA_PIN_H - -/* Include the Library and Other header file */ -#include "SkyWalker1TunerPin.h" -#include "SkyWalker1Extended.h" //For the Extended BDA -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Global & Static variables Declaration */ - -//Antenna Pin class. -class CAntennaPin : public CTunerPin -{ -public: - - static NTSTATUS PinSetDeviceState( - IN PKSPIN Pin, - IN KSSTATE ToState, - IN KSSTATE FromState - ); - - static NTSTATUS IntersectDataFormat( - IN PVOID pContext, - IN PIRP pIoRequestPacket, - IN PKSP_PIN Pin, - IN PKSDATARANGE DataRange, - IN PKSDATARANGE MatchingDataRange, - IN ULONG DataBufferSize, - OUT PVOID Data OPTIONAL, - OUT PULONG DataSize - ); - - //Network provider and AVStream use these functions - //to set and get properties of nodes that are controlled - //by the input pin. - - static NTSTATUS GetTunerProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ); - - static NTSTATUS SetTunerProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ); - - - - //Network provider and AVStream use these functions - //to set and get properties of nodes that are controlled - //by the input pin. - static NTSTATUS GetTunerLnbProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ); - - static NTSTATUS SetTunerLnbProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ); - -private: - - KSSTATE m_KsState; -}; - - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1AntennaPin.h + Author : + Date : + Purpose : This File Holds the Antenna Pin related declarations + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +#ifndef __SKYWALKER1_ANTENNA_PIN_H +#define __SKYWALKER1_ANTENNA_PIN_H + +/* Include the Library and Other header file */ +#include "SkyWalker1TunerPin.h" +#include "SkyWalker1Extended.h" //For the Extended BDA +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Global & Static variables Declaration */ + +//Antenna Pin class. +class CAntennaPin : public CTunerPin +{ +public: + + static NTSTATUS PinSetDeviceState( + IN PKSPIN Pin, + IN KSSTATE ToState, + IN KSSTATE FromState + ); + + static NTSTATUS IntersectDataFormat( + IN PVOID pContext, + IN PIRP pIoRequestPacket, + IN PKSP_PIN Pin, + IN PKSDATARANGE DataRange, + IN PKSDATARANGE MatchingDataRange, + IN ULONG DataBufferSize, + OUT PVOID Data OPTIONAL, + OUT PULONG DataSize + ); + + //Network provider and AVStream use these functions + //to set and get properties of nodes that are controlled + //by the input pin. + + static NTSTATUS GetTunerProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ); + + static NTSTATUS SetTunerProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ); + + + + //Network provider and AVStream use these functions + //to set and get properties of nodes that are controlled + //by the input pin. + static NTSTATUS GetTunerLnbProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ); + + static NTSTATUS SetTunerLnbProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ); + +private: + + KSSTATE m_KsState; +}; + + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + #endif /*__SKYWALKER1_TRANSPORT_PIN_H*/ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CaptureFilter.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CaptureFilter.h index bbe9723..b078715 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CaptureFilter.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CaptureFilter.h @@ -1,72 +1,72 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1CaptureFilter.h - Author : - Date : - Purpose : This file contains the filter level header for - the capture filter. - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -#ifndef SKYWALKER1_CAPTURE_FILTER_H -#define SKYWALKER1_CAPTURE_FILTER_H - -/* Include the Library and Other header file */ -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Global & Static variables Declaration */ - -//The Capture filter class. -class CCaptureFilter -{ -public: - - //The capture filter object constructor. Since the new operator will - //have zeroed the memory, do not bother initializing any NULL or 0 - //fields. Only initialize non-NULL, non-0 fields. - CCaptureFilter (IN PKSFILTER Filter); - - //The capture filter destructor. - ~CCaptureFilter (); - - //This is the filter creation dispatch for the capture filter. It - //creates the CCaptureFilter object, associates it with the AVStream - //object, and bags it for easy cleanup later. - static NTSTATUS Create( IN OUT PKSFILTER pKSFilter, - IN PIRP pIoRequestPacket); - - -private: - - //The AVStream filter object associated with this CCaptureFilter. - PKSFILTER m_Filter; - - //This is the bag cleanup callback for the CCaptureFilter. Not providing - //one would cause ExFreePool to be used. This is not good for C++ - //constructed objects. We simply delete the object here. - static void Cleanup ( IN CCaptureFilter *CapFilter ); - -}; - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1CaptureFilter.h + Author : + Date : + Purpose : This file contains the filter level header for + the capture filter. + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +#ifndef SKYWALKER1_CAPTURE_FILTER_H +#define SKYWALKER1_CAPTURE_FILTER_H + +/* Include the Library and Other header file */ +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Global & Static variables Declaration */ + +//The Capture filter class. +class CCaptureFilter +{ +public: + + //The capture filter object constructor. Since the new operator will + //have zeroed the memory, do not bother initializing any NULL or 0 + //fields. Only initialize non-NULL, non-0 fields. + CCaptureFilter (IN PKSFILTER Filter); + + //The capture filter destructor. + ~CCaptureFilter (); + + //This is the filter creation dispatch for the capture filter. It + //creates the CCaptureFilter object, associates it with the AVStream + //object, and bags it for easy cleanup later. + static NTSTATUS Create( IN OUT PKSFILTER pKSFilter, + IN PIRP pIoRequestPacket); + + +private: + + //The AVStream filter object associated with this CCaptureFilter. + PKSFILTER m_Filter; + + //This is the bag cleanup callback for the CCaptureFilter. Not providing + //one would cause ExFreePool to be used. This is not good for C++ + //constructed objects. We simply delete the object here. + static void Cleanup ( IN CCaptureFilter *CapFilter ); + +}; + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + #endif /*SKYWALKER1_CAPTURE_FILTER_H*/ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CapturePin.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CapturePin.h index 28d06f5..b2c5442 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CapturePin.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CapturePin.h @@ -1,161 +1,161 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1CapturePin.h - Author : - Date : - Purpose : This file contains header for the video capture pin on the capture - filter. - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -/* Include the Library and Other header file */ -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Global & Static variables Declaration */ - -//This Structure can be used in future for the -//better stream management -typedef struct _STREAM_POINTER_CONTEXT -{ - ULONG ulFrameIndex; - -} STREAM_POINTER_CONTEXT, *PSTREAM_POINTER_CONTEXT; - - -//The video capture pin class. -class CCapturePin : public ICaptureSink -{ - -private: - - //The AVStream pin we're associated with. - PKSPIN m_Pin; - - //Pointer to the internal device object for our capture device. - //We access the Device through this object. - CSkyWalker1Device *m_Device; - - //The state we've put the hardware into. This allows us to keep track - //of whether to do things like unpausing or restarting. - HARDWARE_STATE m_HardwareState; - - //The clock we've been assigned. As with other capture filters, we do - //not expose a clock. If one has been assigned, we will use it to - //time stamp packets (plus a reasonable delta to work the capture stream - //in a preview graph). - PIKSREFERENCECLOCK m_Clock; - - //The transport information for this capture pin. The settings for device will be - //programmed for this transport info. - PBDA_TRANSPORT_INFO m_TransportInfo; - - //This Variable is used to keep the count of the Streams sent for the - //Data read - ULONG m_CurrentFrameIndex; - - //An indication of whether or not this pin has acquired the necessary - //hardware resources to operate. When the pin reaches KSSTATE_ACQUIRE, - //we attempt to acquire the hardware. This flag will be set based on - //our success / failure. - BOOLEAN m_AcquiredResources; - - //Clean up any references we hold on frames in the queue. This is called - //when we abruptly stop the fake hardware. - NTSTATUS CleanupReferences (); - - //This is the state transition handler for the capture pin. It attempts - //to acquire resources for the capture pin (or releasing them if - //necessary) and starts and stops the hardware as required. - NTSTATUS SetState ( IN KSSTATE ToState,IN KSSTATE FromState); - - //This is the processing dispatch for the capture pin. It handles - //sending the Streams to the Device. - NTSTATUS Process(); - - //This routine is not required as the Transport Information is already - //provided into the CCapturePin but still using it to make the - //settings flexible - PBDA_TRANSPORT_INFO CaptureBdaTransportInfo (); - - //This is the free callback from the bagged item (CCapturePin). If we - //do not provide a callback when we bag the CCapturePin, ExFreePool - //would be called. This is not desirable for C++ constructed objects. - //We merely delete the object here. - static void Cleanup (IN CCapturePin *Pin) - { - delete Pin; - } - -public: - - - //The capture pin's constructor. Initialize any non-0, non-NULL fields - //(since new will have zero'ed the memory anyway) and set up our - //device level pointers for access during capture routines. - CCapturePin (IN PKSPIN Pin); - - //The capture pin's destructor. - ~CCapturePin (); - - CSkyWalker1Device* GetDevice() - { - return m_Device; - } - - //This is the capture sink notification mechanism for mapping completion. - //When the device DPC detects that a given number of mappings have been - //completed by the fake hardware, it signals the capture sink of this - //through this method. - virtual void ReleaseStream(IN ULONG ulStreamIndex); - - //This is the creation dispatch for the capture pin. It creates - //the CCapturePin object and associates it with the AVStream object - //bagging it in the process. - static NTSTATUS PinCreate( IN OUT PKSPIN pKSPin, - IN PIRP pIoRequestPacket - ); - - //This is the set device state dispatch for the pin. The routine bridges - //to SetState() in the context of the CCapturePin. - static NTSTATUS DispatchSetState ( - IN PKSPIN Pin, - IN KSSTATE ToState, - IN KSSTATE FromState - ) - { - return - (reinterpret_cast(Pin->Context))-> - SetState(ToState, FromState); - } - - //This is the processing dispatch for the capture pin. The routine - //bridges to Process() in the context of the CCapturePin. - static NTSTATUS DispatchProcess (IN PKSPIN Pin) - { - return - (reinterpret_cast(Pin->Context))->Process(); - } -}; - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1CapturePin.h + Author : + Date : + Purpose : This file contains header for the video capture pin on the capture + filter. + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +/* Include the Library and Other header file */ +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Global & Static variables Declaration */ + +//This Structure can be used in future for the +//better stream management +typedef struct _STREAM_POINTER_CONTEXT +{ + ULONG ulFrameIndex; + +} STREAM_POINTER_CONTEXT, *PSTREAM_POINTER_CONTEXT; + + +//The video capture pin class. +class CCapturePin : public ICaptureSink +{ + +private: + + //The AVStream pin we're associated with. + PKSPIN m_Pin; + + //Pointer to the internal device object for our capture device. + //We access the Device through this object. + CSkyWalker1Device *m_Device; + + //The state we've put the hardware into. This allows us to keep track + //of whether to do things like unpausing or restarting. + HARDWARE_STATE m_HardwareState; + + //The clock we've been assigned. As with other capture filters, we do + //not expose a clock. If one has been assigned, we will use it to + //time stamp packets (plus a reasonable delta to work the capture stream + //in a preview graph). + PIKSREFERENCECLOCK m_Clock; + + //The transport information for this capture pin. The settings for device will be + //programmed for this transport info. + PBDA_TRANSPORT_INFO m_TransportInfo; + + //This Variable is used to keep the count of the Streams sent for the + //Data read + ULONG m_CurrentFrameIndex; + + //An indication of whether or not this pin has acquired the necessary + //hardware resources to operate. When the pin reaches KSSTATE_ACQUIRE, + //we attempt to acquire the hardware. This flag will be set based on + //our success / failure. + BOOLEAN m_AcquiredResources; + + //Clean up any references we hold on frames in the queue. This is called + //when we abruptly stop the fake hardware. + NTSTATUS CleanupReferences (); + + //This is the state transition handler for the capture pin. It attempts + //to acquire resources for the capture pin (or releasing them if + //necessary) and starts and stops the hardware as required. + NTSTATUS SetState ( IN KSSTATE ToState,IN KSSTATE FromState); + + //This is the processing dispatch for the capture pin. It handles + //sending the Streams to the Device. + NTSTATUS Process(); + + //This routine is not required as the Transport Information is already + //provided into the CCapturePin but still using it to make the + //settings flexible + PBDA_TRANSPORT_INFO CaptureBdaTransportInfo (); + + //This is the free callback from the bagged item (CCapturePin). If we + //do not provide a callback when we bag the CCapturePin, ExFreePool + //would be called. This is not desirable for C++ constructed objects. + //We merely delete the object here. + static void Cleanup (IN CCapturePin *Pin) + { + delete Pin; + } + +public: + + + //The capture pin's constructor. Initialize any non-0, non-NULL fields + //(since new will have zero'ed the memory anyway) and set up our + //device level pointers for access during capture routines. + CCapturePin (IN PKSPIN Pin); + + //The capture pin's destructor. + ~CCapturePin (); + + CSkyWalker1Device* GetDevice() + { + return m_Device; + } + + //This is the capture sink notification mechanism for mapping completion. + //When the device DPC detects that a given number of mappings have been + //completed by the fake hardware, it signals the capture sink of this + //through this method. + virtual void ReleaseStream(IN ULONG ulStreamIndex); + + //This is the creation dispatch for the capture pin. It creates + //the CCapturePin object and associates it with the AVStream object + //bagging it in the process. + static NTSTATUS PinCreate( IN OUT PKSPIN pKSPin, + IN PIRP pIoRequestPacket + ); + + //This is the set device state dispatch for the pin. The routine bridges + //to SetState() in the context of the CCapturePin. + static NTSTATUS DispatchSetState ( + IN PKSPIN Pin, + IN KSSTATE ToState, + IN KSSTATE FromState + ) + { + return + (reinterpret_cast(Pin->Context))-> + SetState(ToState, FromState); + } + + //This is the processing dispatch for the capture pin. The routine + //bridges to Process() in the context of the CCapturePin. + static NTSTATUS DispatchProcess (IN PKSPIN Pin) + { + return + (reinterpret_cast(Pin->Context))->Process(); + } +}; + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + + diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CommonDef.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CommonDef.h index bf9ec7d..8a47836 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CommonDef.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1CommonDef.h @@ -1,149 +1,149 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1CommonDef.h - Author : - Date : - Purpose : File to hold the common definitions for the Driver - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -#ifndef __SKYWALKER1_COMMON_DEF_H -#define __SKYWALKER1_COMMON_DEF_H - -/* Include the Library and Other header file */ -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -#define TUNER_MEM_TAG 'MNUT' -#define CAPTURE_MEM_TAG 'MPAC' - -// Implementation GUID for SkyWalker1 Tuner and Capture -// Must match the KSSTRING used in the installation INFs interface sections -//Tuner Filter GUID {5C4E764F-AB43-46A9-B21E-8529C70F0A23} -#define GUID_SKYWALKER_TUNER_FILTER\ - 0x5C4E764F, 0xAB43, 0x46A9, 0xB2, 0x1E, 0x85, 0x29, 0xC7, 0x0F, 0x0A, 0x23 -DEFINE_GUIDSTRUCT("5C4E764F-AB43-46A9-B21E-8529C70F0A23", SKYWALKER_TUNER_FILTER); -#define SKYWALKER_TUNER_FILTER DEFINE_GUIDNAMED(SKYWALKER_TUNER_FILTER) - -//Capture Filter GUID {0F8F74D9-E524-4D05-BB60-F0C69ACB1756} -#define GUID_SKYWALKER_CAPTURE_FILTER\ - 0x0F8F74D9, 0xE524, 0x4D05, 0xBB, 0x60, 0xF0, 0xC6, 0x9A, 0xCB, 0x17, 0x56 -DEFINE_GUIDSTRUCT("0F8F74D9-E524-4D05-BB60-F0C69ACB1756", SKYWALKER_CAPTURE_FILTER); -// Defines the SKYWALKER_CAPTURE_FILTER as a GUID. -#define SKYWALKER_CAPTURE_FILTER DEFINE_GUIDNAMED(SKYWALKER_CAPTURE_FILTER) - -//Medium GUID {2AEB4A94-FBB7-4FB1-8D74-243B91886EAB} -#define GUID_SKYWALKER_TUNER_OUT_MEDIUM\ - 0x2AEB4A94, 0xFBB7, 0x4FB1, 0x8D, 0x74, 0x24, 0x3B, 0x91, 0x88, 0x6E, 0xAB -DEFINE_GUIDSTRUCT("2AEB4A94-FBB7-4FB1-8D74-243B91886EAB", SKYWALKER_TUNER_OUT_MEDIUM); -// Defines the SKYWALKER_TUNER_OUT_MEDIUM as a GUID. -#define SKYWALKER_TUNER_OUT_MEDIUM DEFINE_GUIDNAMED(SKYWALKER_TUNER_OUT_MEDIUM) - -#define TRANSPORT_PACKET_SIZE 128 //188 -#define TRANSPORT_PACKET_COUNT 64 //512 -#define NUMBER_OF_FRAMES 8 -#define PACKET_PER_FRAME 2 - -#define SYMBOL_RATE_MIN 1000 -#define SYMBOL_RATE_MAX 45000 - -#define TUNER_FREQ_MIN 800000 -#define TUNER_FREQ_MAX 2250000 - -#define IS_VALID(X) (((X)!=NULL)?true:false) - -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ - -// Structure for the Tuner Parameters - -typedef struct _BDATUNER_DEVICE_PARAMETER -{ - //Tuner Properties - ULONG ulCarrierFrequency; - ULONG ulFrequencyMultiplier; - ULONG ulBandWidth; - Polarisation Polarity; - ULONG ulRange; - ULONG ulTransponder; - - //LNB Parameters - ULONG ulLnbLowLOFrequency; - ULONG ulLnbHighLOFrequency; - ULONG ulLnbSwitchFrequency; - - //Demodulator Properties - ULONG ulInnerFecType; - BinaryConvolutionCodeRate InnerFecRate; - ULONG ulOuterFecType; - BinaryConvolutionCodeRate OuterFecRate; - ModulationType CurrentModulationType; - TransmissionMode CurrentTransmissionMode; - GuardInterval CurrentGuardInterval; - SpectralInversion CurrentSpectralInversion; - ULONG ulSymbolRate; - -} BDATUNER_DEVICE_PARAMETER, * PBDATUNER_DEVICE_PARAMETER; - - -// Define a structure that represents the underlying device status. -// -typedef struct _BDATUNER_DEVICE_STATUS -{ - //Tuner Status - DWORD dwSignalStrength; - DWORD dwSignalQuality; - BOOLEAN fCarrierPresent; - - //Demodulator Status - BOOLEAN fSignalLocked; -} BDATUNER_DEVICE_STATUS, * PBDATUNER_DEVICE_STATUS; - -// ICaptureSink: -// -// This is a capture sink interface. The device level calls back the -// CompleteMappings method passing the number of completed mappings for -// the capture pin. This method is called during the device DPC. - -class ICaptureSink -{ -public: - - virtual void ReleaseStream (IN ULONG ulStreamIndex) = 0; -}; - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -extern const BDA_FILTER_TEMPLATE TunerFilterTemplate; -extern const BDA_FILTER_TEMPLATE SkyWalker1CaptureTemplate; -extern const KSFILTER_DESCRIPTOR SkyWalker1TunerFilterDescriptor; -extern const KSFILTER_DESCRIPTOR SkyWalker1CaptureFilterDescriptor; - -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -typedef enum _HARDWARE_STATE { - - HardwareStopped = 0, - HardwarePaused, - HardwareRunning - -} HARDWARE_STATE, *PHARDWARE_STATE; - -/* End of Enumeration declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - -#endif // __SKYWALKER1_COMMON_DEF_H - - - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1CommonDef.h + Author : + Date : + Purpose : File to hold the common definitions for the Driver + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +#ifndef __SKYWALKER1_COMMON_DEF_H +#define __SKYWALKER1_COMMON_DEF_H + +/* Include the Library and Other header file */ +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +#define TUNER_MEM_TAG 'MNUT' +#define CAPTURE_MEM_TAG 'MPAC' + +// Implementation GUID for SkyWalker1 Tuner and Capture +// Must match the KSSTRING used in the installation INFs interface sections +//Tuner Filter GUID {5C4E764F-AB43-46A9-B21E-8529C70F0A23} +#define GUID_SKYWALKER_TUNER_FILTER\ + 0x5C4E764F, 0xAB43, 0x46A9, 0xB2, 0x1E, 0x85, 0x29, 0xC7, 0x0F, 0x0A, 0x23 +DEFINE_GUIDSTRUCT("5C4E764F-AB43-46A9-B21E-8529C70F0A23", SKYWALKER_TUNER_FILTER); +#define SKYWALKER_TUNER_FILTER DEFINE_GUIDNAMED(SKYWALKER_TUNER_FILTER) + +//Capture Filter GUID {0F8F74D9-E524-4D05-BB60-F0C69ACB1756} +#define GUID_SKYWALKER_CAPTURE_FILTER\ + 0x0F8F74D9, 0xE524, 0x4D05, 0xBB, 0x60, 0xF0, 0xC6, 0x9A, 0xCB, 0x17, 0x56 +DEFINE_GUIDSTRUCT("0F8F74D9-E524-4D05-BB60-F0C69ACB1756", SKYWALKER_CAPTURE_FILTER); +// Defines the SKYWALKER_CAPTURE_FILTER as a GUID. +#define SKYWALKER_CAPTURE_FILTER DEFINE_GUIDNAMED(SKYWALKER_CAPTURE_FILTER) + +//Medium GUID {2AEB4A94-FBB7-4FB1-8D74-243B91886EAB} +#define GUID_SKYWALKER_TUNER_OUT_MEDIUM\ + 0x2AEB4A94, 0xFBB7, 0x4FB1, 0x8D, 0x74, 0x24, 0x3B, 0x91, 0x88, 0x6E, 0xAB +DEFINE_GUIDSTRUCT("2AEB4A94-FBB7-4FB1-8D74-243B91886EAB", SKYWALKER_TUNER_OUT_MEDIUM); +// Defines the SKYWALKER_TUNER_OUT_MEDIUM as a GUID. +#define SKYWALKER_TUNER_OUT_MEDIUM DEFINE_GUIDNAMED(SKYWALKER_TUNER_OUT_MEDIUM) + +#define TRANSPORT_PACKET_SIZE 128 //188 +#define TRANSPORT_PACKET_COUNT 64 //512 +#define NUMBER_OF_FRAMES 8 +#define PACKET_PER_FRAME 2 + +#define SYMBOL_RATE_MIN 1000 +#define SYMBOL_RATE_MAX 45000 + +#define TUNER_FREQ_MIN 800000 +#define TUNER_FREQ_MAX 2250000 + +#define IS_VALID(X) (((X)!=NULL)?true:false) + +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ + +// Structure for the Tuner Parameters + +typedef struct _BDATUNER_DEVICE_PARAMETER +{ + //Tuner Properties + ULONG ulCarrierFrequency; + ULONG ulFrequencyMultiplier; + ULONG ulBandWidth; + Polarisation Polarity; + ULONG ulRange; + ULONG ulTransponder; + + //LNB Parameters + ULONG ulLnbLowLOFrequency; + ULONG ulLnbHighLOFrequency; + ULONG ulLnbSwitchFrequency; + + //Demodulator Properties + ULONG ulInnerFecType; + BinaryConvolutionCodeRate InnerFecRate; + ULONG ulOuterFecType; + BinaryConvolutionCodeRate OuterFecRate; + ModulationType CurrentModulationType; + TransmissionMode CurrentTransmissionMode; + GuardInterval CurrentGuardInterval; + SpectralInversion CurrentSpectralInversion; + ULONG ulSymbolRate; + +} BDATUNER_DEVICE_PARAMETER, * PBDATUNER_DEVICE_PARAMETER; + + +// Define a structure that represents the underlying device status. +// +typedef struct _BDATUNER_DEVICE_STATUS +{ + //Tuner Status + DWORD dwSignalStrength; + DWORD dwSignalQuality; + BOOLEAN fCarrierPresent; + + //Demodulator Status + BOOLEAN fSignalLocked; +} BDATUNER_DEVICE_STATUS, * PBDATUNER_DEVICE_STATUS; + +// ICaptureSink: +// +// This is a capture sink interface. The device level calls back the +// CompleteMappings method passing the number of completed mappings for +// the capture pin. This method is called during the device DPC. + +class ICaptureSink +{ +public: + + virtual void ReleaseStream (IN ULONG ulStreamIndex) = 0; +}; + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +extern const BDA_FILTER_TEMPLATE TunerFilterTemplate; +extern const BDA_FILTER_TEMPLATE SkyWalker1CaptureTemplate; +extern const KSFILTER_DESCRIPTOR SkyWalker1TunerFilterDescriptor; +extern const KSFILTER_DESCRIPTOR SkyWalker1CaptureFilterDescriptor; + +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +typedef enum _HARDWARE_STATE { + + HardwareStopped = 0, + HardwarePaused, + HardwareRunning + +} HARDWARE_STATE, *PHARDWARE_STATE; + +/* End of Enumeration declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + +#endif // __SKYWALKER1_COMMON_DEF_H + + + diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Control.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Control.h index d1f9093..c08a792 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Control.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Control.h @@ -1,127 +1,127 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1Control.cpp - Author : - Date : - Purpose : This File Holds the Device Control related declarations - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -#ifndef __SKYWALKER1_CONTROL_H -#define __SKYWALKER1_CONTROL_H - -/* Include the Library and Other header file */ -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ - -/* gp8psk commands */ - -/* Twinhan Vendor requests */ -#define TH_COMMAND_IN 0xC0 -#define TH_COMMAND_OUT 0xC1 - -/* gp8psk commands */ - -#define GET_8PSK_CONFIG 0x80 /* in */ -#define SET_8PSK_CONFIG 0x81 -#define I2C_WRITE 0x83 -#define I2C_READ 0x84 -#define ARM_TRANSFER 0x85 -#define TUNE_8PSK 0x86 -#define GET_SIGNAL_STRENGTH 0x87 /* in */ -#define LOAD_BCM4500 0x88 -#define BOOT_8PSK 0x89 /* in */ -#define START_INTERSIL 0x8A /* in */ -#define SET_LNB_VOLTAGE 0x8B -#define SET_22KHZ_TONE 0x8C -#define SEND_DISEQC_COMMAND 0x8D -#define SET_DVB_MODE 0x8E -#define SET_DN_SWITCH 0x8F -#define GET_SIGNAL_LOCK 0x90 /* in */ -#define GET_SERIAL_NUMBER 0x93 /* in */ -#define USE_EXTRA_VOLT 0x94 -#define CW3K_INIT 0x9d - -/* PSK_configuration bits */ -#define bm8pskStarted 0x01 -#define bm8pskFW_Loaded 0x02 -#define bmIntersilOn 0x04 -#define bmDVBmode 0x08 -#define bm22kHz 0x10 -#define bmSEL18V 0x20 -#define bmDCtuned 0x40 -#define bmArmed 0x80 - -/* Satellite modulation modes */ -#define ADV_MOD_DVB_QPSK 0 /* DVB-S QPSK */ -#define ADV_MOD_TURBO_QPSK 1 /* Turbo QPSK */ -#define ADV_MOD_TURBO_8PSK 2 /* Turbo 8PSK (also used for Trellis 8PSK) */ -#define ADV_MOD_TURBO_16QAM 3 /* Turbo 16QAM (also used for Trellis 8PSK) */ - -#define ADV_MOD_DCII_C_QPSK 4 /* Digicipher II Combo */ -#define ADV_MOD_DCII_I_QPSK 5 /* Digicipher II I-stream */ -#define ADV_MOD_DCII_Q_QPSK 6 /* Digicipher II Q-stream */ -#define ADV_MOD_DCII_C_OQPSK 7 /* Digicipher II offset QPSK */ -#define ADV_MOD_DSS_QPSK 8 /* DSS (DIRECTV) QPSK */ -#define ADV_MOD_DVB_BPSK 9 /* DVB-S BPSK */ - -#define GET_USB_SPEED 0x07 -#define USB_SPEED_LOW 0 -#define USB_SPEED_FULL 1 -#define USB_SPEED_HIGH 2 - -#define RESET_FX2 0x13 - -#define FW_VERSION_READ 0x0B -#define VENDOR_STRING_READ 0x0C -#define PRODUCT_STRING_READ 0x0D -#define FW_BCD_VERSION_READ 0x14 - -#define SEC_VOLTAGE_13 0 -#define SEC_VOLTAGE_18 1 - -#define SEC_TONE_ON 0 -#define SEC_TONE_OFF 1 - -#define SWITCH_ON_TUNER 1 -#define SWITCH_OFF_TUNER 0 -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Function Prototypes */ -NTSTATUS GetSignalStatus( IN PKSDEVICE pKSDeviceObject, - OUT PBOOLEAN pbSignalLockStatus - ); -NTSTATUS ReadTunerSignalStrength( IN PKSDEVICE pKSDeviceObject, - OUT PULONG pulSigStrength - ); -NTSTATUS SetLnbVoltage(IN PKSDEVICE pKSDeviceObject, - IN UCHAR ucVoltage); -NTSTATUS TuneDevice(IN PKSDEVICE pKSDeviceObject, - IN PBDATUNER_DEVICE_PARAMETER pDeviceParameter); -NTSTATUS SetupTunerPower( IN PKSDEVICE pKSDeviceObject, - IN BOOLEAN bOnOff); -NTSTATUS SetStreamingControl( IN PKSDEVICE pKSDeviceObject, - IN UCHAR ucOnOff); -NTSTATUS SetTunerTone( IN PKSDEVICE pKSDeviceObject, - IN UCHAR ucTone); -NTSTATUS ConfigureTuner(IN PKSDEVICE pKSDeviceObject, - IN PBDATUNER_DEVICE_PARAMETER pNewConfiguration); -NTSTATUS DiseqcCommand( IN PKSDEVICE pKSDeviceObject, - IN PDISEQC_COMMAND pCommand); -/* End of Function prototype definitions */ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1Control.cpp + Author : + Date : + Purpose : This File Holds the Device Control related declarations + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +#ifndef __SKYWALKER1_CONTROL_H +#define __SKYWALKER1_CONTROL_H + +/* Include the Library and Other header file */ +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ + +/* gp8psk commands */ + +/* Twinhan Vendor requests */ +#define TH_COMMAND_IN 0xC0 +#define TH_COMMAND_OUT 0xC1 + +/* gp8psk commands */ + +#define GET_8PSK_CONFIG 0x80 /* in */ +#define SET_8PSK_CONFIG 0x81 +#define I2C_WRITE 0x83 +#define I2C_READ 0x84 +#define ARM_TRANSFER 0x85 +#define TUNE_8PSK 0x86 +#define GET_SIGNAL_STRENGTH 0x87 /* in */ +#define LOAD_BCM4500 0x88 +#define BOOT_8PSK 0x89 /* in */ +#define START_INTERSIL 0x8A /* in */ +#define SET_LNB_VOLTAGE 0x8B +#define SET_22KHZ_TONE 0x8C +#define SEND_DISEQC_COMMAND 0x8D +#define SET_DVB_MODE 0x8E +#define SET_DN_SWITCH 0x8F +#define GET_SIGNAL_LOCK 0x90 /* in */ +#define GET_SERIAL_NUMBER 0x93 /* in */ +#define USE_EXTRA_VOLT 0x94 +#define CW3K_INIT 0x9d + +/* PSK_configuration bits */ +#define bm8pskStarted 0x01 +#define bm8pskFW_Loaded 0x02 +#define bmIntersilOn 0x04 +#define bmDVBmode 0x08 +#define bm22kHz 0x10 +#define bmSEL18V 0x20 +#define bmDCtuned 0x40 +#define bmArmed 0x80 + +/* Satellite modulation modes */ +#define ADV_MOD_DVB_QPSK 0 /* DVB-S QPSK */ +#define ADV_MOD_TURBO_QPSK 1 /* Turbo QPSK */ +#define ADV_MOD_TURBO_8PSK 2 /* Turbo 8PSK (also used for Trellis 8PSK) */ +#define ADV_MOD_TURBO_16QAM 3 /* Turbo 16QAM (also used for Trellis 8PSK) */ + +#define ADV_MOD_DCII_C_QPSK 4 /* Digicipher II Combo */ +#define ADV_MOD_DCII_I_QPSK 5 /* Digicipher II I-stream */ +#define ADV_MOD_DCII_Q_QPSK 6 /* Digicipher II Q-stream */ +#define ADV_MOD_DCII_C_OQPSK 7 /* Digicipher II offset QPSK */ +#define ADV_MOD_DSS_QPSK 8 /* DSS (DIRECTV) QPSK */ +#define ADV_MOD_DVB_BPSK 9 /* DVB-S BPSK */ + +#define GET_USB_SPEED 0x07 +#define USB_SPEED_LOW 0 +#define USB_SPEED_FULL 1 +#define USB_SPEED_HIGH 2 + +#define RESET_FX2 0x13 + +#define FW_VERSION_READ 0x0B +#define VENDOR_STRING_READ 0x0C +#define PRODUCT_STRING_READ 0x0D +#define FW_BCD_VERSION_READ 0x14 + +#define SEC_VOLTAGE_13 0 +#define SEC_VOLTAGE_18 1 + +#define SEC_TONE_ON 0 +#define SEC_TONE_OFF 1 + +#define SWITCH_ON_TUNER 1 +#define SWITCH_OFF_TUNER 0 +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Function Prototypes */ +NTSTATUS GetSignalStatus( IN PKSDEVICE pKSDeviceObject, + OUT PBOOLEAN pbSignalLockStatus + ); +NTSTATUS ReadTunerSignalStrength( IN PKSDEVICE pKSDeviceObject, + OUT PULONG pulSigStrength + ); +NTSTATUS SetLnbVoltage(IN PKSDEVICE pKSDeviceObject, + IN UCHAR ucVoltage); +NTSTATUS TuneDevice(IN PKSDEVICE pKSDeviceObject, + IN PBDATUNER_DEVICE_PARAMETER pDeviceParameter); +NTSTATUS SetupTunerPower( IN PKSDEVICE pKSDeviceObject, + IN BOOLEAN bOnOff); +NTSTATUS SetStreamingControl( IN PKSDEVICE pKSDeviceObject, + IN UCHAR ucOnOff); +NTSTATUS SetTunerTone( IN PKSDEVICE pKSDeviceObject, + IN UCHAR ucTone); +NTSTATUS ConfigureTuner(IN PKSDEVICE pKSDeviceObject, + IN PBDATUNER_DEVICE_PARAMETER pNewConfiguration); +NTSTATUS DiseqcCommand( IN PKSDEVICE pKSDeviceObject, + IN PDISEQC_COMMAND pCommand); +/* End of Function prototype definitions */ + #endif /*__SKYWALKER1_CONTROL_H*/ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Device.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Device.h index e53f9f7..3d1a67e 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Device.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Device.h @@ -1,187 +1,187 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1Device.h - Author : - Date : - Purpose : Main Skywalker Device level Implementation - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - 01 Initial Version - -*****************************************************************************/ - -#ifndef SKYWALKER1_TUNER_DEVICE_H -#define SKYWALKER1_TUNER_DEVICE_H - -/* Include the Library and Other header file */ -#include "SkyWalker1USB.h" -#include "SkyWalker1Extended.h" -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ - -//The SkyWalker1 Device class. -class CSkyWalker1Device -{ -public: - - //Device Initialization and Dispatch Related definitions - NTSTATUS Create(IN PKSDEVICE pKSDeviceObject); - NTSTATUS Start( - IN PKSDEVICE pKSDeviceObject, - IN PIRP pIrp, - IN PCM_RESOURCE_LIST pResourceList OPTIONAL, - IN PCM_RESOURCE_LIST pTranslatedResourceList OPTIONAL - ); - - NTSTATUS Stop( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); - - NTSTATUS Close( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); - NTSTATUS SetPower( - IN PKSDEVICE pKSDeviceObject, //Pointer to the device object - //provided by the system. - IN PIRP pIoRequestPacket,//Pointer to the IRP related to this request. - IN DEVICE_POWER_STATE To, //Requested power state. - IN DEVICE_POWER_STATE From //Current power state. - ); - - NTSTATUS InitializeTuner( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); - - // Device access related Function definitions - // An instance of the filter uses these functions - // to manage resources on the device. - - NTSTATUS Acquire(IN PBDATUNER_DEVICE_PARAMETER pNewResource, - OUT PULONG pulAcquiredResourceID); - - NTSTATUS Update( IN PBDATUNER_DEVICE_PARAMETER pNewResource, - IN ULONG ulResourceID); - - NTSTATUS SendDiseqcCommand( - IN PDISEQC_COMMAND pDiseqcCommand, - IN ULONG ulResourceID - ); - - NTSTATUS Release(IN ULONG ulResourceID); - - NTSTATUS GetStatus(PBDATUNER_DEVICE_STATUS pDeviceStatus); - - //DMA Adapter related Functions - NTSTATUS InitializeAdapterStream(IN PKSDEVICE pKSDeviceObject); - - //Stream Capture Related Fuctions - NTSTATUS SetupCaptureSink ( - IN ICaptureSink * pCapturePin, - IN PBDA_TRANSPORT_INFO TransportInfo - ); - void RemoveCaptureSink (); - - NTSTATUS StartStream (); - NTSTATUS PauseStream (IN BOOLEAN Pausing); - NTSTATUS StopStream (); - NTSTATUS ReadStream(IN ULONG ulStreamIndex); - void ProcessStream(IN ULONG ulStreamIndex); - - BOOLEAN TimeToReadSignalStatus(void); - - PKSDEVICE m_pKSDevice; - - //USB Related definitions - USB_DEVICE_DESCRIPTOR USBDeviceDescriptor; - USBD_PIPE_INFORMATION ReadPipe; - USBD_PIPE_INFORMATION WritePipe; - USBSTATE UsbDeviceState; - USBSTATE PreviousUsbDeviceState; - //Pending I/O queue state - QUEUE_STATE QueueState; - - //obtain and hold this lock while changing the device state, - //the queue state and while processing the queue. - KSPIN_LOCK DeviceStateLock; - //Current Usb Irp - PIRP pUsbStreamIrp[PACKET_PER_FRAME * NUMBER_OF_FRAMES]; - //Device Stop Event - KEVENT EvDeviceStopOk; - //Device Remove Event - KEVENT EvDeviceRemoveOk; - //Outstanding IO Count for the Driver - ULONG ulOutStandingIoCount; - //Outstanding IO Count Lock - KSPIN_LOCK kIoCountLock; - - PDMA_ADAPTER m_pDMAAdapter; - - ULONG m_SampleSize; - - //Temporary Bytes Read Counter - ULONG m_NumberOfBytesRead[NUMBER_OF_FRAMES]; - - PUCHAR GetSynthBuffer(ULONG ulStreamIndex) - { - return m_SynthesisBuffer[ulStreamIndex]; - } - -private: - - ULONG m_ulDeviceInstance; - BDATUNER_DEVICE_PARAMETER m_CurResource; - BDATUNER_DEVICE_STATUS m_TunerStatus; - ULONG m_ulCurResourceID; - ULONG m_ulcResourceUsers; - LARGE_INTEGER m_PreviousStatusReadTime; - - //The synthesis buffer. This is a private buffer we use to store the - //references of the Streaming Data.It is used for the Reading data - //from the device and also Copying Data to the Stream Buffer sent - //by the application - PUCHAR m_SynthesisBuffer[NUMBER_OF_FRAMES]; - - //Key information regarding the frames we generate. - LONGLONG m_TimePerFrame; - ULONG m_PacketSize; - ULONG m_PacketsPerSample; - - //The current state of the Device - HARDWARE_STATE m_HardwareState; - - //The pause / stop hardware flag and event. - BOOLEAN m_StopHardware; - KEVENT m_HardwareEvent; - - //Number of pins with resources acquired. This is used as a locking - //mechanism for resource acquisition on the device. - LONG m_PinsWithResources; - - //The capture sink. When we complete stream reading, we - //notify the capture sink. - ICaptureSink *m_CaptureSink; - - //The video info header we're basing hardware settings on. The pin - //provides this to us when acquiring resources and must guarantee its - //stability until resources are released. - PBDA_TRANSPORT_INFO m_TransportInfo; -}; - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - -#endif /*SKYWALKER1_TUNER_DEVICE_H*/ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1Device.h + Author : + Date : + Purpose : Main Skywalker Device level Implementation + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + 01 Initial Version + +*****************************************************************************/ + +#ifndef SKYWALKER1_TUNER_DEVICE_H +#define SKYWALKER1_TUNER_DEVICE_H + +/* Include the Library and Other header file */ +#include "SkyWalker1USB.h" +#include "SkyWalker1Extended.h" +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ + +//The SkyWalker1 Device class. +class CSkyWalker1Device +{ +public: + + //Device Initialization and Dispatch Related definitions + NTSTATUS Create(IN PKSDEVICE pKSDeviceObject); + NTSTATUS Start( + IN PKSDEVICE pKSDeviceObject, + IN PIRP pIrp, + IN PCM_RESOURCE_LIST pResourceList OPTIONAL, + IN PCM_RESOURCE_LIST pTranslatedResourceList OPTIONAL + ); + + NTSTATUS Stop( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); + + NTSTATUS Close( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); + NTSTATUS SetPower( + IN PKSDEVICE pKSDeviceObject, //Pointer to the device object + //provided by the system. + IN PIRP pIoRequestPacket,//Pointer to the IRP related to this request. + IN DEVICE_POWER_STATE To, //Requested power state. + IN DEVICE_POWER_STATE From //Current power state. + ); + + NTSTATUS InitializeTuner( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); + + // Device access related Function definitions + // An instance of the filter uses these functions + // to manage resources on the device. + + NTSTATUS Acquire(IN PBDATUNER_DEVICE_PARAMETER pNewResource, + OUT PULONG pulAcquiredResourceID); + + NTSTATUS Update( IN PBDATUNER_DEVICE_PARAMETER pNewResource, + IN ULONG ulResourceID); + + NTSTATUS SendDiseqcCommand( + IN PDISEQC_COMMAND pDiseqcCommand, + IN ULONG ulResourceID + ); + + NTSTATUS Release(IN ULONG ulResourceID); + + NTSTATUS GetStatus(PBDATUNER_DEVICE_STATUS pDeviceStatus); + + //DMA Adapter related Functions + NTSTATUS InitializeAdapterStream(IN PKSDEVICE pKSDeviceObject); + + //Stream Capture Related Fuctions + NTSTATUS SetupCaptureSink ( + IN ICaptureSink * pCapturePin, + IN PBDA_TRANSPORT_INFO TransportInfo + ); + void RemoveCaptureSink (); + + NTSTATUS StartStream (); + NTSTATUS PauseStream (IN BOOLEAN Pausing); + NTSTATUS StopStream (); + NTSTATUS ReadStream(IN ULONG ulStreamIndex); + void ProcessStream(IN ULONG ulStreamIndex); + + BOOLEAN TimeToReadSignalStatus(void); + + PKSDEVICE m_pKSDevice; + + //USB Related definitions + USB_DEVICE_DESCRIPTOR USBDeviceDescriptor; + USBD_PIPE_INFORMATION ReadPipe; + USBD_PIPE_INFORMATION WritePipe; + USBSTATE UsbDeviceState; + USBSTATE PreviousUsbDeviceState; + //Pending I/O queue state + QUEUE_STATE QueueState; + + //obtain and hold this lock while changing the device state, + //the queue state and while processing the queue. + KSPIN_LOCK DeviceStateLock; + //Current Usb Irp + PIRP pUsbStreamIrp[PACKET_PER_FRAME * NUMBER_OF_FRAMES]; + //Device Stop Event + KEVENT EvDeviceStopOk; + //Device Remove Event + KEVENT EvDeviceRemoveOk; + //Outstanding IO Count for the Driver + ULONG ulOutStandingIoCount; + //Outstanding IO Count Lock + KSPIN_LOCK kIoCountLock; + + PDMA_ADAPTER m_pDMAAdapter; + + ULONG m_SampleSize; + + //Temporary Bytes Read Counter + ULONG m_NumberOfBytesRead[NUMBER_OF_FRAMES]; + + PUCHAR GetSynthBuffer(ULONG ulStreamIndex) + { + return m_SynthesisBuffer[ulStreamIndex]; + } + +private: + + ULONG m_ulDeviceInstance; + BDATUNER_DEVICE_PARAMETER m_CurResource; + BDATUNER_DEVICE_STATUS m_TunerStatus; + ULONG m_ulCurResourceID; + ULONG m_ulcResourceUsers; + LARGE_INTEGER m_PreviousStatusReadTime; + + //The synthesis buffer. This is a private buffer we use to store the + //references of the Streaming Data.It is used for the Reading data + //from the device and also Copying Data to the Stream Buffer sent + //by the application + PUCHAR m_SynthesisBuffer[NUMBER_OF_FRAMES]; + + //Key information regarding the frames we generate. + LONGLONG m_TimePerFrame; + ULONG m_PacketSize; + ULONG m_PacketsPerSample; + + //The current state of the Device + HARDWARE_STATE m_HardwareState; + + //The pause / stop hardware flag and event. + BOOLEAN m_StopHardware; + KEVENT m_HardwareEvent; + + //Number of pins with resources acquired. This is used as a locking + //mechanism for resource acquisition on the device. + LONG m_PinsWithResources; + + //The capture sink. When we complete stream reading, we + //notify the capture sink. + ICaptureSink *m_CaptureSink; + + //The video info header we're basing hardware settings on. The pin + //provides this to us when acquiring resources and must guarantee its + //stability until resources are released. + PBDA_TRANSPORT_INFO m_TransportInfo; +}; + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + +#endif /*SKYWALKER1_TUNER_DEVICE_H*/ + diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Extended.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Extended.h index 047d686..7c16ee8 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Extended.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Extended.h @@ -1,109 +1,109 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1AntennaPin.h - Author : - Date : - Purpose : This File Holds the Extended BDA Definitions - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -#ifndef __SKYWALKER1_EXTENDED_H -#define __SKYWALKER1_EXTENDED_H - -/* Include the Library and Other header file */ -#if !defined(_KSMEDIA_) -#error KSMEDIA.H must be included before BDAMEDIA.H -#endif // !defined(_KSMEDIA_) - -#if !defined(_BDATYPES_) -#error BDATYPES.H must be included before BDAMEDIA.H -#endif // !defined(_BDATYPES_) - -#if !defined(_BDAMEDIA_) -#define _BDAMEDIA_ -#endif // !defined(_BDAMEDIA_) - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ - -#define MAX_DISEQC_COMMAND_LENGTH 6 - -/* Extended Property*/ - -//Used to extend the feature of the BDA -//{0B5221EB-F4C4-4976-B959-EF74427464D9} -#define STATIC_KSPROPSETID_BdaExtendedProperty \ - 0x0B5221EB, 0xF4C4, 0x4976, 0xB9, 0x59, 0xEF, 0x74, 0x42, 0x74, 0x64, 0xD9 -DEFINE_GUIDSTRUCT("0B5221EB-F4C4-4976-B959-EF74427464D9", KSPROPSETID_BdaExtendedProperty); -#define KSPROPSETID_BdaExtendedProperty DEFINE_GUIDNAMED(KSPROPSETID_BdaExtendedProperty) - -/* End of Macro Definitions */ - -/* Declare Enumerations here */ -//Extended Property List -typedef enum __KSPROPERTY_EXTENDED -{ - /* DiSEqC Command */ - //Used to send the Digital Sattelite Equipment Control (DiSEqC) - //Commands by application - KSPROPERTY_BDA_DISEQC = 0, //Extension Property 1 - - //Add New Extended Commands Here - -}KSPROPERTY_EXTENDED; - -//Enumeration can be used during Simple Tone Burst Command -typedef enum enSimpleToneBurst -{ - SEC_MINI_A, - SEC_MINI_B -}SIMPLE_TONE_BURST; - -/* End of Enumeration declaration */ - -/* Global & Static variables Declaration */ - -//DiSEqC Command related Structure definitions -typedef struct __DISEQC_COMMAND -{ - UCHAR ucMessage[MAX_DISEQC_COMMAND_LENGTH]; /* - Byte - 0 : Framing, - Byte - 1 : Address, - Byte - 2 : Command, - Byte - 3 : Data[0], - Byte - 4 : Data[1], - Byte - 5 : Data[2] - */ - UCHAR ucMessageLength;/* The Valid values for DiSEqC Command are 3...6 - If this value is 1 then the Byte 0 is taken as Simple "Tone Burst" Control - Command */ - -}DISEQC_COMMAND,*PDISEQC_COMMAND; - -//Property that will be used to send the Diseqc command by the application -#define DEFINE_KSPROPERTY_ITEM_BDA_DISEQC(GetHandler, SetHandler)\ - DEFINE_KSPROPERTY_ITEM(\ - KSPROPERTY_BDA_DISEQC,\ - (GetHandler),\ - sizeof(KSP_NODE),\ - sizeof(DISEQC_COMMAND),\ - (SetHandler),\ - NULL, 0, NULL, NULL, 0) - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1AntennaPin.h + Author : + Date : + Purpose : This File Holds the Extended BDA Definitions + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +#ifndef __SKYWALKER1_EXTENDED_H +#define __SKYWALKER1_EXTENDED_H + +/* Include the Library and Other header file */ +#if !defined(_KSMEDIA_) +#error KSMEDIA.H must be included before BDAMEDIA.H +#endif // !defined(_KSMEDIA_) + +#if !defined(_BDATYPES_) +#error BDATYPES.H must be included before BDAMEDIA.H +#endif // !defined(_BDATYPES_) + +#if !defined(_BDAMEDIA_) +#define _BDAMEDIA_ +#endif // !defined(_BDAMEDIA_) + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ + +#define MAX_DISEQC_COMMAND_LENGTH 6 + +/* Extended Property*/ + +//Used to extend the feature of the BDA +//{0B5221EB-F4C4-4976-B959-EF74427464D9} +#define STATIC_KSPROPSETID_BdaExtendedProperty \ + 0x0B5221EB, 0xF4C4, 0x4976, 0xB9, 0x59, 0xEF, 0x74, 0x42, 0x74, 0x64, 0xD9 +DEFINE_GUIDSTRUCT("0B5221EB-F4C4-4976-B959-EF74427464D9", KSPROPSETID_BdaExtendedProperty); +#define KSPROPSETID_BdaExtendedProperty DEFINE_GUIDNAMED(KSPROPSETID_BdaExtendedProperty) + +/* End of Macro Definitions */ + +/* Declare Enumerations here */ +//Extended Property List +typedef enum __KSPROPERTY_EXTENDED +{ + /* DiSEqC Command */ + //Used to send the Digital Sattelite Equipment Control (DiSEqC) + //Commands by application + KSPROPERTY_BDA_DISEQC = 0, //Extension Property 1 + + //Add New Extended Commands Here + +}KSPROPERTY_EXTENDED; + +//Enumeration can be used during Simple Tone Burst Command +typedef enum enSimpleToneBurst +{ + SEC_MINI_A, + SEC_MINI_B +}SIMPLE_TONE_BURST; + +/* End of Enumeration declaration */ + +/* Global & Static variables Declaration */ + +//DiSEqC Command related Structure definitions +typedef struct __DISEQC_COMMAND +{ + UCHAR ucMessage[MAX_DISEQC_COMMAND_LENGTH]; /* + Byte - 0 : Framing, + Byte - 1 : Address, + Byte - 2 : Command, + Byte - 3 : Data[0], + Byte - 4 : Data[1], + Byte - 5 : Data[2] + */ + UCHAR ucMessageLength;/* The Valid values for DiSEqC Command are 3...6 + If this value is 1 then the Byte 0 is taken as Simple "Tone Burst" Control + Command */ + +}DISEQC_COMMAND,*PDISEQC_COMMAND; + +//Property that will be used to send the Diseqc command by the application +#define DEFINE_KSPROPERTY_ITEM_BDA_DISEQC(GetHandler, SetHandler)\ + DEFINE_KSPROPERTY_ITEM(\ + KSPROPERTY_BDA_DISEQC,\ + (GetHandler),\ + sizeof(KSP_NODE),\ + sizeof(DISEQC_COMMAND),\ + (SetHandler),\ + NULL, 0, NULL, NULL, 0) + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + #endif /*__SKYWALKER1_EXTENDED_H*/ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Main.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Main.h index a072e07..7f2c8e0 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Main.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Main.h @@ -1,69 +1,69 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1Main.h - Author : - Date : - Purpose : Entry point for the Driver - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -#ifndef __SKYWALKER1_MAIN_H -#define __SKYWALKER1_MAIN_H - -/* Include the Library and Other header file */ -#include -#include -#include //Kernel Streaming Driver -#include -#include -#include -#include -#include - -#include -#include -#include "SkyWalker1AntennaPin.h" -#include "SkyWalker1CaptureFilter.h" -#include "SkyWalker1CapturePin.h" -#include "SkyWalker1CommonDef.h" -#include "SkyWalker1Control.h" -#include "SkyWalker1Device.h" -#include "SkyWalker1TransportPin.h" -#include "SkyWalker1TunerFilter.h" -#include "SkyWalker1TunerPin.h" -#include "SkyWalker1Extended.h" - -extern "C" -{ -#include //For the Mutex -#include //For the WCHAR and swprintf -#include //For sprintf() function - -#include "SkyWalker1PnP.h" //Header for the PnP related definitions -#include "SkyWalker1USB.h" -#include "SkyWalker1Utility.h" - -} - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1Main.h + Author : + Date : + Purpose : Entry point for the Driver + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +#ifndef __SKYWALKER1_MAIN_H +#define __SKYWALKER1_MAIN_H + +/* Include the Library and Other header file */ +#include +#include +#include //Kernel Streaming Driver +#include +#include +#include +#include +#include + +#include +#include +#include "SkyWalker1AntennaPin.h" +#include "SkyWalker1CaptureFilter.h" +#include "SkyWalker1CapturePin.h" +#include "SkyWalker1CommonDef.h" +#include "SkyWalker1Control.h" +#include "SkyWalker1Device.h" +#include "SkyWalker1TransportPin.h" +#include "SkyWalker1TunerFilter.h" +#include "SkyWalker1TunerPin.h" +#include "SkyWalker1Extended.h" + +extern "C" +{ +#include //For the Mutex +#include //For the WCHAR and swprintf +#include //For sprintf() function + +#include "SkyWalker1PnP.h" //Header for the PnP related definitions +#include "SkyWalker1USB.h" +#include "SkyWalker1Utility.h" + +} + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + #endif /*__SKYWALKER1_MAIN_H*/ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1PnP.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1PnP.h index 1b54c69..44e7851 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1PnP.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1PnP.h @@ -1,56 +1,56 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1PnP.h - Author : - Date : - Purpose : PnP IRP Message Handler - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -#ifndef __SKYWALKER1_PNP_H -#define __SKYWALKER1_PNP_H - -/* Include the Library and Other header file */ -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -NTSTATUS SkyWalker1AddDevice(IN PKSDEVICE pKSDeviceObject); -VOID SkyWalker1Remove( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); -NTSTATUS SkyWalker1Start(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket, - IN PCM_RESOURCE_LIST pResourceList, - IN PCM_RESOURCE_LIST pCIResourceListTranslated); -VOID SkyWalker1Stop(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); -NTSTATUS SkyWalker1QueryStop( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); -VOID SkyWalker1SetPower -( - IN PKSDEVICE pKSDeviceObject, //Pointer to the device object - //provided by the system. - IN PIRP pIoRequestPacket, //Pointer to the IRP related to this request. - IN DEVICE_POWER_STATE To, //Requested power state. - IN DEVICE_POWER_STATE From //Current power state. -); -/* End of Function prototype definitions */ - -#endif // __SKYWALKER1_PNP_H +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1PnP.h + Author : + Date : + Purpose : PnP IRP Message Handler + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +#ifndef __SKYWALKER1_PNP_H +#define __SKYWALKER1_PNP_H + +/* Include the Library and Other header file */ +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +NTSTATUS SkyWalker1AddDevice(IN PKSDEVICE pKSDeviceObject); +VOID SkyWalker1Remove( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); +NTSTATUS SkyWalker1Start(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket, + IN PCM_RESOURCE_LIST pResourceList, + IN PCM_RESOURCE_LIST pCIResourceListTranslated); +VOID SkyWalker1Stop(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); +NTSTATUS SkyWalker1QueryStop( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); +VOID SkyWalker1SetPower +( + IN PKSDEVICE pKSDeviceObject, //Pointer to the device object + //provided by the system. + IN PIRP pIoRequestPacket, //Pointer to the IRP related to this request. + IN DEVICE_POWER_STATE To, //Requested power state. + IN DEVICE_POWER_STATE From //Current power state. +); +/* End of Function prototype definitions */ + +#endif // __SKYWALKER1_PNP_H diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TransportPin.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TransportPin.h index 5a4ab97..37d6599 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TransportPin.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TransportPin.h @@ -1,84 +1,84 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1TransportPin.h - Author : - Date : - Purpose : This file contains header for the Transport pin on the Tuner - filter. - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -#ifndef __SKYWALKER1_TRANSPORT_PIN_H -#define __SKYWALKER1_TRANSPORT_PIN_H - -/* Include the Library and Other header file */ -//#include "SkyWalker1CommonDef.h" -#include "SkyWalker1TunerPin.h" -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Global & Static variables Declaration */ - -//The Trasport Pin class. -class CTransportPin : public CTunerPin -{ -public: - - - //Define a data intersection handler function for the - //pin (KSPIN_DESCRIPTOR_EX structure). - //Network provider and AVStream use this function - //to connect the output pin with a downstream filter. - static NTSTATUS IntersectDataFormat( - IN PVOID pContext, - IN PIRP pIoRequestPacket, - IN PKSP_PIN Pin, - IN PKSDATARANGE DataRange, - IN PKSDATARANGE MatchingDataRange, - IN ULONG DataBufferSize, - OUT PVOID Data OPTIONAL, - OUT PULONG DataSize - ); - - static NTSTATUS GetDigitalDemodProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ); - - static NTSTATUS SetDigitalDemodProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ); - - //Function to send the Extended BDA Commands to the Tuner - static NTSTATUS SetExtendedProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ); -}; - - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1TransportPin.h + Author : + Date : + Purpose : This file contains header for the Transport pin on the Tuner + filter. + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +#ifndef __SKYWALKER1_TRANSPORT_PIN_H +#define __SKYWALKER1_TRANSPORT_PIN_H + +/* Include the Library and Other header file */ +//#include "SkyWalker1CommonDef.h" +#include "SkyWalker1TunerPin.h" +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Global & Static variables Declaration */ + +//The Trasport Pin class. +class CTransportPin : public CTunerPin +{ +public: + + + //Define a data intersection handler function for the + //pin (KSPIN_DESCRIPTOR_EX structure). + //Network provider and AVStream use this function + //to connect the output pin with a downstream filter. + static NTSTATUS IntersectDataFormat( + IN PVOID pContext, + IN PIRP pIoRequestPacket, + IN PKSP_PIN Pin, + IN PKSDATARANGE DataRange, + IN PKSDATARANGE MatchingDataRange, + IN ULONG DataBufferSize, + OUT PVOID Data OPTIONAL, + OUT PULONG DataSize + ); + + static NTSTATUS GetDigitalDemodProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ); + + static NTSTATUS SetDigitalDemodProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ); + + //Function to send the Extended BDA Commands to the Tuner + static NTSTATUS SetExtendedProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ); +}; + + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + #endif /*__SKYWALKER1_TRANSPORT_PIN_H*/ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TunerFilter.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TunerFilter.h index 2993d68..dd7c026 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TunerFilter.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TunerFilter.h @@ -1,140 +1,140 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1TunerFilter.h - Author : - Date : - Purpose : Tuner Filter Class Definition - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -#ifndef SKYWALKER1_TUNER_FILTER_H -#define SKYWALKER1_TUNER_FILTER_H - -/* Include the Library and Other header file */ -#include "SkyWalker1CommonDef.h" -#include "SkyWalker1Device.h" -#include "SkyWalker1Extended.h" -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Global & Static variables Declaration */ - -//The Tuner Filter class. -class CTunerFilter -{ -public: - - //Functions to Create and Close Filter - static NTSTATUS Create( IN OUT PKSFILTER pKSFilter, - IN PIRP pIoRequestPacket); - - static NTSTATUS FilterClose( IN OUT PKSFILTER pKSFilter, - IN PIRP pIoRequestPacket); - - //KSMETHODSETID_BdaChangeSync pKSFilter change sync methods - static NTSTATUS StartChanges( IN PIRP pIoRequestPacket, - IN PKSMETHOD pKSMethod, - OPTIONAL PVOID pvIgnored); - - static NTSTATUS CheckChanges( IN PIRP pIoRequestPacket, - IN PKSMETHOD pKSMethod, - OPTIONAL PVOID pvIgnored); - - static NTSTATUS CommitChanges( IN PIRP pIoRequestPacket, - IN PKSMETHOD pKSMethod, - OPTIONAL PVOID pvIgnored); - - static NTSTATUS GetChangeState( IN PIRP pIoRequestPacket, - IN PKSMETHOD pKSMethod, - OUT PULONG pulChangeState); - - - //KSMETHODSETID_BdaDeviceConfiguration Method to modify filter topology. - static NTSTATUS CreateTopology( IN PIRP pIoRequestPacket, - IN PKSMETHOD pKSMethod, - OPTIONAL PVOID pvIgnored); - - - //pKSFilter Implementation Methods - (class CSkyWalker1Device *) GetDevice() - { - return m_pDevice; - }; - - NTSTATUS SetDeviceState(KSSTATE NewKsState) - { - m_KsState = NewKsState; - return STATUS_SUCCESS; - }; - - - //Tuner Node Properties - NTSTATUS GetTunerProperty(OUT PBDATUNER_DEVICE_PARAMETER pTunerParameter); - NTSTATUS SetFrequency(IN ULONG ulBdaParameter); - NTSTATUS SetMultiplier(IN ULONG ulBdaParameter); - NTSTATUS SetBandwidth(IN ULONG ulBdaParameter); - NTSTATUS SetPolarity(IN Polarisation NewPolarity); - NTSTATUS SetRange(IN ULONG ulBdaParameter); - NTSTATUS SetTransponder(IN ULONG ulBdaParameter); - NTSTATUS SetLowLOFrequency(IN ULONG ulBdaParameter); - NTSTATUS SetHighLOFrequency(IN ULONG ulBdaParameter); - NTSTATUS SetSwitchFrequency(IN ULONG ulBdaParameter); - - //Demodulator Node Properties - NTSTATUS GetDemodProperty(OUT PBDATUNER_DEVICE_PARAMETER pDemodParameter); - NTSTATUS SetModulatorType(IN ModulationType NewModulatorType); - NTSTATUS SetInnerFecType(IN ULONG ulNewInnerFecType); - NTSTATUS SetInnerFecRate(IN BinaryConvolutionCodeRate NewFecRate); - NTSTATUS SetOuterFecType(IN ULONG ulNewOuterFecType); - NTSTATUS SetOuterFecRate(IN BinaryConvolutionCodeRate NewFecRate); - NTSTATUS SetSymbolRate(IN ULONG ulNewSymbolRate); - NTSTATUS SetSpectralInversion(IN SpectralInversion SpecInv); - NTSTATUS SetGuardInterval(IN GuardInterval GuardInt); - NTSTATUS SetTransmissionMode(IN TransmissionMode TransMode); - - //Extended Property - NTSTATUS SendDiseqcCommand(IN PDISEQC_COMMAND pDiseqcCommand); - - //Function to retrive the Device Status. - NTSTATUS GetStatus(PBDATUNER_DEVICE_STATUS pDeviceStatus); - - //Functions to get and release device access - NTSTATUS AcquireResources(); - NTSTATUS ReleaseResources(); - - -private: - class CSkyWalker1Device * m_pDevice; - - //Filter Resources - KSSTATE m_KsState; - BDA_CHANGE_STATE m_BdaChangeState; - BDATUNER_DEVICE_PARAMETER m_CurResource; - BDATUNER_DEVICE_PARAMETER m_NewResource; - ULONG m_ulResourceID; - BOOLEAN m_fResourceAcquired; - CTunerFilter(); - ~CTunerFilter(); -}; - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1TunerFilter.h + Author : + Date : + Purpose : Tuner Filter Class Definition + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +#ifndef SKYWALKER1_TUNER_FILTER_H +#define SKYWALKER1_TUNER_FILTER_H + +/* Include the Library and Other header file */ +#include "SkyWalker1CommonDef.h" +#include "SkyWalker1Device.h" +#include "SkyWalker1Extended.h" +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Global & Static variables Declaration */ + +//The Tuner Filter class. +class CTunerFilter +{ +public: + + //Functions to Create and Close Filter + static NTSTATUS Create( IN OUT PKSFILTER pKSFilter, + IN PIRP pIoRequestPacket); + + static NTSTATUS FilterClose( IN OUT PKSFILTER pKSFilter, + IN PIRP pIoRequestPacket); + + //KSMETHODSETID_BdaChangeSync pKSFilter change sync methods + static NTSTATUS StartChanges( IN PIRP pIoRequestPacket, + IN PKSMETHOD pKSMethod, + OPTIONAL PVOID pvIgnored); + + static NTSTATUS CheckChanges( IN PIRP pIoRequestPacket, + IN PKSMETHOD pKSMethod, + OPTIONAL PVOID pvIgnored); + + static NTSTATUS CommitChanges( IN PIRP pIoRequestPacket, + IN PKSMETHOD pKSMethod, + OPTIONAL PVOID pvIgnored); + + static NTSTATUS GetChangeState( IN PIRP pIoRequestPacket, + IN PKSMETHOD pKSMethod, + OUT PULONG pulChangeState); + + + //KSMETHODSETID_BdaDeviceConfiguration Method to modify filter topology. + static NTSTATUS CreateTopology( IN PIRP pIoRequestPacket, + IN PKSMETHOD pKSMethod, + OPTIONAL PVOID pvIgnored); + + + //pKSFilter Implementation Methods + (class CSkyWalker1Device *) GetDevice() + { + return m_pDevice; + }; + + NTSTATUS SetDeviceState(KSSTATE NewKsState) + { + m_KsState = NewKsState; + return STATUS_SUCCESS; + }; + + + //Tuner Node Properties + NTSTATUS GetTunerProperty(OUT PBDATUNER_DEVICE_PARAMETER pTunerParameter); + NTSTATUS SetFrequency(IN ULONG ulBdaParameter); + NTSTATUS SetMultiplier(IN ULONG ulBdaParameter); + NTSTATUS SetBandwidth(IN ULONG ulBdaParameter); + NTSTATUS SetPolarity(IN Polarisation NewPolarity); + NTSTATUS SetRange(IN ULONG ulBdaParameter); + NTSTATUS SetTransponder(IN ULONG ulBdaParameter); + NTSTATUS SetLowLOFrequency(IN ULONG ulBdaParameter); + NTSTATUS SetHighLOFrequency(IN ULONG ulBdaParameter); + NTSTATUS SetSwitchFrequency(IN ULONG ulBdaParameter); + + //Demodulator Node Properties + NTSTATUS GetDemodProperty(OUT PBDATUNER_DEVICE_PARAMETER pDemodParameter); + NTSTATUS SetModulatorType(IN ModulationType NewModulatorType); + NTSTATUS SetInnerFecType(IN ULONG ulNewInnerFecType); + NTSTATUS SetInnerFecRate(IN BinaryConvolutionCodeRate NewFecRate); + NTSTATUS SetOuterFecType(IN ULONG ulNewOuterFecType); + NTSTATUS SetOuterFecRate(IN BinaryConvolutionCodeRate NewFecRate); + NTSTATUS SetSymbolRate(IN ULONG ulNewSymbolRate); + NTSTATUS SetSpectralInversion(IN SpectralInversion SpecInv); + NTSTATUS SetGuardInterval(IN GuardInterval GuardInt); + NTSTATUS SetTransmissionMode(IN TransmissionMode TransMode); + + //Extended Property + NTSTATUS SendDiseqcCommand(IN PDISEQC_COMMAND pDiseqcCommand); + + //Function to retrive the Device Status. + NTSTATUS GetStatus(PBDATUNER_DEVICE_STATUS pDeviceStatus); + + //Functions to get and release device access + NTSTATUS AcquireResources(); + NTSTATUS ReleaseResources(); + + +private: + class CSkyWalker1Device * m_pDevice; + + //Filter Resources + KSSTATE m_KsState; + BDA_CHANGE_STATE m_BdaChangeState; + BDATUNER_DEVICE_PARAMETER m_CurResource; + BDATUNER_DEVICE_PARAMETER m_NewResource; + ULONG m_ulResourceID; + BOOLEAN m_fResourceAcquired; + CTunerFilter(); + ~CTunerFilter(); +}; + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + #endif /*SKYWALKER1_TUNER_FILTER_H*/ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TunerPin.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TunerPin.h index b815d67..1f19b3d 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TunerPin.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1TunerPin.h @@ -1,82 +1,82 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1TunerPin.h - Author : - Date : - Purpose : This File Holds the Generic Tuner Pin related declarations - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -#ifndef __SKYWALKER1_TUNER_PIN_H -#define __SKYWALKER1_TUNER_PIN_H - -/* Include the Library and Other header file */ -#include "SkyWalker1CommonDef.h" -#include "SkyWalker1TunerFilter.h" -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Global & Static variables Declaration */ - -// The Tuner Pin class. -class CTunerPin -{ -public: - - - //Creates the Pin - static NTSTATUS PinCreate( IN OUT PKSPIN pKSPin, - IN PIRP pIoRequestPacket - ); - - //Closes the Opened Pin - static NTSTATUS PinClose( IN OUT PKSPIN pKSPin, - IN PIRP pIoRequestPacket - ); - - //Member Function to get the Various Signal attributes - static NTSTATUS GetSignalStatus( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ); - - - (class CTunerFilter *) GetFilter(void) - { - return m_pFilter; - }; - - void SetFilter(class CTunerFilter * pFilter) - { - m_pFilter = pFilter; - }; - -protected: - - class CTunerFilter* m_pFilter; - -}; - - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1TunerPin.h + Author : + Date : + Purpose : This File Holds the Generic Tuner Pin related declarations + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +#ifndef __SKYWALKER1_TUNER_PIN_H +#define __SKYWALKER1_TUNER_PIN_H + +/* Include the Library and Other header file */ +#include "SkyWalker1CommonDef.h" +#include "SkyWalker1TunerFilter.h" +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Global & Static variables Declaration */ + +// The Tuner Pin class. +class CTunerPin +{ +public: + + + //Creates the Pin + static NTSTATUS PinCreate( IN OUT PKSPIN pKSPin, + IN PIRP pIoRequestPacket + ); + + //Closes the Opened Pin + static NTSTATUS PinClose( IN OUT PKSPIN pKSPin, + IN PIRP pIoRequestPacket + ); + + //Member Function to get the Various Signal attributes + static NTSTATUS GetSignalStatus( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ); + + + (class CTunerFilter *) GetFilter(void) + { + return m_pFilter; + }; + + void SetFilter(class CTunerFilter * pFilter) + { + m_pFilter = pFilter; + }; + +protected: + + class CTunerFilter* m_pFilter; + +}; + + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + #endif /*__SKYWALKER1_TUNER_PIN_H*/ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1USB.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1USB.h index 1f41a30..1dd2ea8 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1USB.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1USB.h @@ -1,126 +1,126 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1Usb.h - Author : - Date : - Purpose : This File Holds all the USB Device access related declarations - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -#ifndef __SKYWALKER1_USB_H -#define __SKYWALKER1_USB_H - -/* Include the Library and Other header file */ -extern "C" -{ -//USB Related Headers -#include -#pragma warning(disable:4200) -#include -#include -#include -#include -#include "SkyWalker1Device.h" - -} -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -#define MAX_BULK_PACKET_SIZE 8 * 512 -#define MAX_BULK_TRANSFER_SIZE (TRANSPORT_PACKET_COUNT * TRANSPORT_PACKET_SIZE) //Suppoting max Frame Size -/* End of Macro Definitions */ - -/* Declare Enumerations here */ -typedef enum __USBSTATE { - - NotStarted, // not started - Stopped, // device stopped - Working, // started and working - PendingStop, // stop pending - PendingRemove, // remove pending - SurpriseRemoved, // removed by surprise - Removed // removed - -}USBSTATE; - -typedef enum _QUEUE_STATE { - - HoldRequests, // device is not started yet - AllowRequests, // device is ready to process - FailRequests // fail both existing and queued up requests - -} QUEUE_STATE; - -#define INITIALIZE_PNP_STATE(_Data_) \ - (_Data_)->UsbDeviceState = NotStarted;\ - (_Data_)->PreviousUsbDeviceState = NotStarted; - -#define SET_NEW_PNP_STATE(_Data_, _state_) \ - (_Data_)->PreviousUsbDeviceState = (_Data_)->UsbDeviceState;\ - (_Data_)->UsbDeviceState = (_state_); - -#define RESTORE_PREVIOUS_PNP_STATE(_Data_) \ - (_Data_)->UsbDeviceState = (_Data_)->PreviousUsbDeviceState; - -/* End of Enumeration declaration */ - -/* Global & Static variables Declaration */ -//BulkUsb Read Write Context -typedef struct _BULKUSB_RW_CONTEXT { - - PURB pUSBRequestBlock; - PUCHAR pTransferBuffer; - ULONG ulRemainingByteTransfer; // remaining to xfer - ULONG ulCompletedByteTransfer; // cumulate xfer - ULONG ulStreamIndex; - class CSkyWalker1Device * pDevice; - -} BULKUSB_RW_CONTEXT, * PBULKUSB_RW_CONTEXT; -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Function Prototypes */ -//Usb Access functions -NTSTATUS InitializeUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoReqestPacket); -NTSTATUS DeconfigureUsbDevice(IN PKSDEVICE pKSDeviceObject); -NTSTATUS StopUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); -NTSTATUS ReadWriteUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN ULONG ulStreamIndex, - IN ULONG ulPacketIndex, - IN PUCHAR pucTransferBuffer, - IN ULONG ulTransferLength, - IN BOOLEAN bRead); -NTSTATUS ControlUsbDevice( IN PKSDEVICE pKSDeviceObject, - IN UCHAR ucRequest, - IN USHORT usValue, - IN USHORT usIndex, - IN PUCHAR pucTransferBuffer, - IN ULONG ulTransferLength, - IN BOOLEAN bRead); -NTSTATUS RemoveUsbDevice( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); -NTSTATUS SurpriseUsbDeviceRemoval(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); -NTSTATUS CancelRemoveUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); -NTSTATUS QueryRemoveUsbDevice( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); -NTSTATUS QueryStopUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); -NTSTATUS CancelStopUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket); - -/* End of Function prototype definitions */ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1Usb.h + Author : + Date : + Purpose : This File Holds all the USB Device access related declarations + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +#ifndef __SKYWALKER1_USB_H +#define __SKYWALKER1_USB_H + +/* Include the Library and Other header file */ +extern "C" +{ +//USB Related Headers +#include +#pragma warning(disable:4200) +#include +#include +#include +#include +#include "SkyWalker1Device.h" + +} +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +#define MAX_BULK_PACKET_SIZE 8 * 512 +#define MAX_BULK_TRANSFER_SIZE (TRANSPORT_PACKET_COUNT * TRANSPORT_PACKET_SIZE) //Suppoting max Frame Size +/* End of Macro Definitions */ + +/* Declare Enumerations here */ +typedef enum __USBSTATE { + + NotStarted, // not started + Stopped, // device stopped + Working, // started and working + PendingStop, // stop pending + PendingRemove, // remove pending + SurpriseRemoved, // removed by surprise + Removed // removed + +}USBSTATE; + +typedef enum _QUEUE_STATE { + + HoldRequests, // device is not started yet + AllowRequests, // device is ready to process + FailRequests // fail both existing and queued up requests + +} QUEUE_STATE; + +#define INITIALIZE_PNP_STATE(_Data_) \ + (_Data_)->UsbDeviceState = NotStarted;\ + (_Data_)->PreviousUsbDeviceState = NotStarted; + +#define SET_NEW_PNP_STATE(_Data_, _state_) \ + (_Data_)->PreviousUsbDeviceState = (_Data_)->UsbDeviceState;\ + (_Data_)->UsbDeviceState = (_state_); + +#define RESTORE_PREVIOUS_PNP_STATE(_Data_) \ + (_Data_)->UsbDeviceState = (_Data_)->PreviousUsbDeviceState; + +/* End of Enumeration declaration */ + +/* Global & Static variables Declaration */ +//BulkUsb Read Write Context +typedef struct _BULKUSB_RW_CONTEXT { + + PURB pUSBRequestBlock; + PUCHAR pTransferBuffer; + ULONG ulRemainingByteTransfer; // remaining to xfer + ULONG ulCompletedByteTransfer; // cumulate xfer + ULONG ulStreamIndex; + class CSkyWalker1Device * pDevice; + +} BULKUSB_RW_CONTEXT, * PBULKUSB_RW_CONTEXT; +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Function Prototypes */ +//Usb Access functions +NTSTATUS InitializeUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoReqestPacket); +NTSTATUS DeconfigureUsbDevice(IN PKSDEVICE pKSDeviceObject); +NTSTATUS StopUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); +NTSTATUS ReadWriteUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN ULONG ulStreamIndex, + IN ULONG ulPacketIndex, + IN PUCHAR pucTransferBuffer, + IN ULONG ulTransferLength, + IN BOOLEAN bRead); +NTSTATUS ControlUsbDevice( IN PKSDEVICE pKSDeviceObject, + IN UCHAR ucRequest, + IN USHORT usValue, + IN USHORT usIndex, + IN PUCHAR pucTransferBuffer, + IN ULONG ulTransferLength, + IN BOOLEAN bRead); +NTSTATUS RemoveUsbDevice( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); +NTSTATUS SurpriseUsbDeviceRemoval(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); +NTSTATUS CancelRemoveUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); +NTSTATUS QueryRemoveUsbDevice( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); +NTSTATUS QueryStopUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); +NTSTATUS CancelStopUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket); + +/* End of Function prototype definitions */ + #endif /*__SKYWALKER1_USB_H*/ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Utility.h b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Utility.h index f018fbc..28befbf 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Utility.h +++ b/firmware-driver/SkyWalker1_Final_Release/Include/SkyWalker1Utility.h @@ -1,91 +1,91 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1Utility.h - Author : - Date : - Purpose : This file basically holds the Utility related Common Declarations - used in the SkyWalker Driver Module - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -#ifndef SKYWALKER1_UTILITY_H -#define SKYWALKER1_UTILITY_H - -/* Include the Library and Other header file */ -#include -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -#define SKYWALKER1_DRIVER_NAME "SkyWalker1TVTuner" - -extern int nCurrentDebugLevel; - -#define DEBUG_ON -#ifdef DEBUG_ON - -#define SkyWalkerDebugPrint(DebugLevel,_ARGUMENTS_) \ - if((DebugLevel) <= nCurrentDebugLevel) \ - { \ - DbgPrint _ARGUMENTS_; \ - } - -#else - -#define SkyWalkerDebugPrint(DebugLevel,__ARGUMENTS__) - -#endif - -#define TRUE 1 -#define FALSE 0 - -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ - -/* ENUM : enDebugLevels */ -/* PURPOSE : To Define Different Debug Levels */ -enum enDebugLevels -{ - DISABLE_DEBUG = 0, - ENTRY_LEVEL, - INTERMEDIATE_LEVEL, - EXTREME_LEVEL, -}; -/* End of Enumeration declaration */ - -/* Function Prototypes */ -void PrintFunctionEntry(IN char * pcFunctionName); -void PrintFunctionExit(IN char * pcFunctionName, IN NTSTATUS ntReturnCode); -char * GetCurrentIrqlString(void); -VOID Delay(IN ULONG ulDelayInMicroSeconds); //To Delay the Execution Thread - -NTSTATUS LowerDeviceCompletedIrp(IN PDEVICE_OBJECT pDeviceObject, - IN PIRP pIoRequestPacket, - IN PVOID pContext); -NTSTATUS PassDownIRPAndWaitForCompletion(IN PDEVICE_OBJECT pLowerDeviceObject, - IN PIRP pIoRequestPacket, - IN BOOLEAN bCopyStackLocation); -NTSTATUS PassDownIRPAndForget(IN PDEVICE_OBJECT pLowerDeviceObject, - IN PIRP pIoRequestPacket); -VOID CompleteIrpInDispatch(IN PDEVICE_OBJECT pDeviceObject, - IN PIRP pIoRequestPacket); -PUCHAR NTStatusToString(NTSTATUS Status) ; -VOID PrintDeviceChangeState(IN KSSTATE ToState,IN KSSTATE FromState); -/* End of Function prototype definitions */ - - - -#endif /*SKYWALKER1_UTILITY_H*/ - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1Utility.h + Author : + Date : + Purpose : This file basically holds the Utility related Common Declarations + used in the SkyWalker Driver Module + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +#ifndef SKYWALKER1_UTILITY_H +#define SKYWALKER1_UTILITY_H + +/* Include the Library and Other header file */ +#include +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +#define SKYWALKER1_DRIVER_NAME "SkyWalker1TVTuner" + +extern int nCurrentDebugLevel; + +#define DEBUG_ON +#ifdef DEBUG_ON + +#define SkyWalkerDebugPrint(DebugLevel,_ARGUMENTS_) \ + if((DebugLevel) <= nCurrentDebugLevel) \ + { \ + DbgPrint _ARGUMENTS_; \ + } + +#else + +#define SkyWalkerDebugPrint(DebugLevel,__ARGUMENTS__) + +#endif + +#define TRUE 1 +#define FALSE 0 + +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ + +/* ENUM : enDebugLevels */ +/* PURPOSE : To Define Different Debug Levels */ +enum enDebugLevels +{ + DISABLE_DEBUG = 0, + ENTRY_LEVEL, + INTERMEDIATE_LEVEL, + EXTREME_LEVEL, +}; +/* End of Enumeration declaration */ + +/* Function Prototypes */ +void PrintFunctionEntry(IN char * pcFunctionName); +void PrintFunctionExit(IN char * pcFunctionName, IN NTSTATUS ntReturnCode); +char * GetCurrentIrqlString(void); +VOID Delay(IN ULONG ulDelayInMicroSeconds); //To Delay the Execution Thread + +NTSTATUS LowerDeviceCompletedIrp(IN PDEVICE_OBJECT pDeviceObject, + IN PIRP pIoRequestPacket, + IN PVOID pContext); +NTSTATUS PassDownIRPAndWaitForCompletion(IN PDEVICE_OBJECT pLowerDeviceObject, + IN PIRP pIoRequestPacket, + IN BOOLEAN bCopyStackLocation); +NTSTATUS PassDownIRPAndForget(IN PDEVICE_OBJECT pLowerDeviceObject, + IN PIRP pIoRequestPacket); +VOID CompleteIrpInDispatch(IN PDEVICE_OBJECT pDeviceObject, + IN PIRP pIoRequestPacket); +PUCHAR NTStatusToString(NTSTATUS Status) ; +VOID PrintDeviceChangeState(IN KSSTATE ToState,IN KSSTATE FromState); +/* End of Function prototype definitions */ + + + +#endif /*SKYWALKER1_UTILITY_H*/ + diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1AntennaPin.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1AntennaPin.cpp index 589edca..c3ec4f2 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1AntennaPin.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1AntennaPin.cpp @@ -1,546 +1,546 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1AntennaPin.cpp - Author : - Date : - Purpose : This File Holds the Antenna Pin related declarations - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Common For all the Definitions, - //Declarations and Library Routines - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -PCHAR GetTunerPropertyString(ULONG ulTunerProperty); -PCHAR GetTunerLnbPropertyString(ULONG ulTunerLnbProperty); -/* End of Function prototype definitions */ - -/***************************************************************************** - Function : CAntennaPin::IntersectDataFormat - Description : Enables connection of the input pin with a upstream filter. - IN PARAM : - OUT PARAM : Status of the IntersectDataFormat - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS CAntennaPin::IntersectDataFormat( - IN PVOID pContext, - IN PIRP pIoRequestPacket, - IN PKSP_PIN Pin, - IN PKSDATARANGE pDataRange, - IN PKSDATARANGE pMatchingDataRange, - IN ULONG ulDataBufferSize, - OUT PVOID pData OPTIONAL, - OUT PULONG pulDataSize - ) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if ( ulDataBufferSize < sizeof(KS_DATARANGE_BDA_ANTENNA) ) - { - *pulDataSize = sizeof( KS_DATARANGE_BDA_ANTENNA ); - ntStatus = STATUS_BUFFER_OVERFLOW; - goto ExitDataFormat; - } - else if (pDataRange->FormatSize < sizeof (KS_DATARANGE_BDA_ANTENNA)) - { - ntStatus = STATUS_NO_MATCH; - goto ExitDataFormat; - } - else - { - *pulDataSize = sizeof( KS_DATARANGE_BDA_ANTENNA ); - RtlCopyMemory( pData, (PVOID)pDataRange, sizeof(KS_DATARANGE_BDA_ANTENNA)); - ntStatus = STATUS_SUCCESS; - } - -ExitDataFormat: - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; - -} - -/***************************************************************************** - Function : CAntennaPin::PinSetDeviceState - Description : An AVStream minidriver's AVStrMiniPinSetDeviceState - routine is called when the state of a KSPIN structure is - changed due to the arrival of a connection state property - 'set' IOCTL. Typically, this will be provided by minidrivers - that need to change the state of hardware. - - The KSSTATE enumeration lists possible states of a kernel - streaming object. - typedef enum { - KSSTATE_STOP; - KSSTATE_ACQUIRE; - KSSTATE_PAUSE; - KSSTATE_RUN; - } KSSTATE; - - Enumerators - KSSTATE_STOP - Indicates that the object is in minimum resource consumption mode. - KSSTATE_ACQUIRE - Indicates that the object is acquiring resources. - KSSTATE_PAUSE - Indicates that the object is preparing to make instant transition to Run state. - KSSTATE_RUN - Indicates that the object is actively streaming. - - Because the most upstream pin (input pin) is the last - to transition, use this pin's state to set the state - of the filter. - Also, release filter resouces if the pin's state - transitions to stop, and acquire resources if the pin's - state transitions from stop. - IN PARAM : Pointer to the KSPIN structure for which state is changing. - The target KSSTATE after receipt of the IOCTL. - The previous KSSTATE. - OUT PARAM : Status of the PinSetDeviceState - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CAntennaPin::PinSetDeviceState( - IN PKSPIN pKSPin, - IN KSSTATE ToState, - IN KSSTATE FromState - ) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - PKSDEVICE pKSDevice = NULL; - CAntennaPin * pPin = NULL; - CSkyWalker1Device * pDevice = NULL; - - PrintFunctionEntry(__FUNCTION__); - - //Obtain a pointer to the device object from - //the passed in pointer to the KSPIN structure. - pKSDevice = KsPinGetDevice( pKSPin); - - //Obtain a pointer to the pin object from context member of - //the passed in pointer to the KSPIN structure. - pPin = reinterpret_cast(pKSPin->Context); - - //Obtain a pointer to the device object from context member of - //the retrieved pointer to the KSDEVICE structure. - pDevice = reinterpret_cast(pKSDevice->Context); - - pPin->m_pFilter->SetDeviceState( pPin->m_KsState); - - if ((ToState == KSSTATE_STOP) && (FromState != KSSTATE_STOP)) - { - //Because the driver allocates resources on a filter wide basis, - //inform the filter to release resources when the last pin - //(that is, the most upstream pin) transitions to the stop state. - // - //The input pin is the last pin to transition to the stop state, - //therefore inform the filter to release its resources. - // - ntSetStatus = pPin->m_pFilter->ReleaseResources(); - pPin->m_KsState = ToState; - } - else if ((ToState == KSSTATE_ACQUIRE) && (FromState == KSSTATE_STOP)) - { - //Because the driver allocates resources on a filter wide basis, - //inform the filter to acquire resources when the last pin - //(that is, the most upstream pin) transitions from the stop state. - // - //The input pin is the last pin to transition from the stop state, - //therefore inform the filter to acquire its resources. - // - ntSetStatus = pPin->m_pFilter->AcquireResources(); - if (NT_SUCCESS( ntSetStatus)) - { - pPin->m_KsState = ToState; - } - } - else if (ToState > KSSTATE_RUN) - { - - SkyWalkerDebugPrint(EXTREME_LEVEL, - ("Invalid Device State. ToState 0x%08x. FromState 0x%08x.", - ToState, FromState)); - ntSetStatus = STATUS_INVALID_PARAMETER; - } - else - { - pPin->m_KsState = ToState; - } - - PrintDeviceChangeState(ToState,FromState); - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - - - -/***************************************************************************** - Function : CAntennaPin::GetTunerProperty - Description : Retrieves the value of the Tuner node Properties - IN PARAM : IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - OUT PULONG pulProperty - OUT PARAM : Status SUCCESS in case Valid Property request - STATUS_INVALID_PARAMETER in case of Invalid property request - Else error from the lower device - PreCondition : None - PostCondtion : Tuner propery read in case of successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CAntennaPin::GetTunerProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - OUT PULONG pulProperty - ) -{ - NTSTATUS ntGetStatus = STATUS_SUCCESS; - CAntennaPin * pPin = NULL; - CTunerFilter* pFilter = NULL; - BDATUNER_DEVICE_PARAMETER TunerProperty; - - PrintFunctionEntry(__FUNCTION__); - //Call the BDA support library to - //validate that the node type is associated with the pin. - - //The BdaValidateNodeProperty function validates that a node property - //request is associated with a specific pin. - ntGetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); - if (NT_SUCCESS( ntGetStatus)) - { - //Obtain a pointer to the pin object. - - //Because the property dispatch table calls the CAntennaPin::GetTunerProperty() - //method directly, the method must retrieve a pointer to the underlying pin object. - pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); - - //Retrieve the filter context from the pin context. - pFilter = pPin->GetFilter(); - - ntGetStatus = pFilter->GetTunerProperty(&TunerProperty); - //Retrieve the actual filter parameter. - switch (pKSProperty->Id) - { - case KSPROPERTY_BDA_RF_TUNER_FREQUENCY: - *pulProperty = TunerProperty.ulCarrierFrequency; - break; - case KSPROPERTY_BDA_RF_TUNER_FREQUENCY_MULTIPLIER: - *pulProperty = TunerProperty.ulFrequencyMultiplier; - break; - case KSPROPERTY_BDA_RF_TUNER_BANDWIDTH: - *pulProperty = TunerProperty.ulBandWidth; - break; - case KSPROPERTY_BDA_RF_TUNER_POLARITY: - *pulProperty = TunerProperty.Polarity; - break; - case KSPROPERTY_BDA_RF_TUNER_RANGE: - *pulProperty = TunerProperty.ulRange; - break; - case KSPROPERTY_BDA_RF_TUNER_TRANSPONDER: - *pulProperty = TunerProperty.ulTransponder; - break; - default: - ntGetStatus = STATUS_INVALID_PARAMETER; - break; - } - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Get : %s : %ul",GetTunerPropertyString(pKSProperty->Id),*pulProperty)); - - PrintFunctionExit(__FUNCTION__,ntGetStatus); - return ntGetStatus; -} - -/***************************************************************************** - Function : CAntennaPin::SetTunerProperty - Description : Sets the value of the Tuner node Freq. Properties - IN PARAM : IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - OUT PULONG pulProperty - OUT PARAM : Status SUCCESS in case Valid Property request - STATUS_INVALID_PARAMETER in case of Invalid property request - Else error from the lower device - PreCondition : None - PostCondtion : Tuner Freq. property read in case of successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CAntennaPin::SetTunerProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - CAntennaPin * pPin; - CTunerFilter* pFilter; - - PrintFunctionEntry(__FUNCTION__); - //Call the BDA support library to - //validate that the node type is associated with the pin. - - //The BdaValidateNodeProperty function validates that a node property - //request is associated with a specific pin. - ntSetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); - if (NT_SUCCESS( ntSetStatus)) - { - //Obtain a pointer to the pin object. - - //Because the property dispatch table calls the CAntennaPin::SetTunerProperty() - //method directly, the method must retrieve a pointer to the underlying pin object. - pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); - - //Retrieve the filter context from the pin context. - pFilter = pPin->GetFilter(); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Set : %s : %lu\n", - GetTunerPropertyString(pKSProperty->Id), - *pulProperty)); - //Retrieve the actual filter parameter. - switch (pKSProperty->Id) - { - case KSPROPERTY_BDA_RF_TUNER_FREQUENCY: - ntSetStatus = pFilter->SetFrequency(*pulProperty); - break; - case KSPROPERTY_BDA_RF_TUNER_FREQUENCY_MULTIPLIER: - ntSetStatus = pFilter->SetMultiplier(*pulProperty); - break; - case KSPROPERTY_BDA_RF_TUNER_BANDWIDTH: - ntSetStatus = pFilter->SetBandwidth(*pulProperty); - break; - case KSPROPERTY_BDA_RF_TUNER_POLARITY: - ntSetStatus = pFilter->SetPolarity((Polarisation) *pulProperty); - break; - case KSPROPERTY_BDA_RF_TUNER_RANGE: - ntSetStatus = pFilter->SetRange(*pulProperty); - break; - case KSPROPERTY_BDA_RF_TUNER_TRANSPONDER: - ntSetStatus = pFilter->SetTransponder(*pulProperty); - break; - default: - ntSetStatus = STATUS_INVALID_PARAMETER; - break; - } - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CAntennaPin::GetTunerLnbProperty - Description : Retrieves the value of the Tuner Lnb node Properties - IN PARAM : IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - OUT PULONG pulProperty - OUT PARAM : Status SUCCESS in case Valid Property request - STATUS_INVALID_PARAMETER in case of Invalid property request - Else error from the lower device - PreCondition : None - PostCondtion : Tuner lnb propery read in case of successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CAntennaPin::GetTunerLnbProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - OUT PULONG pulProperty - ) -{ - NTSTATUS ntGetStatus = STATUS_SUCCESS; - CAntennaPin * pPin; - CTunerFilter* pFilter; - BDATUNER_DEVICE_PARAMETER LnbProperty; - - PrintFunctionEntry(__FUNCTION__); - //Call the BDA support library to - //validate that the node type is associated with the pin. - - //The BdaValidateNodeProperty function validates that a node property - //request is associated with a specific pin. - ntGetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); - if (NT_SUCCESS( ntGetStatus)) - { - //Obtain a pointer to the pin object. - - //Because the property dispatch table calls the CAntennaPin::GetTunerProperty() - //method directly, the method must retrieve a pointer to the underlying pin object. - pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); - - //Retrieve the filter context from the pin context. - pFilter = pPin->GetFilter(); - - ntGetStatus = pFilter->GetTunerProperty(&LnbProperty); - - //Retrieve the actual filter parameter. - switch (pKSProperty->Id) - { - case KSPROPERTY_BDA_LNB_LOF_LOW_BAND: - *pulProperty = LnbProperty.ulLnbLowLOFrequency; - break; - case KSPROPERTY_BDA_LNB_LOF_HIGH_BAND: - *pulProperty = LnbProperty.ulLnbHighLOFrequency; - break; - case KSPROPERTY_BDA_LNB_SWITCH_FREQUENCY: - *pulProperty = LnbProperty.ulLnbSwitchFrequency; - break; - default: - ntGetStatus = STATUS_INVALID_PARAMETER; - break; - } - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Get : %s : %ul",GetTunerLnbPropertyString(pKSProperty->Id),*pulProperty)); - - PrintFunctionExit(__FUNCTION__,ntGetStatus); - return ntGetStatus; -} - -/***************************************************************************** - Function : CAntennaPin::SetTunerLnbProperty - Description : Sets the value of the Tuner Lnb node Properties - IN PARAM : IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - OUT PARAM : Status SUCCESS in case Valid Property request - STATUS_INVALID_PARAMETER in case of Invalid property request - Else error from the lower device - PreCondition : None - PostCondtion : Tuner propery Set in case of successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CAntennaPin::SetTunerLnbProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - CAntennaPin * pPin; - CTunerFilter* pFilter; - - PrintFunctionEntry(__FUNCTION__); - //Call the BDA support library to - //validate that the node type is associated with the pin. - - //The BdaValidateNodeProperty function validates that a node property - //request is associated with a specific pin. - ntSetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); - if (NT_SUCCESS( ntSetStatus)) - { - //Obtain a pointer to the pin object. - - //Because the property dispatch table calls the CAntennaPin::SetTunerProperty() - //method directly, the method must retrieve a pointer to the underlying pin object. - - pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); - - //Retrieve the filter context from the pin context. - pFilter = pPin->GetFilter(); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Set : %s : %lu(%l)", - GetTunerLnbPropertyString(pKSProperty->Id), - *pulProperty, - *((LONG*)(pulProperty)))); - //Retrieve the actual filter parameter. - switch (pKSProperty->Id) - { - case KSPROPERTY_BDA_LNB_LOF_LOW_BAND: - ntSetStatus = pFilter->SetLowLOFrequency(*pulProperty); - break; - case KSPROPERTY_BDA_LNB_LOF_HIGH_BAND: - ntSetStatus = pFilter->SetHighLOFrequency(*pulProperty); - break; - case KSPROPERTY_BDA_LNB_SWITCH_FREQUENCY: - ntSetStatus = pFilter->SetSwitchFrequency(*pulProperty); - break; - default: - ntSetStatus = STATUS_INVALID_PARAMETER; - break; - } - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - - - - -PCHAR GetTunerPropertyString(ULONG ulTunerProperty) -{ - switch(ulTunerProperty) - { - case KSPROPERTY_BDA_RF_TUNER_FREQUENCY: - return "KSPROPERTY_BDA_RF_TUNER_FREQUENCY"; - case KSPROPERTY_BDA_RF_TUNER_POLARITY: - return "KSPROPERTY_BDA_RF_TUNER_POLARITY"; - case KSPROPERTY_BDA_RF_TUNER_RANGE: - return "KSPROPERTY_BDA_RF_TUNER_RANGE"; - case KSPROPERTY_BDA_RF_TUNER_TRANSPONDER: - return "KSPROPERTY_BDA_RF_TUNER_TRANSPONDER"; - case KSPROPERTY_BDA_RF_TUNER_BANDWIDTH: - return "KSPROPERTY_BDA_RF_TUNER_BANDWIDTH"; - case KSPROPERTY_BDA_RF_TUNER_FREQUENCY_MULTIPLIER: - return "KSPROPERTY_BDA_RF_TUNER_FREQUENCY_MULTIPLIER"; - default: - return "KSPROPERTY_BDA_INVALID_PROPERTY"; - } -} - -PCHAR GetTunerLnbPropertyString(ULONG ulTunerLnbProperty) -{ - switch(ulTunerLnbProperty) - { - case KSPROPERTY_BDA_LNB_LOF_LOW_BAND: - return "KSPROPERTY_BDA_LNB_LOF_LOW_BAND"; - case KSPROPERTY_BDA_LNB_LOF_HIGH_BAND: - return "KSPROPERTY_BDA_LNB_LOF_HIGH_BAND"; - case KSPROPERTY_BDA_LNB_SWITCH_FREQUENCY: - return "KSPROPERTY_BDA_LNB_SWITCH_FREQUENCY"; - default: - return "KSPROPERTY_BDA_INVALID_PROPERTY"; - } -} +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1AntennaPin.cpp + Author : + Date : + Purpose : This File Holds the Antenna Pin related declarations + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Common For all the Definitions, + //Declarations and Library Routines + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +PCHAR GetTunerPropertyString(ULONG ulTunerProperty); +PCHAR GetTunerLnbPropertyString(ULONG ulTunerLnbProperty); +/* End of Function prototype definitions */ + +/***************************************************************************** + Function : CAntennaPin::IntersectDataFormat + Description : Enables connection of the input pin with a upstream filter. + IN PARAM : + OUT PARAM : Status of the IntersectDataFormat + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS CAntennaPin::IntersectDataFormat( + IN PVOID pContext, + IN PIRP pIoRequestPacket, + IN PKSP_PIN Pin, + IN PKSDATARANGE pDataRange, + IN PKSDATARANGE pMatchingDataRange, + IN ULONG ulDataBufferSize, + OUT PVOID pData OPTIONAL, + OUT PULONG pulDataSize + ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if ( ulDataBufferSize < sizeof(KS_DATARANGE_BDA_ANTENNA) ) + { + *pulDataSize = sizeof( KS_DATARANGE_BDA_ANTENNA ); + ntStatus = STATUS_BUFFER_OVERFLOW; + goto ExitDataFormat; + } + else if (pDataRange->FormatSize < sizeof (KS_DATARANGE_BDA_ANTENNA)) + { + ntStatus = STATUS_NO_MATCH; + goto ExitDataFormat; + } + else + { + *pulDataSize = sizeof( KS_DATARANGE_BDA_ANTENNA ); + RtlCopyMemory( pData, (PVOID)pDataRange, sizeof(KS_DATARANGE_BDA_ANTENNA)); + ntStatus = STATUS_SUCCESS; + } + +ExitDataFormat: + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; + +} + +/***************************************************************************** + Function : CAntennaPin::PinSetDeviceState + Description : An AVStream minidriver's AVStrMiniPinSetDeviceState + routine is called when the state of a KSPIN structure is + changed due to the arrival of a connection state property + 'set' IOCTL. Typically, this will be provided by minidrivers + that need to change the state of hardware. + + The KSSTATE enumeration lists possible states of a kernel + streaming object. + typedef enum { + KSSTATE_STOP; + KSSTATE_ACQUIRE; + KSSTATE_PAUSE; + KSSTATE_RUN; + } KSSTATE; + + Enumerators + KSSTATE_STOP + Indicates that the object is in minimum resource consumption mode. + KSSTATE_ACQUIRE + Indicates that the object is acquiring resources. + KSSTATE_PAUSE + Indicates that the object is preparing to make instant transition to Run state. + KSSTATE_RUN + Indicates that the object is actively streaming. + + Because the most upstream pin (input pin) is the last + to transition, use this pin's state to set the state + of the filter. + Also, release filter resouces if the pin's state + transitions to stop, and acquire resources if the pin's + state transitions from stop. + IN PARAM : Pointer to the KSPIN structure for which state is changing. + The target KSSTATE after receipt of the IOCTL. + The previous KSSTATE. + OUT PARAM : Status of the PinSetDeviceState + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CAntennaPin::PinSetDeviceState( + IN PKSPIN pKSPin, + IN KSSTATE ToState, + IN KSSTATE FromState + ) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + PKSDEVICE pKSDevice = NULL; + CAntennaPin * pPin = NULL; + CSkyWalker1Device * pDevice = NULL; + + PrintFunctionEntry(__FUNCTION__); + + //Obtain a pointer to the device object from + //the passed in pointer to the KSPIN structure. + pKSDevice = KsPinGetDevice( pKSPin); + + //Obtain a pointer to the pin object from context member of + //the passed in pointer to the KSPIN structure. + pPin = reinterpret_cast(pKSPin->Context); + + //Obtain a pointer to the device object from context member of + //the retrieved pointer to the KSDEVICE structure. + pDevice = reinterpret_cast(pKSDevice->Context); + + pPin->m_pFilter->SetDeviceState( pPin->m_KsState); + + if ((ToState == KSSTATE_STOP) && (FromState != KSSTATE_STOP)) + { + //Because the driver allocates resources on a filter wide basis, + //inform the filter to release resources when the last pin + //(that is, the most upstream pin) transitions to the stop state. + // + //The input pin is the last pin to transition to the stop state, + //therefore inform the filter to release its resources. + // + ntSetStatus = pPin->m_pFilter->ReleaseResources(); + pPin->m_KsState = ToState; + } + else if ((ToState == KSSTATE_ACQUIRE) && (FromState == KSSTATE_STOP)) + { + //Because the driver allocates resources on a filter wide basis, + //inform the filter to acquire resources when the last pin + //(that is, the most upstream pin) transitions from the stop state. + // + //The input pin is the last pin to transition from the stop state, + //therefore inform the filter to acquire its resources. + // + ntSetStatus = pPin->m_pFilter->AcquireResources(); + if (NT_SUCCESS( ntSetStatus)) + { + pPin->m_KsState = ToState; + } + } + else if (ToState > KSSTATE_RUN) + { + + SkyWalkerDebugPrint(EXTREME_LEVEL, + ("Invalid Device State. ToState 0x%08x. FromState 0x%08x.", + ToState, FromState)); + ntSetStatus = STATUS_INVALID_PARAMETER; + } + else + { + pPin->m_KsState = ToState; + } + + PrintDeviceChangeState(ToState,FromState); + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + + + +/***************************************************************************** + Function : CAntennaPin::GetTunerProperty + Description : Retrieves the value of the Tuner node Properties + IN PARAM : IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + OUT PULONG pulProperty + OUT PARAM : Status SUCCESS in case Valid Property request + STATUS_INVALID_PARAMETER in case of Invalid property request + Else error from the lower device + PreCondition : None + PostCondtion : Tuner propery read in case of successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CAntennaPin::GetTunerProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + OUT PULONG pulProperty + ) +{ + NTSTATUS ntGetStatus = STATUS_SUCCESS; + CAntennaPin * pPin = NULL; + CTunerFilter* pFilter = NULL; + BDATUNER_DEVICE_PARAMETER TunerProperty; + + PrintFunctionEntry(__FUNCTION__); + //Call the BDA support library to + //validate that the node type is associated with the pin. + + //The BdaValidateNodeProperty function validates that a node property + //request is associated with a specific pin. + ntGetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); + if (NT_SUCCESS( ntGetStatus)) + { + //Obtain a pointer to the pin object. + + //Because the property dispatch table calls the CAntennaPin::GetTunerProperty() + //method directly, the method must retrieve a pointer to the underlying pin object. + pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); + + //Retrieve the filter context from the pin context. + pFilter = pPin->GetFilter(); + + ntGetStatus = pFilter->GetTunerProperty(&TunerProperty); + //Retrieve the actual filter parameter. + switch (pKSProperty->Id) + { + case KSPROPERTY_BDA_RF_TUNER_FREQUENCY: + *pulProperty = TunerProperty.ulCarrierFrequency; + break; + case KSPROPERTY_BDA_RF_TUNER_FREQUENCY_MULTIPLIER: + *pulProperty = TunerProperty.ulFrequencyMultiplier; + break; + case KSPROPERTY_BDA_RF_TUNER_BANDWIDTH: + *pulProperty = TunerProperty.ulBandWidth; + break; + case KSPROPERTY_BDA_RF_TUNER_POLARITY: + *pulProperty = TunerProperty.Polarity; + break; + case KSPROPERTY_BDA_RF_TUNER_RANGE: + *pulProperty = TunerProperty.ulRange; + break; + case KSPROPERTY_BDA_RF_TUNER_TRANSPONDER: + *pulProperty = TunerProperty.ulTransponder; + break; + default: + ntGetStatus = STATUS_INVALID_PARAMETER; + break; + } + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Get : %s : %ul",GetTunerPropertyString(pKSProperty->Id),*pulProperty)); + + PrintFunctionExit(__FUNCTION__,ntGetStatus); + return ntGetStatus; +} + +/***************************************************************************** + Function : CAntennaPin::SetTunerProperty + Description : Sets the value of the Tuner node Freq. Properties + IN PARAM : IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + OUT PULONG pulProperty + OUT PARAM : Status SUCCESS in case Valid Property request + STATUS_INVALID_PARAMETER in case of Invalid property request + Else error from the lower device + PreCondition : None + PostCondtion : Tuner Freq. property read in case of successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CAntennaPin::SetTunerProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + CAntennaPin * pPin; + CTunerFilter* pFilter; + + PrintFunctionEntry(__FUNCTION__); + //Call the BDA support library to + //validate that the node type is associated with the pin. + + //The BdaValidateNodeProperty function validates that a node property + //request is associated with a specific pin. + ntSetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); + if (NT_SUCCESS( ntSetStatus)) + { + //Obtain a pointer to the pin object. + + //Because the property dispatch table calls the CAntennaPin::SetTunerProperty() + //method directly, the method must retrieve a pointer to the underlying pin object. + pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); + + //Retrieve the filter context from the pin context. + pFilter = pPin->GetFilter(); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Set : %s : %lu\n", + GetTunerPropertyString(pKSProperty->Id), + *pulProperty)); + //Retrieve the actual filter parameter. + switch (pKSProperty->Id) + { + case KSPROPERTY_BDA_RF_TUNER_FREQUENCY: + ntSetStatus = pFilter->SetFrequency(*pulProperty); + break; + case KSPROPERTY_BDA_RF_TUNER_FREQUENCY_MULTIPLIER: + ntSetStatus = pFilter->SetMultiplier(*pulProperty); + break; + case KSPROPERTY_BDA_RF_TUNER_BANDWIDTH: + ntSetStatus = pFilter->SetBandwidth(*pulProperty); + break; + case KSPROPERTY_BDA_RF_TUNER_POLARITY: + ntSetStatus = pFilter->SetPolarity((Polarisation) *pulProperty); + break; + case KSPROPERTY_BDA_RF_TUNER_RANGE: + ntSetStatus = pFilter->SetRange(*pulProperty); + break; + case KSPROPERTY_BDA_RF_TUNER_TRANSPONDER: + ntSetStatus = pFilter->SetTransponder(*pulProperty); + break; + default: + ntSetStatus = STATUS_INVALID_PARAMETER; + break; + } + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CAntennaPin::GetTunerLnbProperty + Description : Retrieves the value of the Tuner Lnb node Properties + IN PARAM : IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + OUT PULONG pulProperty + OUT PARAM : Status SUCCESS in case Valid Property request + STATUS_INVALID_PARAMETER in case of Invalid property request + Else error from the lower device + PreCondition : None + PostCondtion : Tuner lnb propery read in case of successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CAntennaPin::GetTunerLnbProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + OUT PULONG pulProperty + ) +{ + NTSTATUS ntGetStatus = STATUS_SUCCESS; + CAntennaPin * pPin; + CTunerFilter* pFilter; + BDATUNER_DEVICE_PARAMETER LnbProperty; + + PrintFunctionEntry(__FUNCTION__); + //Call the BDA support library to + //validate that the node type is associated with the pin. + + //The BdaValidateNodeProperty function validates that a node property + //request is associated with a specific pin. + ntGetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); + if (NT_SUCCESS( ntGetStatus)) + { + //Obtain a pointer to the pin object. + + //Because the property dispatch table calls the CAntennaPin::GetTunerProperty() + //method directly, the method must retrieve a pointer to the underlying pin object. + pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); + + //Retrieve the filter context from the pin context. + pFilter = pPin->GetFilter(); + + ntGetStatus = pFilter->GetTunerProperty(&LnbProperty); + + //Retrieve the actual filter parameter. + switch (pKSProperty->Id) + { + case KSPROPERTY_BDA_LNB_LOF_LOW_BAND: + *pulProperty = LnbProperty.ulLnbLowLOFrequency; + break; + case KSPROPERTY_BDA_LNB_LOF_HIGH_BAND: + *pulProperty = LnbProperty.ulLnbHighLOFrequency; + break; + case KSPROPERTY_BDA_LNB_SWITCH_FREQUENCY: + *pulProperty = LnbProperty.ulLnbSwitchFrequency; + break; + default: + ntGetStatus = STATUS_INVALID_PARAMETER; + break; + } + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Get : %s : %ul",GetTunerLnbPropertyString(pKSProperty->Id),*pulProperty)); + + PrintFunctionExit(__FUNCTION__,ntGetStatus); + return ntGetStatus; +} + +/***************************************************************************** + Function : CAntennaPin::SetTunerLnbProperty + Description : Sets the value of the Tuner Lnb node Properties + IN PARAM : IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + OUT PARAM : Status SUCCESS in case Valid Property request + STATUS_INVALID_PARAMETER in case of Invalid property request + Else error from the lower device + PreCondition : None + PostCondtion : Tuner propery Set in case of successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CAntennaPin::SetTunerLnbProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + CAntennaPin * pPin; + CTunerFilter* pFilter; + + PrintFunctionEntry(__FUNCTION__); + //Call the BDA support library to + //validate that the node type is associated with the pin. + + //The BdaValidateNodeProperty function validates that a node property + //request is associated with a specific pin. + ntSetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); + if (NT_SUCCESS( ntSetStatus)) + { + //Obtain a pointer to the pin object. + + //Because the property dispatch table calls the CAntennaPin::SetTunerProperty() + //method directly, the method must retrieve a pointer to the underlying pin object. + + pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); + + //Retrieve the filter context from the pin context. + pFilter = pPin->GetFilter(); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Set : %s : %lu(%l)", + GetTunerLnbPropertyString(pKSProperty->Id), + *pulProperty, + *((LONG*)(pulProperty)))); + //Retrieve the actual filter parameter. + switch (pKSProperty->Id) + { + case KSPROPERTY_BDA_LNB_LOF_LOW_BAND: + ntSetStatus = pFilter->SetLowLOFrequency(*pulProperty); + break; + case KSPROPERTY_BDA_LNB_LOF_HIGH_BAND: + ntSetStatus = pFilter->SetHighLOFrequency(*pulProperty); + break; + case KSPROPERTY_BDA_LNB_SWITCH_FREQUENCY: + ntSetStatus = pFilter->SetSwitchFrequency(*pulProperty); + break; + default: + ntSetStatus = STATUS_INVALID_PARAMETER; + break; + } + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + + + + +PCHAR GetTunerPropertyString(ULONG ulTunerProperty) +{ + switch(ulTunerProperty) + { + case KSPROPERTY_BDA_RF_TUNER_FREQUENCY: + return "KSPROPERTY_BDA_RF_TUNER_FREQUENCY"; + case KSPROPERTY_BDA_RF_TUNER_POLARITY: + return "KSPROPERTY_BDA_RF_TUNER_POLARITY"; + case KSPROPERTY_BDA_RF_TUNER_RANGE: + return "KSPROPERTY_BDA_RF_TUNER_RANGE"; + case KSPROPERTY_BDA_RF_TUNER_TRANSPONDER: + return "KSPROPERTY_BDA_RF_TUNER_TRANSPONDER"; + case KSPROPERTY_BDA_RF_TUNER_BANDWIDTH: + return "KSPROPERTY_BDA_RF_TUNER_BANDWIDTH"; + case KSPROPERTY_BDA_RF_TUNER_FREQUENCY_MULTIPLIER: + return "KSPROPERTY_BDA_RF_TUNER_FREQUENCY_MULTIPLIER"; + default: + return "KSPROPERTY_BDA_INVALID_PROPERTY"; + } +} + +PCHAR GetTunerLnbPropertyString(ULONG ulTunerLnbProperty) +{ + switch(ulTunerLnbProperty) + { + case KSPROPERTY_BDA_LNB_LOF_LOW_BAND: + return "KSPROPERTY_BDA_LNB_LOF_LOW_BAND"; + case KSPROPERTY_BDA_LNB_LOF_HIGH_BAND: + return "KSPROPERTY_BDA_LNB_LOF_HIGH_BAND"; + case KSPROPERTY_BDA_LNB_SWITCH_FREQUENCY: + return "KSPROPERTY_BDA_LNB_SWITCH_FREQUENCY"; + default: + return "KSPROPERTY_BDA_INVALID_PROPERTY"; + } +} diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CaptureFilter.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CaptureFilter.cpp index 304d258..2ddc17f 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CaptureFilter.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CaptureFilter.cpp @@ -1,165 +1,165 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1CaptureFilter.cpp - Author : - Date : - Purpose : This file contains the filter level header for the - capture filter. - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Main Header file - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - -/***************************************************************************** - Function : CCaptureFilter - Description : Constructor of the CCaptureFilter Class - The capture filter object constructor. Since the new operator will - have zeroed the memory, do not bother initializing any NULL or 0 - fields.Only initialize non-NULL, non-0 fields. - IN PARAM : Filter - OUT PARAM : NONE - PreCondition : Filter Object is not created - PostCondtion : Filter Object is created and Initialzed on successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -CCaptureFilter::CCaptureFilter(IN PKSFILTER pKSFilter) : - m_Filter (pKSFilter) -{ - -} - -/***************************************************************************** - Function : CCaptureFilter - Description : Destructor of the CCaptureFilter Class - Destroys the filter object - IN PARAM : NONE - OUT PARAM : NONE - PreCondition : Filter Object is created - PostCondtion : Filter Object is Removed and Memory freed - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -CCaptureFilter::~CCaptureFilter() -{ - -} - -/***************************************************************************** - Function : CCaptureFilter::Cleanup() - Description : This is the bag cleanup callback for the CCaptureFilter. - Destroys the filter object - IN PARAM : Reference to the current Object - OUT PARAM : NONE - PreCondition : Filter Object is created - PostCondtion : Filter Object is Removed and Memory freed - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -void CCaptureFilter::Cleanup (IN CCaptureFilter *pFilter) -{ - delete pFilter; -} - - -/***************************************************************************** - Function : CCaptureFilter::Create() - Description : It creates the CCaptureFilter object, associates it with - the AVStream filter object, and bag the CCaptureFilter - for later cleanup. - IN PARAM : Pointer to KSFILTER that just created - Pointer to IRP_MJ_CREATE for Filter - OUT PARAM : Status of the Filter Create routine - STATUS_SUCCESS on Routine success - Else Error code from the attempt to create the Filter - PreCondition : Filter is not created - PostCondtion : Filter is created and Initialzed on successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -STDMETHODIMP_(NTSTATUS) CCaptureFilter::Create( IN OUT PKSFILTER pKSFilter, - IN PIRP pIoRequestPacket) -{ - NTSTATUS ntFilterCreationStatus = STATUS_SUCCESS; - ULONG ulPinId; // just useful when no network provider is present - PKSDEVICE pKSDeviceObject = NULL; - CSkyWalker1Device * pDevice = NULL; - - PrintFunctionEntry(__FUNCTION__); - - //Create a filter object for the filter instance. - CCaptureFilter* pFilter = new(NonPagedPool,CAPTURE_MEM_TAG) CCaptureFilter(pKSFilter); // Tags the allocated memory - if (!IS_VALID(pFilter)) - { - //Exit if the Filter Memory could not be allocated - ntFilterCreationStatus = STATUS_INSUFFICIENT_RESOURCES; - goto ErrorFilterCreate; - } - else - { - // Add the item to the object bag if we we were successful. - // Whenever the filter closes, the bag is cleaned up and we will be - // freed. - // - ntFilterCreationStatus = KsAddItemToObjectBag ( - pKSFilter -> Bag, - reinterpret_cast (pFilter), - reinterpret_cast (CCaptureFilter::Cleanup) - ); - - if (!NT_SUCCESS (ntFilterCreationStatus)) - { - goto ErrorFilterCreate; - } - else - { - pKSFilter->Context = reinterpret_cast (pFilter); - } - } - -CompleteFilterCreate : - - PrintFunctionExit(__FUNCTION__,ntFilterCreationStatus); - return ntFilterCreationStatus; - -ErrorFilterCreate: - if (IS_VALID(pFilter)) - { - delete pFilter; - } - - goto CompleteFilterCreate; +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1CaptureFilter.cpp + Author : + Date : + Purpose : This file contains the filter level header for the + capture filter. + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Main Header file + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + +/***************************************************************************** + Function : CCaptureFilter + Description : Constructor of the CCaptureFilter Class + The capture filter object constructor. Since the new operator will + have zeroed the memory, do not bother initializing any NULL or 0 + fields.Only initialize non-NULL, non-0 fields. + IN PARAM : Filter + OUT PARAM : NONE + PreCondition : Filter Object is not created + PostCondtion : Filter Object is created and Initialzed on successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +CCaptureFilter::CCaptureFilter(IN PKSFILTER pKSFilter) : + m_Filter (pKSFilter) +{ + +} + +/***************************************************************************** + Function : CCaptureFilter + Description : Destructor of the CCaptureFilter Class + Destroys the filter object + IN PARAM : NONE + OUT PARAM : NONE + PreCondition : Filter Object is created + PostCondtion : Filter Object is Removed and Memory freed + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +CCaptureFilter::~CCaptureFilter() +{ + +} + +/***************************************************************************** + Function : CCaptureFilter::Cleanup() + Description : This is the bag cleanup callback for the CCaptureFilter. + Destroys the filter object + IN PARAM : Reference to the current Object + OUT PARAM : NONE + PreCondition : Filter Object is created + PostCondtion : Filter Object is Removed and Memory freed + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +void CCaptureFilter::Cleanup (IN CCaptureFilter *pFilter) +{ + delete pFilter; +} + + +/***************************************************************************** + Function : CCaptureFilter::Create() + Description : It creates the CCaptureFilter object, associates it with + the AVStream filter object, and bag the CCaptureFilter + for later cleanup. + IN PARAM : Pointer to KSFILTER that just created + Pointer to IRP_MJ_CREATE for Filter + OUT PARAM : Status of the Filter Create routine + STATUS_SUCCESS on Routine success + Else Error code from the attempt to create the Filter + PreCondition : Filter is not created + PostCondtion : Filter is created and Initialzed on successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +STDMETHODIMP_(NTSTATUS) CCaptureFilter::Create( IN OUT PKSFILTER pKSFilter, + IN PIRP pIoRequestPacket) +{ + NTSTATUS ntFilterCreationStatus = STATUS_SUCCESS; + ULONG ulPinId; // just useful when no network provider is present + PKSDEVICE pKSDeviceObject = NULL; + CSkyWalker1Device * pDevice = NULL; + + PrintFunctionEntry(__FUNCTION__); + + //Create a filter object for the filter instance. + CCaptureFilter* pFilter = new(NonPagedPool,CAPTURE_MEM_TAG) CCaptureFilter(pKSFilter); // Tags the allocated memory + if (!IS_VALID(pFilter)) + { + //Exit if the Filter Memory could not be allocated + ntFilterCreationStatus = STATUS_INSUFFICIENT_RESOURCES; + goto ErrorFilterCreate; + } + else + { + // Add the item to the object bag if we we were successful. + // Whenever the filter closes, the bag is cleaned up and we will be + // freed. + // + ntFilterCreationStatus = KsAddItemToObjectBag ( + pKSFilter -> Bag, + reinterpret_cast (pFilter), + reinterpret_cast (CCaptureFilter::Cleanup) + ); + + if (!NT_SUCCESS (ntFilterCreationStatus)) + { + goto ErrorFilterCreate; + } + else + { + pKSFilter->Context = reinterpret_cast (pFilter); + } + } + +CompleteFilterCreate : + + PrintFunctionExit(__FUNCTION__,ntFilterCreationStatus); + return ntFilterCreationStatus; + +ErrorFilterCreate: + if (IS_VALID(pFilter)) + { + delete pFilter; + } + + goto CompleteFilterCreate; } \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CaptureFilterDefinitions.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CaptureFilterDefinitions.cpp index 91f7ad2..29c3cd6 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CaptureFilterDefinitions.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CaptureFilterDefinitions.cpp @@ -1,336 +1,336 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1CaptureFilterDefinitions.cpp - Author : - Date : - Purpose : Capture Filter Definition - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Main Header file - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ - -const KSPIN_DISPATCH CaptureInputPinDispatch={ - /* Create */ CCapturePin::PinCreate, - /* Close */ NULL, - /* Process */ NULL, - /* Reset */ NULL, - /* SetDataFormat */ NULL, - /* SetDeviceState */ NULL, - /* Connect */ NULL, - /* Disconnect */ NULL, - /* Allocator */ NULL -}; - -DEFINE_KSAUTOMATION_TABLE(NullAutomation) { - DEFINE_KSAUTOMATION_PROPERTIES_NULL, - DEFINE_KSAUTOMATION_METHODS_NULL, - DEFINE_KSAUTOMATION_EVENTS_NULL -}; - -//The list of category GUIDs for the capture filter. -const GUID SkyWalker1CaptureCatagories [] = { - STATICGUIDOF (KSCATEGORY_BDA_RECEIVER_COMPONENT) -}; - -//Medium GUIDs for the Transport Output Pin. -// -//Pin Medium descriptor containing all medium accepted to be connected to -//the tuner output pin.This insures contection to the correct Capture Filter pin. -// -//{2AEB4A94-FBB7-4FB1-8D74-243B91886EAB} - -const KSPIN_MEDIUM TransportPinMediums[] = -{ - { - GUID_SKYWALKER_TUNER_OUT_MEDIUM, - 0, - 0 - } -}; - -// -//This is the data range description of the capture input pin. -//This is same as the Outpin of the Tuner i.e. The Transport Pin -//The Output of the Tuner is given to the Capture thus it has to -//be same -// -const KS_DATARANGE_BDA_TRANSPORT FormatCaptureIn = -{ - //insert the KSDATARANGE and KSDATAFORMAT here - { - sizeof( KS_DATARANGE_BDA_TRANSPORT), //FormatSize - 0, //Flags - (N/A) - 0, //SampleSize - (N/A) - 0, //Reserved - { STATIC_KSDATAFORMAT_TYPE_STREAM }, //MajorFormat - { STATIC_KSDATAFORMAT_TYPE_MPEG2_TRANSPORT }, //SubFormat - { STATIC_KSDATAFORMAT_SPECIFIER_BDA_TRANSPORT } //Specifier - }, - //insert the BDA_TRANSPORT_INFO here - { - TRANSPORT_PACKET_SIZE, //ulcbPhyiscalPacket - TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE, //ulcbPhyiscalFrame - 0, //ulcbPhyiscalFrameAlignment (no requirement) - 0 //AvgTimePerFrame (not known) - } -}; - -const PKSDATARANGE CaptureInPinDataRanges[]={ - (PKSDATARANGE)&FormatCaptureIn, -}; - -//Capture Outout Pin Definitions -const KSPIN_DISPATCH CaptureOutputPinDispatch={ - /* Create */ CCapturePin::PinCreate, - /* Close */ NULL, - /* Process */ CCapturePin::DispatchProcess, - /* Reset */ NULL, - /* SetDataFormat */ NULL, - /* SetDeviceState */ CCapturePin::DispatchSetState, - /* Connect */ NULL, - /* Disconnect */ NULL, - /* Allocator */ NULL -}; - - -// -//This is the data range description of the capture output pin. -// -const KSDATARANGE FormatCaptureOut = -{ - //insert the KSDATARANGE and KSDATAFORMAT here - { - sizeof( KSDATARANGE), //FormatSize - 0, //Flags - (N/A) - TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE, //SampleSize - 0, //Reserved - { STATIC_KSDATAFORMAT_TYPE_STREAM }, //MajorFormat - { STATIC_KSDATAFORMAT_SUBTYPE_BDA_MPEG2_TRANSPORT },//SubFormat - { STATIC_KSDATAFORMAT_SPECIFIER_NONE } //Specifier - } -}; - -const PKSDATARANGE CaptureOutPinDataRanges[]={ - (PKSDATARANGE)&FormatCaptureOut, -}; - -// -//CapturePinAllocatorFraming: -// -//This is the simple framing structure for the capture pin. Note that this -//will be modified via KsEdit when the actual capture format is determined. -// -DECLARE_SIMPLE_FRAMING_EX ( - CapturePinAllocatorFraming, //FramingExName - STATICGUIDOF (KSMEMORY_TYPE_KERNEL_NONPAGED), //MemoryType - KSALLOCATOR_REQUIREMENTF_SYSTEM_MEMORY | - KSALLOCATOR_REQUIREMENTF_PREFERENCES_ONLY, //Flags - NUMBER_OF_FRAMES, //Frames - 0, //Alignment - TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE, //MinFrameSize - TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE //MaxFrameSize - ); - -/**********************************************************************************/ - -//Not Supporting Filter Methods,Properties and Events -DEFINE_KSAUTOMATION_TABLE(SkyWalker1CaptureAutomationTable) -{ - DEFINE_KSAUTOMATION_PROPERTIES_NULL, - DEFINE_KSAUTOMATION_METHODS_NULL, - DEFINE_KSAUTOMATION_EVENTS_NULL -}; -/**********************************************************************************/ -// -//CaptureFilterDispatch: -// -//This is the dispatch table for the capture filter. It provides notification -//of creation, closure, processing (for filter-centrics, not for the capture -//filter), and resets (for filter-centrics, not for the capture filter). -// -const KSFILTER_DISPATCH SkyWalker1CaptureDispatchTable = -{ - /* Create */ CCaptureFilter::Create, //Routine called when the Filter is created - /* Close */ NULL, //Routine called when the Filter is closed - /* Process */ NULL, - /* Reset */ NULL -}; - -// -//Capture Pin Descriptors -// -//This data structure defines the pin types available in the filters -//template topology. These structures will be used to create a -//KDPinFactory for a pin type when BdaCreatePin or BdaMethodCreatePin -//are called. -// -//This structure defines ALL pins the filter is capable of supporting, -//including those pins which may only be created dynamically by a ring -//3 component such as a Network Provider. - -//The list of pin descriptors on the capture filter. -const KSPIN_DESCRIPTOR_EX SkyWalker1CapturePinDescriptors[]={ - { //Capture Filter input pin - &CaptureInputPinDispatch, //Dispatch Table - &NullAutomation, //Automation Table - { - 0, //Interfaces - NULL, - SIZEOF_ARRAY(TransportPinMediums), //Medium Count - TransportPinMediums, //Medium - SIZEOF_ARRAY(CaptureInPinDataRanges), //Range Count - CaptureInPinDataRanges, //Ranges - KSPIN_DATAFLOW_IN, //Specifies that data flow is into the pin - KSPIN_COMMUNICATION_BOTH, //Specifies that the pin factory instantiates pins - //that are both IRP sinks and IRP sources - (GUID *) &PINNAME_BDA_TRANSPORT, //Category GUID - (GUID *) &PINNAME_BDA_TRANSPORT, //GUID of the localized Unicode string - 0 - }, - KSPIN_FLAG_DO_NOT_USE_STANDARD_TRANSPORT| - KSPIN_FLAG_FRAMES_NOT_REQUIRED_FOR_PROCESSING| - KSPIN_FLAG_FIXED_FORMAT, - 1, //Maximum Possible Instances of the Pin - 1, //Mandatory Instances of this for the Filter function - NULL,//Allocator Framing - NULL //Data Interaction Handler - }, - { //Capture Filter output pin - &CaptureOutputPinDispatch, //Dispatch Table - &NullAutomation, //Automation Table - { - NULL, - 0, - NULL, - 0, - SIZEOF_ARRAY(CaptureOutPinDataRanges), //Range Count - CaptureOutPinDataRanges, - KSPIN_DATAFLOW_OUT, //Specifies that data flow is out of the pin - KSPIN_COMMUNICATION_BOTH,//Specifies that the pin factory instantiates pins - //that are both IRP sinks and IRP sources - (GUID *) &PINNAME_BDA_TRANSPORT, //Category GUID - (GUID *) &PINNAME_BDA_TRANSPORT, //GUID of the localized Unicode string - 0 - }, -#if !defined(_BUILD_SW_TUNER_ON_X64) - KSPIN_FLAG_GENERATE_MAPPINGS | //Pin Flags -#endif - KSPIN_FLAG_PROCESS_IN_RUN_STATE_ONLY, - 1,//Maximum Possible Instances of the Pin - 1,//Mandatory Instances of this for the Filter function - &CapturePinAllocatorFraming, - NULL - }, -}; - - -/*****************************************************************************************/ -//Define BDA Template Topology Connections -// -//Lists the Connections that are possible between pin types and -//node types. This, together with the Template Filter Descriptor, and -//the Pin Pairings, describe how topologies can be created in the filter. -// -// ================= -//TransportPin ----| Capture Filter | -// ================= -// -//The Capture Filter is controlled by the Transport input pin. -//Capture Filter properties will be set as NODE properties (with NodeType == 0) -//on the filter's Tranport Pin -// -const KSTOPOLOGY_CONNECTION SkyWalker1CaptureConnections[]={ - {KSFILTER_NODE, 0, KSFILTER_NODE, KSNODEPIN_STANDARD_IN}, //Transport pin -> Capture Filter pin 0 -}; - -/*****************************************************************************************/ - -//Define the Filter Factory Descriptor for the filter -//This structure brings together all of the structures that define -//the tuner filter as it appears when it is first instantiated. -//Note that not all of the template pin and node types may be exposed as -//pin and node factories when the filter is first instanciated. - -//The KSFILTER_DESCRIPTOR structure describes the characteristics of a filter created by a given filter factory. -DEFINE_KSFILTER_DESCRIPTOR(SkyWalker1CaptureFilterDescriptor) -{ - &SkyWalker1CaptureDispatchTable, //Dispatch (Filter Specific Driver) - NULL, //AutomationTable - KSFILTER_DESCRIPTOR_VERSION, //Version - 0, //Flags - &SKYWALKER_CAPTURE_FILTER, //ReferenceGuid - DEFINE_KSFILTER_PIN_DESCRIPTORS(SkyWalker1CapturePinDescriptors), - //PinDescriptorsCount; must expose at least one pin - //PinDescriptorSize; size of each item - //PinDescriptors; table of pin descriptors - DEFINE_KSFILTER_CATEGORY(KSCATEGORY_BDA_RECEIVER_COMPONENT), - //CategoriesCount; number of categories in the table - //Categories; table of categories - DEFINE_KSFILTER_NODE_DESCRIPTORS_NULL, - //NodeDescriptorsCount; - //NodeDescriptorSize; - //NodeDescriptors; - DEFINE_KSFILTER_CONNECTIONS(SkyWalker1CaptureConnections), - //Automatically fills in the connections table for a filter which defines no explicit connections - //ConnectionsCount; number of connections in the table - //Connections; table of connections - NULL //ComponentId; -}; - -//Array of BDA_PIN_PAIRING structures that are used to determine -//which nodes get duplicated when more than one output pin type is -//connected to a single input pin type or when more that one input pin -//type is connected to a single output pin type. -// -const BDA_PIN_PAIRING SkyWalker1CapturePinPairings[] = -{ - //Input pin to Output pin Topology Joints - // - { - 0, //ulInputPin; 0 element in the TemplatePinDescriptors array. - 1, //ulOutputPin; 1 element in the TemplatePinDescriptors array. - 1, //ulcMaxInputsPerOutput - 1, //ulcMinInputsPerOutput - 1, //ulcMaxOutputsPerInput - 1, //ulcMinOutputsPerInput - 0, //ulcTopologyJoints - NULL //pTopologyJoints; array of joints - } - //If applicable, list topology of joints between other pins. - // -}; - - -//BDA_FILTER_TEMPLATE structure describes the template topology for BDA Driver -const BDA_FILTER_TEMPLATE SkyWalker1CaptureTemplate = -{ - &SkyWalker1CaptureFilterDescriptor,//Pointer to KS_FILTER_DESCRIPTOR which describes the Filter for BDA Device - SIZEOF_ARRAY(SkyWalker1CapturePinPairings), //Number of PAIRS of pins in BDA_PIN_PAIRING Array - SkyWalker1CapturePinPairings //Array of Pin Pairing describes topology between a pair of Filter's Input and Output Pins -}; - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1CaptureFilterDefinitions.cpp + Author : + Date : + Purpose : Capture Filter Definition + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Main Header file + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ + +const KSPIN_DISPATCH CaptureInputPinDispatch={ + /* Create */ CCapturePin::PinCreate, + /* Close */ NULL, + /* Process */ NULL, + /* Reset */ NULL, + /* SetDataFormat */ NULL, + /* SetDeviceState */ NULL, + /* Connect */ NULL, + /* Disconnect */ NULL, + /* Allocator */ NULL +}; + +DEFINE_KSAUTOMATION_TABLE(NullAutomation) { + DEFINE_KSAUTOMATION_PROPERTIES_NULL, + DEFINE_KSAUTOMATION_METHODS_NULL, + DEFINE_KSAUTOMATION_EVENTS_NULL +}; + +//The list of category GUIDs for the capture filter. +const GUID SkyWalker1CaptureCatagories [] = { + STATICGUIDOF (KSCATEGORY_BDA_RECEIVER_COMPONENT) +}; + +//Medium GUIDs for the Transport Output Pin. +// +//Pin Medium descriptor containing all medium accepted to be connected to +//the tuner output pin.This insures contection to the correct Capture Filter pin. +// +//{2AEB4A94-FBB7-4FB1-8D74-243B91886EAB} + +const KSPIN_MEDIUM TransportPinMediums[] = +{ + { + GUID_SKYWALKER_TUNER_OUT_MEDIUM, + 0, + 0 + } +}; + +// +//This is the data range description of the capture input pin. +//This is same as the Outpin of the Tuner i.e. The Transport Pin +//The Output of the Tuner is given to the Capture thus it has to +//be same +// +const KS_DATARANGE_BDA_TRANSPORT FormatCaptureIn = +{ + //insert the KSDATARANGE and KSDATAFORMAT here + { + sizeof( KS_DATARANGE_BDA_TRANSPORT), //FormatSize + 0, //Flags - (N/A) + 0, //SampleSize - (N/A) + 0, //Reserved + { STATIC_KSDATAFORMAT_TYPE_STREAM }, //MajorFormat + { STATIC_KSDATAFORMAT_TYPE_MPEG2_TRANSPORT }, //SubFormat + { STATIC_KSDATAFORMAT_SPECIFIER_BDA_TRANSPORT } //Specifier + }, + //insert the BDA_TRANSPORT_INFO here + { + TRANSPORT_PACKET_SIZE, //ulcbPhyiscalPacket + TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE, //ulcbPhyiscalFrame + 0, //ulcbPhyiscalFrameAlignment (no requirement) + 0 //AvgTimePerFrame (not known) + } +}; + +const PKSDATARANGE CaptureInPinDataRanges[]={ + (PKSDATARANGE)&FormatCaptureIn, +}; + +//Capture Outout Pin Definitions +const KSPIN_DISPATCH CaptureOutputPinDispatch={ + /* Create */ CCapturePin::PinCreate, + /* Close */ NULL, + /* Process */ CCapturePin::DispatchProcess, + /* Reset */ NULL, + /* SetDataFormat */ NULL, + /* SetDeviceState */ CCapturePin::DispatchSetState, + /* Connect */ NULL, + /* Disconnect */ NULL, + /* Allocator */ NULL +}; + + +// +//This is the data range description of the capture output pin. +// +const KSDATARANGE FormatCaptureOut = +{ + //insert the KSDATARANGE and KSDATAFORMAT here + { + sizeof( KSDATARANGE), //FormatSize + 0, //Flags - (N/A) + TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE, //SampleSize + 0, //Reserved + { STATIC_KSDATAFORMAT_TYPE_STREAM }, //MajorFormat + { STATIC_KSDATAFORMAT_SUBTYPE_BDA_MPEG2_TRANSPORT },//SubFormat + { STATIC_KSDATAFORMAT_SPECIFIER_NONE } //Specifier + } +}; + +const PKSDATARANGE CaptureOutPinDataRanges[]={ + (PKSDATARANGE)&FormatCaptureOut, +}; + +// +//CapturePinAllocatorFraming: +// +//This is the simple framing structure for the capture pin. Note that this +//will be modified via KsEdit when the actual capture format is determined. +// +DECLARE_SIMPLE_FRAMING_EX ( + CapturePinAllocatorFraming, //FramingExName + STATICGUIDOF (KSMEMORY_TYPE_KERNEL_NONPAGED), //MemoryType + KSALLOCATOR_REQUIREMENTF_SYSTEM_MEMORY | + KSALLOCATOR_REQUIREMENTF_PREFERENCES_ONLY, //Flags + NUMBER_OF_FRAMES, //Frames + 0, //Alignment + TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE, //MinFrameSize + TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE //MaxFrameSize + ); + +/**********************************************************************************/ + +//Not Supporting Filter Methods,Properties and Events +DEFINE_KSAUTOMATION_TABLE(SkyWalker1CaptureAutomationTable) +{ + DEFINE_KSAUTOMATION_PROPERTIES_NULL, + DEFINE_KSAUTOMATION_METHODS_NULL, + DEFINE_KSAUTOMATION_EVENTS_NULL +}; +/**********************************************************************************/ +// +//CaptureFilterDispatch: +// +//This is the dispatch table for the capture filter. It provides notification +//of creation, closure, processing (for filter-centrics, not for the capture +//filter), and resets (for filter-centrics, not for the capture filter). +// +const KSFILTER_DISPATCH SkyWalker1CaptureDispatchTable = +{ + /* Create */ CCaptureFilter::Create, //Routine called when the Filter is created + /* Close */ NULL, //Routine called when the Filter is closed + /* Process */ NULL, + /* Reset */ NULL +}; + +// +//Capture Pin Descriptors +// +//This data structure defines the pin types available in the filters +//template topology. These structures will be used to create a +//KDPinFactory for a pin type when BdaCreatePin or BdaMethodCreatePin +//are called. +// +//This structure defines ALL pins the filter is capable of supporting, +//including those pins which may only be created dynamically by a ring +//3 component such as a Network Provider. + +//The list of pin descriptors on the capture filter. +const KSPIN_DESCRIPTOR_EX SkyWalker1CapturePinDescriptors[]={ + { //Capture Filter input pin + &CaptureInputPinDispatch, //Dispatch Table + &NullAutomation, //Automation Table + { + 0, //Interfaces + NULL, + SIZEOF_ARRAY(TransportPinMediums), //Medium Count + TransportPinMediums, //Medium + SIZEOF_ARRAY(CaptureInPinDataRanges), //Range Count + CaptureInPinDataRanges, //Ranges + KSPIN_DATAFLOW_IN, //Specifies that data flow is into the pin + KSPIN_COMMUNICATION_BOTH, //Specifies that the pin factory instantiates pins + //that are both IRP sinks and IRP sources + (GUID *) &PINNAME_BDA_TRANSPORT, //Category GUID + (GUID *) &PINNAME_BDA_TRANSPORT, //GUID of the localized Unicode string + 0 + }, + KSPIN_FLAG_DO_NOT_USE_STANDARD_TRANSPORT| + KSPIN_FLAG_FRAMES_NOT_REQUIRED_FOR_PROCESSING| + KSPIN_FLAG_FIXED_FORMAT, + 1, //Maximum Possible Instances of the Pin + 1, //Mandatory Instances of this for the Filter function + NULL,//Allocator Framing + NULL //Data Interaction Handler + }, + { //Capture Filter output pin + &CaptureOutputPinDispatch, //Dispatch Table + &NullAutomation, //Automation Table + { + NULL, + 0, + NULL, + 0, + SIZEOF_ARRAY(CaptureOutPinDataRanges), //Range Count + CaptureOutPinDataRanges, + KSPIN_DATAFLOW_OUT, //Specifies that data flow is out of the pin + KSPIN_COMMUNICATION_BOTH,//Specifies that the pin factory instantiates pins + //that are both IRP sinks and IRP sources + (GUID *) &PINNAME_BDA_TRANSPORT, //Category GUID + (GUID *) &PINNAME_BDA_TRANSPORT, //GUID of the localized Unicode string + 0 + }, +#if !defined(_BUILD_SW_TUNER_ON_X64) + KSPIN_FLAG_GENERATE_MAPPINGS | //Pin Flags +#endif + KSPIN_FLAG_PROCESS_IN_RUN_STATE_ONLY, + 1,//Maximum Possible Instances of the Pin + 1,//Mandatory Instances of this for the Filter function + &CapturePinAllocatorFraming, + NULL + }, +}; + + +/*****************************************************************************************/ +//Define BDA Template Topology Connections +// +//Lists the Connections that are possible between pin types and +//node types. This, together with the Template Filter Descriptor, and +//the Pin Pairings, describe how topologies can be created in the filter. +// +// ================= +//TransportPin ----| Capture Filter | +// ================= +// +//The Capture Filter is controlled by the Transport input pin. +//Capture Filter properties will be set as NODE properties (with NodeType == 0) +//on the filter's Tranport Pin +// +const KSTOPOLOGY_CONNECTION SkyWalker1CaptureConnections[]={ + {KSFILTER_NODE, 0, KSFILTER_NODE, KSNODEPIN_STANDARD_IN}, //Transport pin -> Capture Filter pin 0 +}; + +/*****************************************************************************************/ + +//Define the Filter Factory Descriptor for the filter +//This structure brings together all of the structures that define +//the tuner filter as it appears when it is first instantiated. +//Note that not all of the template pin and node types may be exposed as +//pin and node factories when the filter is first instanciated. + +//The KSFILTER_DESCRIPTOR structure describes the characteristics of a filter created by a given filter factory. +DEFINE_KSFILTER_DESCRIPTOR(SkyWalker1CaptureFilterDescriptor) +{ + &SkyWalker1CaptureDispatchTable, //Dispatch (Filter Specific Driver) + NULL, //AutomationTable + KSFILTER_DESCRIPTOR_VERSION, //Version + 0, //Flags + &SKYWALKER_CAPTURE_FILTER, //ReferenceGuid + DEFINE_KSFILTER_PIN_DESCRIPTORS(SkyWalker1CapturePinDescriptors), + //PinDescriptorsCount; must expose at least one pin + //PinDescriptorSize; size of each item + //PinDescriptors; table of pin descriptors + DEFINE_KSFILTER_CATEGORY(KSCATEGORY_BDA_RECEIVER_COMPONENT), + //CategoriesCount; number of categories in the table + //Categories; table of categories + DEFINE_KSFILTER_NODE_DESCRIPTORS_NULL, + //NodeDescriptorsCount; + //NodeDescriptorSize; + //NodeDescriptors; + DEFINE_KSFILTER_CONNECTIONS(SkyWalker1CaptureConnections), + //Automatically fills in the connections table for a filter which defines no explicit connections + //ConnectionsCount; number of connections in the table + //Connections; table of connections + NULL //ComponentId; +}; + +//Array of BDA_PIN_PAIRING structures that are used to determine +//which nodes get duplicated when more than one output pin type is +//connected to a single input pin type or when more that one input pin +//type is connected to a single output pin type. +// +const BDA_PIN_PAIRING SkyWalker1CapturePinPairings[] = +{ + //Input pin to Output pin Topology Joints + // + { + 0, //ulInputPin; 0 element in the TemplatePinDescriptors array. + 1, //ulOutputPin; 1 element in the TemplatePinDescriptors array. + 1, //ulcMaxInputsPerOutput + 1, //ulcMinInputsPerOutput + 1, //ulcMaxOutputsPerInput + 1, //ulcMinOutputsPerInput + 0, //ulcTopologyJoints + NULL //pTopologyJoints; array of joints + } + //If applicable, list topology of joints between other pins. + // +}; + + +//BDA_FILTER_TEMPLATE structure describes the template topology for BDA Driver +const BDA_FILTER_TEMPLATE SkyWalker1CaptureTemplate = +{ + &SkyWalker1CaptureFilterDescriptor,//Pointer to KS_FILTER_DESCRIPTOR which describes the Filter for BDA Device + SIZEOF_ARRAY(SkyWalker1CapturePinPairings), //Number of PAIRS of pins in BDA_PIN_PAIRING Array + SkyWalker1CapturePinPairings //Array of Pin Pairing describes topology between a pair of Filter's Input and Output Pins +}; + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ /* End of Function prototype definitions */ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CapturePin.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CapturePin.cpp index ad5680b..9abc931 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CapturePin.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1CapturePin.cpp @@ -1,693 +1,693 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1CapturePin.cpp - Author : - Date : - Purpose : This file contains header for the video capture pin on - the capture filter. - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Common For all the Definitions, - //Declarations and Library Routines - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -VOID PrintStream(IN PKSSTREAM_POINTER pStreamPointer); -/* End of Function prototype definitions */ - - -/***************************************************************************** - Function : CCapturePin - Description : Constructor of the CCapturePin Class - IN PARAM : NONE - OUT PARAM : NONE - PreCondition : pKSPin Object is not created - PostCondtion : pKSPin Object is created and Initialzed on successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -CCapturePin::CCapturePin(IN PKSPIN pKSPin) : - m_Pin (pKSPin) -{ - PKSDEVICE pKSDevice = KsPinGetDevice (pKSPin); - - PrintFunctionEntry(__FUNCTION__); - - //Set up our device pointer. This gives us access to "Hardware I/O" - //during the capture routines. - m_Device = reinterpret_cast (pKSDevice->Context); - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); -} - -/***************************************************************************** - Function : CCapturePin - Description : Destructor of the CCapturePin Class - IN PARAM : NONE - OUT PARAM : NONE - PreCondition : pKSPin Object is created - PostCondtion : pKSPin Object is Removed and Memory freed - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -CCapturePin::~CCapturePin() -{ - PrintFunctionEntry(__FUNCTION__); - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); -} - -/***************************************************************************** - Function : CCapturePin::PinCreate - Description : An AVStream minidriver's AVStrMiniPinCreate routine is - called when a pin is created. Typically, this routine is - used by minidrivers that want to initialize the context - and resources associated with the pin. - IN PARAM : Pointer to the KSPIN that was just created. - Pointer to the IRP_MJ_CREATE for pKSPin - OUT PARAM : STATUS_SUCCESS in case of successful pin creation - Failure Code in other cases - PreCondition : None - PostCondtion : Create a new capture pin. This is the creation dispatch for - the video capture pin. - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CCapturePin::PinCreate ( IN PKSPIN pKSPin, - IN PIRP pIoRequestPacket - ) -{ - - NTSTATUS ntCreateStatus = STATUS_SUCCESS; - PBDA_TRANSPORT_INFO pTransportInfo = NULL; - CCapturePin *pCapturePin = new (NonPagedPool, CAPTURE_MEM_TAG) CCapturePin (pKSPin); - - PrintFunctionEntry(__FUNCTION__); - - if (!IS_VALID(pCapturePin)) - { - //Return failure if we couldn't create the pin. - ntCreateStatus = STATUS_INSUFFICIENT_RESOURCES; - - } - else - { - //Add the item to the object bag if we we were successful. - //Whenever the pin closes, the bag is cleaned up and we will be - //freed. - - ntCreateStatus = KsAddItemToObjectBag ( - pKSPin->Bag, - reinterpret_cast (pCapturePin), - reinterpret_cast (CCapturePin::Cleanup) - ); - - if (!NT_SUCCESS (ntCreateStatus)) - { - delete pCapturePin; - } - else - { - pKSPin->Context = reinterpret_cast (pCapturePin); - } - - } - - //If we succeeded so far, stash the video info header away and change - //our allocator framing to reflect the fact that only now do we know - //the framing requirements based on the connection format. - if (NT_SUCCESS (ntCreateStatus)) - { - - pTransportInfo = pCapturePin->CaptureBdaTransportInfo(); - if (!pTransportInfo) - { - ntCreateStatus = STATUS_INSUFFICIENT_RESOURCES; - } - } - - if (NT_SUCCESS (ntCreateStatus)) - { - //We need to edit the descriptor to ensure we don't mess up any other - //pins using the descriptor or touch read-only memory. - - ntCreateStatus = KsEdit (pKSPin, &pKSPin->Descriptor, CAPTURE_MEM_TAG); - - if (NT_SUCCESS (ntCreateStatus)) - { - ntCreateStatus = KsEdit ( - pKSPin, - &(pKSPin->Descriptor->AllocatorFraming), - CAPTURE_MEM_TAG - ); - } - - //If the edits proceeded without running out of memory, adjust - //the framing based on the video info header. - if (NT_SUCCESS (ntCreateStatus)) - { - - //We've KsEdit'ed this... I'm safe to cast away constness as - //long as the edit succeeded. - PKSALLOCATOR_FRAMING_EX pFraming = - const_cast ( - pKSPin->Descriptor-> AllocatorFraming - ); - - pFraming->FramingItem[0].Frames = NUMBER_OF_FRAMES; - - //The physical and optimal ranges must be biSizeImage. We only - //support one frame size, precisely the size of each capture - //image. - pFraming->FramingItem[0].PhysicalRange.MinFrameSize = - pFraming->FramingItem[0].PhysicalRange.MaxFrameSize = - pFraming->FramingItem[0].FramingRange.Range.MinFrameSize = - pFraming->FramingItem[0].FramingRange.Range.MaxFrameSize = - pTransportInfo->ulcbPhyiscalFrame; - - pFraming->FramingItem[0].PhysicalRange.Stepping = - pFraming->FramingItem[0].FramingRange.Range.Stepping = - 0; - - } - - } - - PrintFunctionExit(__FUNCTION__,ntCreateStatus); - return ntCreateStatus; - -} - -/***************************************************************************** - Function : CCapturePin::CaptureBdaTransportInfo - Description : Capture the video info header out of the connection format. - This is what we use to base synthesized images off. - IN PARAM : NONE - OUT PARAM : The captured video info header or - NULL if there is insufficient memory. - PreCondition : None - PostCondtion : Create a new capture pin. This is the creation dispatch for - the video capture pin. - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -PBDA_TRANSPORT_INFO CCapturePin::CaptureBdaTransportInfo() -{ - PrintFunctionEntry(__FUNCTION__); - - m_TransportInfo = reinterpret_cast ( - ExAllocatePoolWithTag ( - NonPagedPool, - sizeof(BDA_TRANSPORT_INFO), - CAPTURE_MEM_TAG - ) - ); - - if (!IS_VALID(m_TransportInfo)) - { - return NULL; - } - - //Bag the newly allocated header space. This will get cleaned up - //automatically when the pin closes. - NTSTATUS Status = - KsAddItemToObjectBag ( - m_Pin->Bag, - reinterpret_cast (m_TransportInfo), - NULL - ); - - if (!NT_SUCCESS (Status)) - { - ExFreePoolWithTag (m_TransportInfo, CAPTURE_MEM_TAG); - return NULL; - - } - else - { - m_TransportInfo->ulcbPhyiscalPacket = TRANSPORT_PACKET_SIZE; - m_TransportInfo->ulcbPhyiscalFrame = TRANSPORT_PACKET_SIZE * TRANSPORT_PACKET_COUNT; - m_TransportInfo->ulcbPhyiscalFrameAlignment = 1; - m_TransportInfo->AvgTimePerFrame = ((ULONGLONG)(19200)/* Maximum Sample Frequency */ * - 10000 /* Maximum Bits Per second */ * - NUMBER_OF_FRAMES) /*Maximum Channels */ / - (TRANSPORT_PACKET_SIZE * TRANSPORT_PACKET_COUNT); - - } - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); - return m_TransportInfo; - -} - -/***************************************************************************** - Function : CCapturePin::CleanupReferences - Description : Clean up any references we're holding on frames after - we abruptly stop the hardware. - IN PARAM : NONE - OUT PARAM : Success / Failure - PreCondition : NONE - PostCondtion : NONE - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CCapturePin::CleanupReferences () -{ - PKSSTREAM_POINTER pCloneStream = KsPinGetFirstCloneStreamPointer(m_Pin); - PKSSTREAM_POINTER pNextCloneStream = NULL; - - PrintFunctionEntry(__FUNCTION__); - - //Walk through the clones, deleting them, and setting DataUsed to - //zero since we didn't use any data! - while (pCloneStream) - { - - pNextCloneStream = KsStreamPointerGetNextClone(pCloneStream); - - pCloneStream->StreamHeader->DataUsed = 0; - KsStreamPointerDelete (pCloneStream); - - pCloneStream = pNextCloneStream; - - } - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); - return STATUS_SUCCESS; -} - -/***************************************************************************** - Function : CCapturePin::SetState - Description : This is called when the caputre pin transitions state. - The routine attempts to acquire / release any hardware - resources and start up or shut down capture based on - the states we are transitioning to and away from. - IN PARAM : ToState : The state we're transitioning to - FromState : The state we're transitioning away from - OUT PARAM : Success / Failure - PreCondition : NONE - PostCondtion : NONE - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CCapturePin::SetState ( - IN KSSTATE ToState, - IN KSSTATE FromState - ) -{ - - NTSTATUS ntSetStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - PrintDeviceChangeState(ToState,FromState); - - switch (ToState) - { - - case KSSTATE_STOP: - //Stopping the Device Operation - if (m_HardwareState != HardwareStopped) - { - ntSetStatus = m_Device->StopStream(); - m_HardwareState = HardwareStopped; - } - - - //The Device is Stopped - //It has cancelled the IRPs and Stopped the Streaming - //In case any Streaming Pointer is left Clean it up - ntSetStatus = CleanupReferences(); - - //Release any hardware resources related to this pin. - if (m_AcquiredResources) - { - //Release the Clock Reference on the Pin - if (m_Clock) - { - m_Clock->Release(); - m_Clock = NULL; - } - - m_Device->RemoveCaptureSink(); - m_AcquiredResources = FALSE; - } - break; - - case KSSTATE_ACQUIRE: - - //Acquire Hardware resources here instead of Filter - //Creation Time.So that the Filter creation does not - //Fail because of Limited Hardware resources. - if (FromState == KSSTATE_STOP) - { - ntSetStatus = m_Device->SetupCaptureSink(this,m_TransportInfo); - - if (NT_SUCCESS (ntSetStatus)) - { - m_AcquiredResources = TRUE; - - //Attempt to get an interface to the master clock. - //This will fail if one has not been assigned. Since - //one must be assigned while the pin is still in - //KSSTATE_STOP, this is a guranteed method of getting - //the clock should one be assigned. - - if (!NT_SUCCESS (KsPinGetReferenceClockInterface(m_Pin, - &m_Clock))) - { - //If we could not get an interface to the clock, - //don't use one. - SkyWalkerDebugPrint(ENTRY_LEVEL,("No Clock Assigned to the Pin\n")); - m_Clock = NULL; - - } - - } - else - { - m_AcquiredResources = FALSE; - } - - } - else - { - // - //Standard transport pins will always receive transitions in - //+/- 1 manner. This means we'll always see a PAUSE->ACQUIRE - //transition before stopping the pin. - // - //The below is done because on DirectX 8.0, when the pin gets - //a message to stop, the queue is inaccessible. The reset - //which comes on every stop happens after this (at which time - //the queue is inaccessible also). So, for compatibility with - //DirectX 8.0, I am stopping the hardware at this - //point and cleaning up all references we have on frames.See - //the comments above regarding the CleanupReferences call. - - if (m_HardwareState != HardwareStopped) - { - ntSetStatus = m_Device->StopStream(); - m_HardwareState = HardwareStopped; - } - - ntSetStatus = CleanupReferences (); - } - - break; - - case KSSTATE_PAUSE: - - //Stop the Streaming if we're coming down from run. - if (FromState == KSSTATE_RUN) - { - - ntSetStatus = m_Device->PauseStream(TRUE); - - if (NT_SUCCESS (ntSetStatus)) - { - m_HardwareState = HardwarePaused; - } - - } - break; - - case KSSTATE_RUN: - - //Start the Streaming or unpause it depending on - //whether we're initially running or we've paused and restarted. - if (m_HardwareState == HardwarePaused) - { - ntSetStatus = m_Device->PauseStream (FALSE); - } - else - { - ntSetStatus = m_Device->StartStream(); - } - - if (NT_SUCCESS (ntSetStatus)) - { - m_HardwareState = HardwareRunning; - } - - break; - - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Completed the State Change\n")); - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; - -} - -/***************************************************************************** - Function : CCapturePin::Process - Description : The process dispatch for the pin bridges to this location. - We handle setting up scatter gather mappings, etc... - IN PARAM : NONE - OUT PARAM : Success / Failure - PreCondition : NONE - PostCondtion : NONE - Logic : NONE - Assumption : NONE - Note : Future Approach for the Streaming Buffer - 1) Create a Buffer of Size Stream->Data/m_SampleSize Here - 2) Store the reference of the Newly Created Buffer into the - Stream Context - 3) Send the Buffer to the ReadStream(BufferPointer) - 4) When CompleteMapping is returned Copy Data from Stream - Context to Stream->Data - Revision History: - *****************************************************************************/ -NTSTATUS CCapturePin::Process() -{ - NTSTATUS ntProcessStatus = STATUS_SUCCESS; - PKSSTREAM_POINTER pLeadingStream = NULL; - PKSSTREAM_POINTER pCloneStream = NULL; - PSTREAM_POINTER_CONTEXT pStreamContext = NULL; - - PrintFunctionEntry(__FUNCTION__); - - pLeadingStream = KsPinGetLeadingEdgeStreamPointer ( - m_Pin, - KSSTREAM_POINTER_STATE_LOCKED - ); - - - if( !pLeadingStream ) - { - //no system buffer available - //This case can happen if it is the last pointer in the queue or - //the system cannot give us the required buffer - SkyWalkerDebugPrint(ENTRY_LEVEL,("Warning: No system buffer available\n")); - ntProcessStatus = STATUS_UNSUCCESSFUL; - goto CompleteProcessing; - } - else - { - - //First thing we need to do is clone the leading edge. This allows - //us to keep reference on the frames while they're in DMA. - ntProcessStatus = KsStreamPointerClone ( - pLeadingStream, - NULL, - sizeof (STREAM_POINTER_CONTEXT), - &pCloneStream - ); - - if( !NT_SUCCESS(ntProcessStatus) ) - { - //No System Buffer Available - SkyWalkerDebugPrint(ENTRY_LEVEL, - ("Error: Streampointer cloning unsuccessful\n")); - ntProcessStatus = STATUS_UNSUCCESSFUL; - goto CompleteProcessing; - } - - //Is the buffer size correct - if( pCloneStream->StreamHeader->FrameExtent < - (static_cast (m_TransportInfo->ulcbPhyiscalFrame)) ) - { - //Buffer size incorrect - KsStreamPointerDelete(pCloneStream); //void function - SkyWalkerDebugPrint(ENTRY_LEVEL,("Error: Buffer size Incorrect\n")); - ntProcessStatus = STATUS_UNSUCCESSFUL; - goto CompleteProcessing; - } - - //Set the stream header data used to 0. We update this - //in the USB Data Read completions. - pCloneStream->StreamHeader->DataUsed = 0; - - pStreamContext = reinterpret_cast - (pCloneStream->Context); - - //Set the Stream Index - pStreamContext->ulFrameIndex = m_CurrentFrameIndex; - - PrintStream(pLeadingStream); - - //(Refer NOTE above) Create a Stream Buffer Here and Submit - //it for the Reading / DMA - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Current Stream Index = %lu",m_CurrentFrameIndex)); - m_Device->ReadStream(m_CurrentFrameIndex); - m_CurrentFrameIndex = (m_CurrentFrameIndex+1) % NUMBER_OF_FRAMES; - - //Advance Stream pointer to the next available data frame - ntProcessStatus = KsStreamPointerAdvance(pLeadingStream); - - if( (ntProcessStatus != STATUS_DEVICE_NOT_READY) && - (ntProcessStatus != STATUS_SUCCESS) ) - { - SkyWalkerDebugPrint(ENTRY_LEVEL, - ("Error: Video Capture Streampointer Advacement Failed\n")); - } - } - -CompleteProcessing: - - PrintFunctionExit(__FUNCTION__,ntProcessStatus); - return ntProcessStatus; -} - -/***************************************************************************** - Function : CCapturePin::ReleaseStream - Description : Called to notify the pin that a given Stream is completed - IN PARAM : The Stream Index - OUT PARAM : NONE - PreCondition : NONE - PostCondtion : Stream data is filled from the Internal Stream Buffer - Other Stream Parameters are set and Clone is Deleted - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -void CCapturePin::ReleaseStream(IN ULONG ulStreamIndex) -{ - - PrintFunctionEntry(__FUNCTION__); - - //Walk through the clones list and delete clones whose time has come. - //The list is guaranteed to be kept in the order they were cloned. - SkyWalkerDebugPrint(EXTREME_LEVEL,("Completing Stream %lu\n",ulStreamIndex)); - - PKSSTREAM_POINTER pCloneStream = KsPinGetFirstCloneStreamPointer (m_Pin); - SkyWalkerDebugPrint(EXTREME_LEVEL,("Clone Stream pointer = 0x%p\n",pCloneStream)); - - if(pCloneStream) - { - //Copy the Stream data from the Corresponding Streaming Buffer - RtlCopyMemory((PUCHAR)pCloneStream->StreamHeader->Data, - m_Device->GetSynthBuffer(ulStreamIndex), - m_TransportInfo->ulcbPhyiscalFrame); - - pCloneStream->StreamHeader->DataUsed = m_TransportInfo->ulcbPhyiscalFrame; - - SkyWalkerDebugPrint(EXTREME_LEVEL,("pCloneStream->StreamHeader->DataUsed = " - "%lu\n", - pCloneStream->StreamHeader->DataUsed)); - - pCloneStream->StreamHeader->Duration = m_TransportInfo->AvgTimePerFrame; - - pCloneStream->StreamHeader->PresentationTime.Numerator = - pCloneStream->StreamHeader->PresentationTime.Denominator = 1; - - //If a clock has been assigned, timestamp the packets with the - //time shown on the clock. - if (m_Clock) - { - - LONGLONG ClockTime = m_Clock->GetTime (); - pCloneStream->StreamHeader->PresentationTime.Time = ClockTime; - pCloneStream->StreamHeader->OptionsFlags = - KSSTREAM_HEADER_OPTIONSF_TIMEVALID | - KSSTREAM_HEADER_OPTIONSF_DURATIONVALID; - - } - else - { - - //If there is no clock, don't time stamp the packets. - pCloneStream->StreamHeader->PresentationTime.Time = 0; - - } - - PrintStream(pCloneStream); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Stream Processed thus deleting the Clone\n")); - KsStreamPointerDelete (pCloneStream); - - } - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); - -} - -VOID PrintStream(IN PKSSTREAM_POINTER pStreamPointer) -{ - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Context = 0x%p\n",pStreamPointer->Context)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Pin = 0x%p\n",pStreamPointer->Pin)); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader = 0x%p\n",pStreamPointer->StreamHeader)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->Size = %lu\n",pStreamPointer->StreamHeader->Size)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->TypeSpecificFlags = %lu\n",pStreamPointer->StreamHeader->TypeSpecificFlags)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->PresentationTime.Time= %l\n",pStreamPointer->StreamHeader->PresentationTime.Time)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->PresentationTime.Numerator= %lu\n",pStreamPointer->StreamHeader->PresentationTime.Numerator)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->PresentationTime.Denominator= %lu\n",pStreamPointer->StreamHeader->PresentationTime.Denominator)); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->Duration = %l\n",pStreamPointer->StreamHeader->Duration)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->FrameExtent = %lu\n",pStreamPointer->StreamHeader->FrameExtent)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->DataUsed = %lu\n",pStreamPointer->StreamHeader->DataUsed)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->Data = 0x%p\n",pStreamPointer->StreamHeader->Data)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->OptionsFlags = %lu\n",pStreamPointer->StreamHeader->OptionsFlags)); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Offset = 0x%p\n",pStreamPointer->Offset)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Offset->Data = 0x%p\n",pStreamPointer->Offset->Data)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Offset->Mappings = 0x%p\n",pStreamPointer->Offset->Mappings)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Offset->Count = %lu\n",pStreamPointer->Offset->Count)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Offset->Remaining = %lu\n",pStreamPointer->Offset->Remaining)); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetIn.Data = 0x%p\n",pStreamPointer->OffsetIn.Data)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetIn.Mappings = 0x%p\n",pStreamPointer->OffsetIn.Mappings)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetIn.Count = %lu\n",pStreamPointer->OffsetIn.Count)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetIn.Remaining = %lu\n",pStreamPointer->OffsetIn.Remaining)); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetOut.Data = 0x%p\n",pStreamPointer->OffsetOut.Data)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetOut.Mappings = 0x%p\n",pStreamPointer->OffsetOut.Mappings)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetOut.Count = %lu\n",pStreamPointer->OffsetOut.Count)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetOut.Remaining = %lu\n",pStreamPointer->OffsetOut.Remaining)); +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1CapturePin.cpp + Author : + Date : + Purpose : This file contains header for the video capture pin on + the capture filter. + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Common For all the Definitions, + //Declarations and Library Routines + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +VOID PrintStream(IN PKSSTREAM_POINTER pStreamPointer); +/* End of Function prototype definitions */ + + +/***************************************************************************** + Function : CCapturePin + Description : Constructor of the CCapturePin Class + IN PARAM : NONE + OUT PARAM : NONE + PreCondition : pKSPin Object is not created + PostCondtion : pKSPin Object is created and Initialzed on successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +CCapturePin::CCapturePin(IN PKSPIN pKSPin) : + m_Pin (pKSPin) +{ + PKSDEVICE pKSDevice = KsPinGetDevice (pKSPin); + + PrintFunctionEntry(__FUNCTION__); + + //Set up our device pointer. This gives us access to "Hardware I/O" + //during the capture routines. + m_Device = reinterpret_cast (pKSDevice->Context); + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); +} + +/***************************************************************************** + Function : CCapturePin + Description : Destructor of the CCapturePin Class + IN PARAM : NONE + OUT PARAM : NONE + PreCondition : pKSPin Object is created + PostCondtion : pKSPin Object is Removed and Memory freed + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +CCapturePin::~CCapturePin() +{ + PrintFunctionEntry(__FUNCTION__); + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); +} + +/***************************************************************************** + Function : CCapturePin::PinCreate + Description : An AVStream minidriver's AVStrMiniPinCreate routine is + called when a pin is created. Typically, this routine is + used by minidrivers that want to initialize the context + and resources associated with the pin. + IN PARAM : Pointer to the KSPIN that was just created. + Pointer to the IRP_MJ_CREATE for pKSPin + OUT PARAM : STATUS_SUCCESS in case of successful pin creation + Failure Code in other cases + PreCondition : None + PostCondtion : Create a new capture pin. This is the creation dispatch for + the video capture pin. + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CCapturePin::PinCreate ( IN PKSPIN pKSPin, + IN PIRP pIoRequestPacket + ) +{ + + NTSTATUS ntCreateStatus = STATUS_SUCCESS; + PBDA_TRANSPORT_INFO pTransportInfo = NULL; + CCapturePin *pCapturePin = new (NonPagedPool, CAPTURE_MEM_TAG) CCapturePin (pKSPin); + + PrintFunctionEntry(__FUNCTION__); + + if (!IS_VALID(pCapturePin)) + { + //Return failure if we couldn't create the pin. + ntCreateStatus = STATUS_INSUFFICIENT_RESOURCES; + + } + else + { + //Add the item to the object bag if we we were successful. + //Whenever the pin closes, the bag is cleaned up and we will be + //freed. + + ntCreateStatus = KsAddItemToObjectBag ( + pKSPin->Bag, + reinterpret_cast (pCapturePin), + reinterpret_cast (CCapturePin::Cleanup) + ); + + if (!NT_SUCCESS (ntCreateStatus)) + { + delete pCapturePin; + } + else + { + pKSPin->Context = reinterpret_cast (pCapturePin); + } + + } + + //If we succeeded so far, stash the video info header away and change + //our allocator framing to reflect the fact that only now do we know + //the framing requirements based on the connection format. + if (NT_SUCCESS (ntCreateStatus)) + { + + pTransportInfo = pCapturePin->CaptureBdaTransportInfo(); + if (!pTransportInfo) + { + ntCreateStatus = STATUS_INSUFFICIENT_RESOURCES; + } + } + + if (NT_SUCCESS (ntCreateStatus)) + { + //We need to edit the descriptor to ensure we don't mess up any other + //pins using the descriptor or touch read-only memory. + + ntCreateStatus = KsEdit (pKSPin, &pKSPin->Descriptor, CAPTURE_MEM_TAG); + + if (NT_SUCCESS (ntCreateStatus)) + { + ntCreateStatus = KsEdit ( + pKSPin, + &(pKSPin->Descriptor->AllocatorFraming), + CAPTURE_MEM_TAG + ); + } + + //If the edits proceeded without running out of memory, adjust + //the framing based on the video info header. + if (NT_SUCCESS (ntCreateStatus)) + { + + //We've KsEdit'ed this... I'm safe to cast away constness as + //long as the edit succeeded. + PKSALLOCATOR_FRAMING_EX pFraming = + const_cast ( + pKSPin->Descriptor-> AllocatorFraming + ); + + pFraming->FramingItem[0].Frames = NUMBER_OF_FRAMES; + + //The physical and optimal ranges must be biSizeImage. We only + //support one frame size, precisely the size of each capture + //image. + pFraming->FramingItem[0].PhysicalRange.MinFrameSize = + pFraming->FramingItem[0].PhysicalRange.MaxFrameSize = + pFraming->FramingItem[0].FramingRange.Range.MinFrameSize = + pFraming->FramingItem[0].FramingRange.Range.MaxFrameSize = + pTransportInfo->ulcbPhyiscalFrame; + + pFraming->FramingItem[0].PhysicalRange.Stepping = + pFraming->FramingItem[0].FramingRange.Range.Stepping = + 0; + + } + + } + + PrintFunctionExit(__FUNCTION__,ntCreateStatus); + return ntCreateStatus; + +} + +/***************************************************************************** + Function : CCapturePin::CaptureBdaTransportInfo + Description : Capture the video info header out of the connection format. + This is what we use to base synthesized images off. + IN PARAM : NONE + OUT PARAM : The captured video info header or + NULL if there is insufficient memory. + PreCondition : None + PostCondtion : Create a new capture pin. This is the creation dispatch for + the video capture pin. + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +PBDA_TRANSPORT_INFO CCapturePin::CaptureBdaTransportInfo() +{ + PrintFunctionEntry(__FUNCTION__); + + m_TransportInfo = reinterpret_cast ( + ExAllocatePoolWithTag ( + NonPagedPool, + sizeof(BDA_TRANSPORT_INFO), + CAPTURE_MEM_TAG + ) + ); + + if (!IS_VALID(m_TransportInfo)) + { + return NULL; + } + + //Bag the newly allocated header space. This will get cleaned up + //automatically when the pin closes. + NTSTATUS Status = + KsAddItemToObjectBag ( + m_Pin->Bag, + reinterpret_cast (m_TransportInfo), + NULL + ); + + if (!NT_SUCCESS (Status)) + { + ExFreePoolWithTag (m_TransportInfo, CAPTURE_MEM_TAG); + return NULL; + + } + else + { + m_TransportInfo->ulcbPhyiscalPacket = TRANSPORT_PACKET_SIZE; + m_TransportInfo->ulcbPhyiscalFrame = TRANSPORT_PACKET_SIZE * TRANSPORT_PACKET_COUNT; + m_TransportInfo->ulcbPhyiscalFrameAlignment = 1; + m_TransportInfo->AvgTimePerFrame = ((ULONGLONG)(19200)/* Maximum Sample Frequency */ * + 10000 /* Maximum Bits Per second */ * + NUMBER_OF_FRAMES) /*Maximum Channels */ / + (TRANSPORT_PACKET_SIZE * TRANSPORT_PACKET_COUNT); + + } + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); + return m_TransportInfo; + +} + +/***************************************************************************** + Function : CCapturePin::CleanupReferences + Description : Clean up any references we're holding on frames after + we abruptly stop the hardware. + IN PARAM : NONE + OUT PARAM : Success / Failure + PreCondition : NONE + PostCondtion : NONE + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CCapturePin::CleanupReferences () +{ + PKSSTREAM_POINTER pCloneStream = KsPinGetFirstCloneStreamPointer(m_Pin); + PKSSTREAM_POINTER pNextCloneStream = NULL; + + PrintFunctionEntry(__FUNCTION__); + + //Walk through the clones, deleting them, and setting DataUsed to + //zero since we didn't use any data! + while (pCloneStream) + { + + pNextCloneStream = KsStreamPointerGetNextClone(pCloneStream); + + pCloneStream->StreamHeader->DataUsed = 0; + KsStreamPointerDelete (pCloneStream); + + pCloneStream = pNextCloneStream; + + } + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); + return STATUS_SUCCESS; +} + +/***************************************************************************** + Function : CCapturePin::SetState + Description : This is called when the caputre pin transitions state. + The routine attempts to acquire / release any hardware + resources and start up or shut down capture based on + the states we are transitioning to and away from. + IN PARAM : ToState : The state we're transitioning to + FromState : The state we're transitioning away from + OUT PARAM : Success / Failure + PreCondition : NONE + PostCondtion : NONE + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CCapturePin::SetState ( + IN KSSTATE ToState, + IN KSSTATE FromState + ) +{ + + NTSTATUS ntSetStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + PrintDeviceChangeState(ToState,FromState); + + switch (ToState) + { + + case KSSTATE_STOP: + //Stopping the Device Operation + if (m_HardwareState != HardwareStopped) + { + ntSetStatus = m_Device->StopStream(); + m_HardwareState = HardwareStopped; + } + + + //The Device is Stopped + //It has cancelled the IRPs and Stopped the Streaming + //In case any Streaming Pointer is left Clean it up + ntSetStatus = CleanupReferences(); + + //Release any hardware resources related to this pin. + if (m_AcquiredResources) + { + //Release the Clock Reference on the Pin + if (m_Clock) + { + m_Clock->Release(); + m_Clock = NULL; + } + + m_Device->RemoveCaptureSink(); + m_AcquiredResources = FALSE; + } + break; + + case KSSTATE_ACQUIRE: + + //Acquire Hardware resources here instead of Filter + //Creation Time.So that the Filter creation does not + //Fail because of Limited Hardware resources. + if (FromState == KSSTATE_STOP) + { + ntSetStatus = m_Device->SetupCaptureSink(this,m_TransportInfo); + + if (NT_SUCCESS (ntSetStatus)) + { + m_AcquiredResources = TRUE; + + //Attempt to get an interface to the master clock. + //This will fail if one has not been assigned. Since + //one must be assigned while the pin is still in + //KSSTATE_STOP, this is a guranteed method of getting + //the clock should one be assigned. + + if (!NT_SUCCESS (KsPinGetReferenceClockInterface(m_Pin, + &m_Clock))) + { + //If we could not get an interface to the clock, + //don't use one. + SkyWalkerDebugPrint(ENTRY_LEVEL,("No Clock Assigned to the Pin\n")); + m_Clock = NULL; + + } + + } + else + { + m_AcquiredResources = FALSE; + } + + } + else + { + // + //Standard transport pins will always receive transitions in + //+/- 1 manner. This means we'll always see a PAUSE->ACQUIRE + //transition before stopping the pin. + // + //The below is done because on DirectX 8.0, when the pin gets + //a message to stop, the queue is inaccessible. The reset + //which comes on every stop happens after this (at which time + //the queue is inaccessible also). So, for compatibility with + //DirectX 8.0, I am stopping the hardware at this + //point and cleaning up all references we have on frames.See + //the comments above regarding the CleanupReferences call. + + if (m_HardwareState != HardwareStopped) + { + ntSetStatus = m_Device->StopStream(); + m_HardwareState = HardwareStopped; + } + + ntSetStatus = CleanupReferences (); + } + + break; + + case KSSTATE_PAUSE: + + //Stop the Streaming if we're coming down from run. + if (FromState == KSSTATE_RUN) + { + + ntSetStatus = m_Device->PauseStream(TRUE); + + if (NT_SUCCESS (ntSetStatus)) + { + m_HardwareState = HardwarePaused; + } + + } + break; + + case KSSTATE_RUN: + + //Start the Streaming or unpause it depending on + //whether we're initially running or we've paused and restarted. + if (m_HardwareState == HardwarePaused) + { + ntSetStatus = m_Device->PauseStream (FALSE); + } + else + { + ntSetStatus = m_Device->StartStream(); + } + + if (NT_SUCCESS (ntSetStatus)) + { + m_HardwareState = HardwareRunning; + } + + break; + + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Completed the State Change\n")); + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; + +} + +/***************************************************************************** + Function : CCapturePin::Process + Description : The process dispatch for the pin bridges to this location. + We handle setting up scatter gather mappings, etc... + IN PARAM : NONE + OUT PARAM : Success / Failure + PreCondition : NONE + PostCondtion : NONE + Logic : NONE + Assumption : NONE + Note : Future Approach for the Streaming Buffer + 1) Create a Buffer of Size Stream->Data/m_SampleSize Here + 2) Store the reference of the Newly Created Buffer into the + Stream Context + 3) Send the Buffer to the ReadStream(BufferPointer) + 4) When CompleteMapping is returned Copy Data from Stream + Context to Stream->Data + Revision History: + *****************************************************************************/ +NTSTATUS CCapturePin::Process() +{ + NTSTATUS ntProcessStatus = STATUS_SUCCESS; + PKSSTREAM_POINTER pLeadingStream = NULL; + PKSSTREAM_POINTER pCloneStream = NULL; + PSTREAM_POINTER_CONTEXT pStreamContext = NULL; + + PrintFunctionEntry(__FUNCTION__); + + pLeadingStream = KsPinGetLeadingEdgeStreamPointer ( + m_Pin, + KSSTREAM_POINTER_STATE_LOCKED + ); + + + if( !pLeadingStream ) + { + //no system buffer available + //This case can happen if it is the last pointer in the queue or + //the system cannot give us the required buffer + SkyWalkerDebugPrint(ENTRY_LEVEL,("Warning: No system buffer available\n")); + ntProcessStatus = STATUS_UNSUCCESSFUL; + goto CompleteProcessing; + } + else + { + + //First thing we need to do is clone the leading edge. This allows + //us to keep reference on the frames while they're in DMA. + ntProcessStatus = KsStreamPointerClone ( + pLeadingStream, + NULL, + sizeof (STREAM_POINTER_CONTEXT), + &pCloneStream + ); + + if( !NT_SUCCESS(ntProcessStatus) ) + { + //No System Buffer Available + SkyWalkerDebugPrint(ENTRY_LEVEL, + ("Error: Streampointer cloning unsuccessful\n")); + ntProcessStatus = STATUS_UNSUCCESSFUL; + goto CompleteProcessing; + } + + //Is the buffer size correct + if( pCloneStream->StreamHeader->FrameExtent < + (static_cast (m_TransportInfo->ulcbPhyiscalFrame)) ) + { + //Buffer size incorrect + KsStreamPointerDelete(pCloneStream); //void function + SkyWalkerDebugPrint(ENTRY_LEVEL,("Error: Buffer size Incorrect\n")); + ntProcessStatus = STATUS_UNSUCCESSFUL; + goto CompleteProcessing; + } + + //Set the stream header data used to 0. We update this + //in the USB Data Read completions. + pCloneStream->StreamHeader->DataUsed = 0; + + pStreamContext = reinterpret_cast + (pCloneStream->Context); + + //Set the Stream Index + pStreamContext->ulFrameIndex = m_CurrentFrameIndex; + + PrintStream(pLeadingStream); + + //(Refer NOTE above) Create a Stream Buffer Here and Submit + //it for the Reading / DMA + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Current Stream Index = %lu",m_CurrentFrameIndex)); + m_Device->ReadStream(m_CurrentFrameIndex); + m_CurrentFrameIndex = (m_CurrentFrameIndex+1) % NUMBER_OF_FRAMES; + + //Advance Stream pointer to the next available data frame + ntProcessStatus = KsStreamPointerAdvance(pLeadingStream); + + if( (ntProcessStatus != STATUS_DEVICE_NOT_READY) && + (ntProcessStatus != STATUS_SUCCESS) ) + { + SkyWalkerDebugPrint(ENTRY_LEVEL, + ("Error: Video Capture Streampointer Advacement Failed\n")); + } + } + +CompleteProcessing: + + PrintFunctionExit(__FUNCTION__,ntProcessStatus); + return ntProcessStatus; +} + +/***************************************************************************** + Function : CCapturePin::ReleaseStream + Description : Called to notify the pin that a given Stream is completed + IN PARAM : The Stream Index + OUT PARAM : NONE + PreCondition : NONE + PostCondtion : Stream data is filled from the Internal Stream Buffer + Other Stream Parameters are set and Clone is Deleted + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +void CCapturePin::ReleaseStream(IN ULONG ulStreamIndex) +{ + + PrintFunctionEntry(__FUNCTION__); + + //Walk through the clones list and delete clones whose time has come. + //The list is guaranteed to be kept in the order they were cloned. + SkyWalkerDebugPrint(EXTREME_LEVEL,("Completing Stream %lu\n",ulStreamIndex)); + + PKSSTREAM_POINTER pCloneStream = KsPinGetFirstCloneStreamPointer (m_Pin); + SkyWalkerDebugPrint(EXTREME_LEVEL,("Clone Stream pointer = 0x%p\n",pCloneStream)); + + if(pCloneStream) + { + //Copy the Stream data from the Corresponding Streaming Buffer + RtlCopyMemory((PUCHAR)pCloneStream->StreamHeader->Data, + m_Device->GetSynthBuffer(ulStreamIndex), + m_TransportInfo->ulcbPhyiscalFrame); + + pCloneStream->StreamHeader->DataUsed = m_TransportInfo->ulcbPhyiscalFrame; + + SkyWalkerDebugPrint(EXTREME_LEVEL,("pCloneStream->StreamHeader->DataUsed = " + "%lu\n", + pCloneStream->StreamHeader->DataUsed)); + + pCloneStream->StreamHeader->Duration = m_TransportInfo->AvgTimePerFrame; + + pCloneStream->StreamHeader->PresentationTime.Numerator = + pCloneStream->StreamHeader->PresentationTime.Denominator = 1; + + //If a clock has been assigned, timestamp the packets with the + //time shown on the clock. + if (m_Clock) + { + + LONGLONG ClockTime = m_Clock->GetTime (); + pCloneStream->StreamHeader->PresentationTime.Time = ClockTime; + pCloneStream->StreamHeader->OptionsFlags = + KSSTREAM_HEADER_OPTIONSF_TIMEVALID | + KSSTREAM_HEADER_OPTIONSF_DURATIONVALID; + + } + else + { + + //If there is no clock, don't time stamp the packets. + pCloneStream->StreamHeader->PresentationTime.Time = 0; + + } + + PrintStream(pCloneStream); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Stream Processed thus deleting the Clone\n")); + KsStreamPointerDelete (pCloneStream); + + } + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); + +} + +VOID PrintStream(IN PKSSTREAM_POINTER pStreamPointer) +{ + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Context = 0x%p\n",pStreamPointer->Context)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Pin = 0x%p\n",pStreamPointer->Pin)); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader = 0x%p\n",pStreamPointer->StreamHeader)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->Size = %lu\n",pStreamPointer->StreamHeader->Size)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->TypeSpecificFlags = %lu\n",pStreamPointer->StreamHeader->TypeSpecificFlags)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->PresentationTime.Time= %l\n",pStreamPointer->StreamHeader->PresentationTime.Time)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->PresentationTime.Numerator= %lu\n",pStreamPointer->StreamHeader->PresentationTime.Numerator)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->PresentationTime.Denominator= %lu\n",pStreamPointer->StreamHeader->PresentationTime.Denominator)); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->Duration = %l\n",pStreamPointer->StreamHeader->Duration)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->FrameExtent = %lu\n",pStreamPointer->StreamHeader->FrameExtent)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->DataUsed = %lu\n",pStreamPointer->StreamHeader->DataUsed)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->Data = 0x%p\n",pStreamPointer->StreamHeader->Data)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->StreamHeader->OptionsFlags = %lu\n",pStreamPointer->StreamHeader->OptionsFlags)); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Offset = 0x%p\n",pStreamPointer->Offset)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Offset->Data = 0x%p\n",pStreamPointer->Offset->Data)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Offset->Mappings = 0x%p\n",pStreamPointer->Offset->Mappings)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Offset->Count = %lu\n",pStreamPointer->Offset->Count)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->Offset->Remaining = %lu\n",pStreamPointer->Offset->Remaining)); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetIn.Data = 0x%p\n",pStreamPointer->OffsetIn.Data)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetIn.Mappings = 0x%p\n",pStreamPointer->OffsetIn.Mappings)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetIn.Count = %lu\n",pStreamPointer->OffsetIn.Count)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetIn.Remaining = %lu\n",pStreamPointer->OffsetIn.Remaining)); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetOut.Data = 0x%p\n",pStreamPointer->OffsetOut.Data)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetOut.Mappings = 0x%p\n",pStreamPointer->OffsetOut.Mappings)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetOut.Count = %lu\n",pStreamPointer->OffsetOut.Count)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamPointer->OffsetOut.Remaining = %lu\n",pStreamPointer->OffsetOut.Remaining)); } \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp index 0f698d2..1cd0f20 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp @@ -1,704 +1,704 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1Control.cpp - Author : - Date : - Purpose : This File Holds the Device Control related declarations - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Common For all the Definitions, - //Declarations and Library Routines - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -VOID PrintDiseqcCommand(PDISEQC_COMMAND pDiseqcCommand); -/* End of Function prototype definitions */ - -/***************************************************************************** - Function : GetSignalStatus - Description : This Function Get the Signal Lock Status - IN PARAM : Pointer to the KSDevice Object - true in case of Signal Locked else False - OUT PARAM : STATUS_SUCCESS in case of successful Lock Read - Failure Code in other cases - PreCondition : None - PostCondtion : Gets the Signal Lock Status in case of successful execution - Logic : NONE - Assumption : NONE - Note : None - Revision History: - *****************************************************************************/ -NTSTATUS GetSignalStatus( IN PKSDEVICE pKSDeviceObject, - OUT PBOOLEAN pbSignalLockStatus - ) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - UCHAR ucSignalStatus = 0; - - PrintFunctionEntry(__FUNCTION__); - - //gp8psk_usb_in_op(st->d, GET_SIGNAL_LOCK, 0, 0, &lock,1) - ntStatus = ControlUsbDevice( pKSDeviceObject, - GET_SIGNAL_LOCK, - 0, - 0, - &ucSignalStatus, - 1, - true); - if(NT_SUCCESS(ntStatus)) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("ucSignalStatus = 0x%02X\n",ucSignalStatus)); - - if(ucSignalStatus) - { - *pbSignalLockStatus = TRUE; - } - else - { - *pbSignalLockStatus = FALSE; - } - } - - //if (lock) - // *status = FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_SIGNAL | FE_HAS_CARRIER; - //else - // *status = 0; - - if(ucSignalStatus) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Lock = 0x%02X\n",*pbSignalLockStatus)); - } - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; - -} - -/***************************************************************************** - Function : ReadTunerSignalStrength - Description : This Function reads the Tuner Signal Strength - IN PARAM : Pointer to the KSDevice Object - Pointer to hold the Signal Strength - OUT PARAM : STATUS_SUCCESS in case of successful read - Failure Code in other cases - PreCondition : None - PostCondtion : Reads the Signal Strength - Logic : NONE - Assumption : NONE - Note : None - Revision History: - *****************************************************************************/ -NTSTATUS ReadTunerSignalStrength( IN PKSDEVICE pKSDeviceObject, - OUT PULONG pulSigStrength - ) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - UCHAR ucBuffer[6] = {0,0,0,0,0,0}; - ULONG ulSignalStrength = 0L; - - PrintFunctionEntry(__FUNCTION__); - - //gp8psk_usb_in_op(st->d, GET_SIGNAL_STRENGTH, 0,0,buf,6); - ntStatus = ControlUsbDevice( pKSDeviceObject, - GET_SIGNAL_STRENGTH, - 0, - 0, - ucBuffer, - 6, - true); - if(NT_SUCCESS(ntStatus)) - { - ulSignalStrength = (int)(ucBuffer[1]) << 8 | ucBuffer[0]; - SkyWalkerDebugPrint(EXTREME_LEVEL,("ulSignalStrength = %lu,ucBuffer[1] = 0x%02X, ucBuffer[2] = 0x%02X\n", - ulSignalStrength,ucBuffer[1],ucBuffer[0])); - //*pulSigStrength = (int)(ucBuffer[1]) << 8 | ucBuffer[0]; - /* snr is reported in dBu*256 */ - /* snr / 38.4 ~= 100% strength */ - /* snr * 17 returns 100% strength as 65535 */ - if (ulSignalStrength <= 0x0F00) - { - *pulSigStrength = (ulSignalStrength <<4) + ulSignalStrength; - } - else - { - *pulSigStrength = 0xFFFF; - } - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Strength = %lu\n",*pulSigStrength)); - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; - -} - -/***************************************************************************** - Function : SetLnbVoltage - Description : This Function Sets the LNB Voltage - IN PARAM : Pointer to the KSDevice Object - Voltage to set 1 for 18V and 0 for 13V - OUT PARAM : STATUS_SUCCESS in case of successful Set - Failure Code in other cases - PreCondition : None - PostCondtion : Sets the LNB Voltage in case of successful execution - Logic : NONE - Assumption : NONE - Note : None - Revision History: - *****************************************************************************/ -NTSTATUS SetLnbVoltage(IN PKSDEVICE pKSDeviceObject, - IN UCHAR ucVoltage) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("New Lnb Voltage (0 = 13V, 1 = 18V) = %02d \n",ucVoltage)); - - //gp8psk_usb_out_op(state->d,SET_LNB_VOLTAGE, - // voltage == SEC_VOLTAGE_18, 0, NULL, 0) - ntStatus = ControlUsbDevice( pKSDeviceObject, - SET_LNB_VOLTAGE, - (ucVoltage == SEC_VOLTAGE_18), - 0, - NULL, - 0, - false); - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; - - -} - - -/***************************************************************************** - Function : TuneDevice - Description : This Function Tunes the Tuner - IN PARAM : Pointer to the KSDevice Object - Tuner parameters to set - OUT PARAM : STATUS_SUCCESS in case of successful Set - Failure Code in other cases - PreCondition : None - PostCondtion : Tuners the Tuner in case of successful execution - Logic : NONE - Assumption : NONE - Note : To tune the Tuner following command needs to be sent - 9 8 7 6 5 4 3 2 1 0 -================================================================================= -| FECR | MOD | TFQ0 | TFQ0 | TFQ0 | TFQ0 | SBR3 | SBR2 | SBR1 | SBR0 | -================================================================================= -Where FECR -> Inner FEC Rate (1 Byte) -MOD = Modulation = QPSK (1 Byte) -TF = Tuner Frequency (4 Bytes) -SBR = Symbol Rate (4 Bytes) - - Revision History: - *****************************************************************************/ -NTSTATUS TuneDevice(IN PKSDEVICE pKSDeviceObject, - IN PBDATUNER_DEVICE_PARAMETER pDeviceParameter) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - UCHAR ucCommand[10]; - ULONG ulTempFrequency = 0L; - ULONG ulTunerFrequency = 0L; - ULONG ulLOFrequency = 0L; - ULONG ulTempSymbolRate = 0L; - - PrintFunctionEntry(__FUNCTION__); - - //Setting the Local Oscillator Frequency - if(pDeviceParameter->ulLnbSwitchFrequency >= pDeviceParameter->ulCarrierFrequency) - { - ulLOFrequency = pDeviceParameter->ulLnbLowLOFrequency; - } - else - { - ulLOFrequency = pDeviceParameter->ulLnbHighLOFrequency; - } - - //Getting the Frequency to be tuned based on the Carrier frequency and - //Local Oscillator frequency (LOF) - if(pDeviceParameter->ulCarrierFrequency > ulLOFrequency) - { - ulTempFrequency = pDeviceParameter->ulCarrierFrequency - ulLOFrequency; - } - else - { - ulTempFrequency = ulLOFrequency - pDeviceParameter->ulCarrierFrequency; - } - - if( (ulTempFrequency < TUNER_FREQ_MIN) || - (ulTempFrequency > TUNER_FREQ_MAX)) - { - SkyWalkerDebugPrint(EXTREME_LEVEL, - ("Frequency Out of Bound %lu, Resetting to %lu\n", - ulTempFrequency,TUNER_FREQ_MIN)); - ulTempFrequency = TUNER_FREQ_MIN; - } - ulTunerFrequency= ulTempFrequency * pDeviceParameter->ulFrequencyMultiplier; - - //Symbol Rate should be in Sample Per Second thus converting the - //Kilo Samples per Second (ksps) to Samples Per Second (sps) - ulTempSymbolRate = pDeviceParameter->ulSymbolRate * 1000; - - SkyWalkerDebugPrint(EXTREME_LEVEL,("New Symbol Rate = %lu sps (%lu ksps)\n" - "Carrier Frequency = %lu\n" - "Local Oscillator Freq = %lu\n" - "Frequency Multiplier = %lu\n" - "Tuner Frequency = %lu\n" - "New Modulation Type (QPSK = 0)= %lu\n" - "New FEC Rate (VITERBI = 1)= %lu \n", - ulTempSymbolRate, - pDeviceParameter->ulSymbolRate, - pDeviceParameter->ulCarrierFrequency, - ulLOFrequency, - pDeviceParameter->ulFrequencyMultiplier, - ulTunerFrequency, - ADV_MOD_DVB_QPSK, - pDeviceParameter->InnerFecRate)); - - ucCommand[0] = (UCHAR)(ulTempSymbolRate & 0xFF); - ucCommand[1] = (UCHAR)((ulTempSymbolRate >> 8) & 0xFF); - ucCommand[2] = (UCHAR)((ulTempSymbolRate >> 16) & 0xFF); - ucCommand[3] = (UCHAR)((ulTempSymbolRate >> 24) & 0xFF); - - ucCommand[4] = (UCHAR)(ulTunerFrequency & 0xFF); - ucCommand[5] = (UCHAR)((ulTunerFrequency >> 8) & 0xFF); - ucCommand[6] = (UCHAR)((ulTunerFrequency >> 16) & 0xFF); - ucCommand[7] = (UCHAR)((ulTunerFrequency >> 24) & 0xFF); - - ucCommand[8] = ADV_MOD_DVB_QPSK; - ucCommand[9] = 0x05; - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Tune Command : Symbol Rate = 0x%02X%02X%02X%02X," - "Frequency = 0x%02X%02X%02X%02X", - ucCommand[3],ucCommand[2],ucCommand[1],ucCommand[0], - ucCommand[7],ucCommand[6],ucCommand[5],ucCommand[4])); - - //gp8psk_usb_out_op(state->d,TUNE_8PSK,0,0,cmd,10); - ntStatus = ControlUsbDevice( pKSDeviceObject, - TUNE_8PSK, - 0, - 0, - ucCommand, - 10, - false); - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; -} - -/***************************************************************************** - Function : SetupTunerPower - Description : Function to used to Setup the SkyWalker1 Device Power - IN PARAM : Pointer to Device Object which needs Power setup - Switch on / Switch Off - OUT PARAM : ntStatus of the SkyWalker1 Power Setup - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Device ready for the Operation - Logic : Linux Method to Setup the Device - 1) Download Firmware (Not done here) - 2) Set Power State to ON - 3) Read 8PSK Config ntStatus - 4) if(Device not Started) then Start it - 5) if(BCM4500 Firmware not loaded) then load it - 6) if (LNB Power not set) then Set it - 7) Set DVB_MODE to 1 - 8) Read Again the 8PSK Config ntStatus - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS SetupTunerPower( IN PKSDEVICE pKSDeviceObject, - IN BOOLEAN bOnOff) -{ - - NTSTATUS ntStatus = STATUS_SUCCESS; - UCHAR ucDeviceConfig = 0; - UCHAR ucBuffer = 0; - - PrintFunctionEntry(__FUNCTION__); - - if (bOnOff) - { - //If Tuner Power On - - //Read the Tuner Configuration First - //gp8psk_usb_in_op(d, GET_8PSK_CONFIG,0,0,&status,1); - ntStatus = ControlUsbDevice( pKSDeviceObject, - GET_8PSK_CONFIG, - 0, - 0, - &ucDeviceConfig, - 1, - true); - - if(!NT_SUCCESS(ntStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Read the Device Configuration\n")); - goto ExitSetupPower; - - } - - SkyWalkerDebugPrint(EXTREME_LEVEL, - ("Device Status Bit0:Device Start,Bit1:Firmware Loaded," \ - "Bit2:LNB Powerup = 0x%02X\n", - ucDeviceConfig)); - - if (!(ucDeviceConfig & bm8pskStarted)) /* Device Start ntStatus BIT-0 */ - { - //Device Not Started - //Send the Boot Command to the Device - //gp8psk_usb_in_op(d, BOOT_8PSK, 1, 0, &buf, 1)) - ntStatus = ControlUsbDevice( pKSDeviceObject, - BOOT_8PSK, - 1, - 0, - &ucBuffer, - 1, - true); - if(!NT_SUCCESS(ntStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Boot the Device\n")); - goto ExitSetupPower; - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Boot Response 0x%02X\n",ucBuffer)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("Device Bootedup\n")); - - } - - if (!(ucDeviceConfig & bm8pskFW_Loaded)) /* Firmware ntStatus BIT-1 */ - { - //Firmware Not Loaded - SkyWalkerDebugPrint(ENTRY_LEVEL,("Firmware not Loaded\n")); - } - - if (!(ucDeviceConfig & bmIntersilOn)) /* LNB Power Status BIT-2 */ - { - //LNB Not powered On - //Sent the Power On Command to the LNB - ucBuffer = 0; - //gp8psk_usb_in_op(d, START_INTERSIL, 1, 0,&buf, 1)) - ntStatus = ControlUsbDevice( pKSDeviceObject, - START_INTERSIL, - 1, - 0, - &ucBuffer, - 1, - true); - if(!NT_SUCCESS(ntStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Powerup the Device\n")); - goto ExitSetupPower; - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("LNB Powerup Response 0x%02X\n",ucBuffer)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("Device Poweredup\n")); - - } - - /* Abort possible TS (if previous tune crashed) */ - //gp8psk_usb_out_op(d, ARM_TRANSFER, 0, 0, NULL, 0) - ntStatus = ControlUsbDevice( pKSDeviceObject, - ARM_TRANSFER, - 0, - 0, - NULL, - 0, - false); - - //Reread the Device Configuration - //gp8psk_usb_in_op(d, GET_8PSK_CONFIG,0,0,&status,1); - ntStatus = ControlUsbDevice( pKSDeviceObject, - GET_8PSK_CONFIG, - 0, - 0, - &ucDeviceConfig, - 1, - true); - - if(!NT_SUCCESS(ntStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Read the Device Configuration\n")); - goto ExitSetupPower; - - } - - SkyWalkerDebugPrint(EXTREME_LEVEL, - ("Device ntStatus Bit0:Device Start,Bit1:Firmware Loaded," \ - "Bit2:LNB Powerup = 0x%02X\n", - ucDeviceConfig)); - - - } - else - { - //Turn Off LNB Power - //gp8psk_usb_in_op(d, START_INTERSIL, 0, 0, &buf, 1) - ucBuffer = 0; - ntStatus = ControlUsbDevice( pKSDeviceObject, - START_INTERSIL, - 0, - 0, - &ucBuffer, - 1, - true); - if(!NT_SUCCESS(ntStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Powerdown the LNB\n")); - goto ExitSetupPower; - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("LNB Powerdown Response 0x%02X\n",ucBuffer)); - - //Turn Off 8PSK Power - //gp8psk_usb_in_op(d, BOOT_8PSK, 0, 0, &buf, 1) - ucBuffer = 0; - ntStatus = ControlUsbDevice( pKSDeviceObject, - BOOT_8PSK, - 0, - 0, - &ucBuffer, - 1, - true); - if(!NT_SUCCESS(ntStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Powerdown the SkyWalker1\n")); - goto ExitSetupPower; - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Tuner Powerdown Response 0x%02X\n",ucBuffer)); - } - -ExitSetupPower: - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; - -} -/***************************************************************************** - Function : SetStreamingControl - Description : This Function Enables / Disables the Streaming - IN PARAM : Pointer to the KSDevice Object - Streaming Control 1 for ON and 0 for OFF - OUT PARAM : STATUS_SUCCESS in case of successful Set - Failure Code in other cases - PreCondition : None - PostCondtion : Controls Streaming in case of successful execution - Logic : NONE - Assumption : NONE - Note : None - Revision History: - *****************************************************************************/ -NTSTATUS SetStreamingControl( IN PKSDEVICE pKSDeviceObject, - IN UCHAR ucOnOff) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Streaming Control (0 = OFF, 1 = ON) = %02d \n",ucOnOff)); - - //gp8psk_usb_out_op(adap->dev, ARM_TRANSFER, onoff, 0 , NULL, 0); - ntStatus = ControlUsbDevice( pKSDeviceObject, - ARM_TRANSFER, - ucOnOff, - 0, - NULL, - 0, - false); - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; -} - -/***************************************************************************** - Function : SetTunerTone - Description : This Function Sets the Tuner Tone - IN PARAM : Pointer to the KSDevice Object - Tuner Tone : 0 for TONE ON and 1 for TONE OFF - OUT PARAM : STATUS_SUCCESS in case of successful Set - Failure Code in other cases - PreCondition : None - PostCondtion : Sets the Tuner Tone in case of successful execution - Logic : NONE - Assumption : NONE - Note : None - Revision History: - *****************************************************************************/ -NTSTATUS SetTunerTone( IN PKSDEVICE pKSDeviceObject, - IN UCHAR ucTone) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Tuner Tone (0 = TONE_ON, 1 = TONE_OFF) = %02d \n",ucTone)); - - //gp8psk_usb_out_op(state->d,SET_22KHZ_TONE, - // (tone == SEC_TONE_ON), 0, NULL, 0) - ntStatus = ControlUsbDevice( pKSDeviceObject, - SET_22KHZ_TONE, - (ucTone == 0), - 0, - NULL, - 0, - false); - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; -} - -/***************************************************************************** - Function : ConfigureTuner - Description : This Function Configures Tuner Frequency, Polarity, - Symbol Rate, Tone etc. - IN PARAM : Pointer to the KSDevice Object - Configuration to tune - OUT PARAM : STATUS_SUCCESS in case of successful Set - Failure Code in other cases - PreCondition : None - PostCondtion : Configures the Tuner with the COnfiguration provided - Logic : NONE - Assumption : NONE - Note : None - Revision History: - *****************************************************************************/ -NTSTATUS ConfigureTuner(IN PKSDEVICE pKSDeviceObject, - IN PBDATUNER_DEVICE_PARAMETER pNewConfiguration) -{ - - NTSTATUS ntStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - //Set the LNB Voltage based on the Polarity - if((pNewConfiguration->Polarity == BDA_POLARISATION_LINEAR_H) || - (pNewConfiguration->Polarity == BDA_POLARISATION_CIRCULAR_L)) - { - //Set the LNB Voltage to 18 Volts - ntStatus = SetLnbVoltage(pKSDeviceObject,SEC_VOLTAGE_18); - } - else - { - //Set the LNB Voltage to 13 Volts - ntStatus = SetLnbVoltage(pKSDeviceObject,SEC_VOLTAGE_13); - } - if(NT_SUCCESS(ntStatus)) - { - ntStatus = SetTunerTone(pKSDeviceObject,SEC_TONE_OFF); - if(NT_SUCCESS(ntStatus)) - { - //Configure the updated resource on the hardware here. - ntStatus = TuneDevice(pKSDeviceObject,pNewConfiguration); - } - } - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; -} - -/***************************************************************************** - Function : DiseqcCommand - Description : This Function Sends Diseqc Command to the Tuner - IN PARAM : Pointer to the KSDevice Object - Command to be sent to the Device - OUT PARAM : STATUS_SUCCESS in case of successful Set - Failure Code in other cases - PreCondition : None - PostCondtion : Diseqc Command sent to the Tuner - Logic : 1) Validate the Diseqc Message - 2) Check the Diseqc Message Length - 3) If length == 1 - Treat the Diseqc Command as the Simple Tone Burst - 4) Else - Treat it as normal Diseqc Command - Assumption : NONE - Note : None - Revision History: - *****************************************************************************/ -NTSTATUS DiseqcCommand( IN PKSDEVICE pKSDeviceObject, - IN PDISEQC_COMMAND pCommand) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - if( !IS_VALID(pCommand) || - (pCommand->ucMessageLength == 0) || - (pCommand->ucMessageLength == 2) || - (pCommand->ucMessageLength > MAX_DISEQC_COMMAND_LENGTH)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid Diseqc Command Received \n")); - ntStatus = STATUS_INVALID_PARAMETER; - goto ExitDiseqcCommand; - } - - if(pCommand->ucMessageLength == 1) - { - //Simple Tone Burst - UCHAR ucBurst = (pCommand->ucMessage[0] == SEC_MINI_A) ? 0x00 : 0x01; - SkyWalkerDebugPrint(EXTREME_LEVEL,("Sending Simple Tone Burst Command = 0x%02X \n",ucBurst)); - //gp8psk_usb_out_op(st->d,SEND_DISEQC_COMMAND, cmd, 0,&cmd, 0) - ntStatus = ControlUsbDevice( pKSDeviceObject, - SEND_DISEQC_COMMAND, - ucBurst, - 0, - &ucBurst, - 0, - false); - - } - else - { - //Normal Diseqc Command - SkyWalkerDebugPrint(EXTREME_LEVEL,("Sending Normal Diseqc Command\n")); - PrintDiseqcCommand(pCommand); - //gp8psk_usb_out_op(st->d,SEND_DISEQC_COMMAND, m->msg[0], 0,m->msg, m->msg_len) - ntStatus = ControlUsbDevice( pKSDeviceObject, - SEND_DISEQC_COMMAND, - pCommand->ucMessage[0], - 0, - pCommand->ucMessage, - pCommand->ucMessageLength, - false); - - } - -ExitDiseqcCommand: - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; -} - -VOID PrintDiseqcCommand(PDISEQC_COMMAND pDiseqcCommand) -{ - if(pDiseqcCommand) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[0] = 0x%02X\n",pDiseqcCommand->ucMessage[0])); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[1] = 0x%02X\n",pDiseqcCommand->ucMessage[1])); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[2] = 0x%02X\n",pDiseqcCommand->ucMessage[2])); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[3] = 0x%02X\n",pDiseqcCommand->ucMessage[3])); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[4] = 0x%02X\n",pDiseqcCommand->ucMessage[4])); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[5] = 0x%02X\n",pDiseqcCommand->ucMessage[5])); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessageLength = %02u\n",pDiseqcCommand->ucMessageLength)); - } +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1Control.cpp + Author : + Date : + Purpose : This File Holds the Device Control related declarations + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Common For all the Definitions, + //Declarations and Library Routines + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +VOID PrintDiseqcCommand(PDISEQC_COMMAND pDiseqcCommand); +/* End of Function prototype definitions */ + +/***************************************************************************** + Function : GetSignalStatus + Description : This Function Get the Signal Lock Status + IN PARAM : Pointer to the KSDevice Object + true in case of Signal Locked else False + OUT PARAM : STATUS_SUCCESS in case of successful Lock Read + Failure Code in other cases + PreCondition : None + PostCondtion : Gets the Signal Lock Status in case of successful execution + Logic : NONE + Assumption : NONE + Note : None + Revision History: + *****************************************************************************/ +NTSTATUS GetSignalStatus( IN PKSDEVICE pKSDeviceObject, + OUT PBOOLEAN pbSignalLockStatus + ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + UCHAR ucSignalStatus = 0; + + PrintFunctionEntry(__FUNCTION__); + + //gp8psk_usb_in_op(st->d, GET_SIGNAL_LOCK, 0, 0, &lock,1) + ntStatus = ControlUsbDevice( pKSDeviceObject, + GET_SIGNAL_LOCK, + 0, + 0, + &ucSignalStatus, + 1, + true); + if(NT_SUCCESS(ntStatus)) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("ucSignalStatus = 0x%02X\n",ucSignalStatus)); + + if(ucSignalStatus) + { + *pbSignalLockStatus = TRUE; + } + else + { + *pbSignalLockStatus = FALSE; + } + } + + //if (lock) + // *status = FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_SIGNAL | FE_HAS_CARRIER; + //else + // *status = 0; + + if(ucSignalStatus) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Lock = 0x%02X\n",*pbSignalLockStatus)); + } + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; + +} + +/***************************************************************************** + Function : ReadTunerSignalStrength + Description : This Function reads the Tuner Signal Strength + IN PARAM : Pointer to the KSDevice Object + Pointer to hold the Signal Strength + OUT PARAM : STATUS_SUCCESS in case of successful read + Failure Code in other cases + PreCondition : None + PostCondtion : Reads the Signal Strength + Logic : NONE + Assumption : NONE + Note : None + Revision History: + *****************************************************************************/ +NTSTATUS ReadTunerSignalStrength( IN PKSDEVICE pKSDeviceObject, + OUT PULONG pulSigStrength + ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + UCHAR ucBuffer[6] = {0,0,0,0,0,0}; + ULONG ulSignalStrength = 0L; + + PrintFunctionEntry(__FUNCTION__); + + //gp8psk_usb_in_op(st->d, GET_SIGNAL_STRENGTH, 0,0,buf,6); + ntStatus = ControlUsbDevice( pKSDeviceObject, + GET_SIGNAL_STRENGTH, + 0, + 0, + ucBuffer, + 6, + true); + if(NT_SUCCESS(ntStatus)) + { + ulSignalStrength = (int)(ucBuffer[1]) << 8 | ucBuffer[0]; + SkyWalkerDebugPrint(EXTREME_LEVEL,("ulSignalStrength = %lu,ucBuffer[1] = 0x%02X, ucBuffer[2] = 0x%02X\n", + ulSignalStrength,ucBuffer[1],ucBuffer[0])); + //*pulSigStrength = (int)(ucBuffer[1]) << 8 | ucBuffer[0]; + /* snr is reported in dBu*256 */ + /* snr / 38.4 ~= 100% strength */ + /* snr * 17 returns 100% strength as 65535 */ + if (ulSignalStrength <= 0x0F00) + { + *pulSigStrength = (ulSignalStrength <<4) + ulSignalStrength; + } + else + { + *pulSigStrength = 0xFFFF; + } + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Strength = %lu\n",*pulSigStrength)); + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; + +} + +/***************************************************************************** + Function : SetLnbVoltage + Description : This Function Sets the LNB Voltage + IN PARAM : Pointer to the KSDevice Object + Voltage to set 1 for 18V and 0 for 13V + OUT PARAM : STATUS_SUCCESS in case of successful Set + Failure Code in other cases + PreCondition : None + PostCondtion : Sets the LNB Voltage in case of successful execution + Logic : NONE + Assumption : NONE + Note : None + Revision History: + *****************************************************************************/ +NTSTATUS SetLnbVoltage(IN PKSDEVICE pKSDeviceObject, + IN UCHAR ucVoltage) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("New Lnb Voltage (0 = 13V, 1 = 18V) = %02d \n",ucVoltage)); + + //gp8psk_usb_out_op(state->d,SET_LNB_VOLTAGE, + // voltage == SEC_VOLTAGE_18, 0, NULL, 0) + ntStatus = ControlUsbDevice( pKSDeviceObject, + SET_LNB_VOLTAGE, + (ucVoltage == SEC_VOLTAGE_18), + 0, + NULL, + 0, + false); + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; + + +} + + +/***************************************************************************** + Function : TuneDevice + Description : This Function Tunes the Tuner + IN PARAM : Pointer to the KSDevice Object + Tuner parameters to set + OUT PARAM : STATUS_SUCCESS in case of successful Set + Failure Code in other cases + PreCondition : None + PostCondtion : Tuners the Tuner in case of successful execution + Logic : NONE + Assumption : NONE + Note : To tune the Tuner following command needs to be sent + 9 8 7 6 5 4 3 2 1 0 +================================================================================= +| FECR | MOD | TFQ0 | TFQ0 | TFQ0 | TFQ0 | SBR3 | SBR2 | SBR1 | SBR0 | +================================================================================= +Where FECR -> Inner FEC Rate (1 Byte) +MOD = Modulation = QPSK (1 Byte) +TF = Tuner Frequency (4 Bytes) +SBR = Symbol Rate (4 Bytes) + + Revision History: + *****************************************************************************/ +NTSTATUS TuneDevice(IN PKSDEVICE pKSDeviceObject, + IN PBDATUNER_DEVICE_PARAMETER pDeviceParameter) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + UCHAR ucCommand[10]; + ULONG ulTempFrequency = 0L; + ULONG ulTunerFrequency = 0L; + ULONG ulLOFrequency = 0L; + ULONG ulTempSymbolRate = 0L; + + PrintFunctionEntry(__FUNCTION__); + + //Setting the Local Oscillator Frequency + if(pDeviceParameter->ulLnbSwitchFrequency >= pDeviceParameter->ulCarrierFrequency) + { + ulLOFrequency = pDeviceParameter->ulLnbLowLOFrequency; + } + else + { + ulLOFrequency = pDeviceParameter->ulLnbHighLOFrequency; + } + + //Getting the Frequency to be tuned based on the Carrier frequency and + //Local Oscillator frequency (LOF) + if(pDeviceParameter->ulCarrierFrequency > ulLOFrequency) + { + ulTempFrequency = pDeviceParameter->ulCarrierFrequency - ulLOFrequency; + } + else + { + ulTempFrequency = ulLOFrequency - pDeviceParameter->ulCarrierFrequency; + } + + if( (ulTempFrequency < TUNER_FREQ_MIN) || + (ulTempFrequency > TUNER_FREQ_MAX)) + { + SkyWalkerDebugPrint(EXTREME_LEVEL, + ("Frequency Out of Bound %lu, Resetting to %lu\n", + ulTempFrequency,TUNER_FREQ_MIN)); + ulTempFrequency = TUNER_FREQ_MIN; + } + ulTunerFrequency= ulTempFrequency * pDeviceParameter->ulFrequencyMultiplier; + + //Symbol Rate should be in Sample Per Second thus converting the + //Kilo Samples per Second (ksps) to Samples Per Second (sps) + ulTempSymbolRate = pDeviceParameter->ulSymbolRate * 1000; + + SkyWalkerDebugPrint(EXTREME_LEVEL,("New Symbol Rate = %lu sps (%lu ksps)\n" + "Carrier Frequency = %lu\n" + "Local Oscillator Freq = %lu\n" + "Frequency Multiplier = %lu\n" + "Tuner Frequency = %lu\n" + "New Modulation Type (QPSK = 0)= %lu\n" + "New FEC Rate (VITERBI = 1)= %lu \n", + ulTempSymbolRate, + pDeviceParameter->ulSymbolRate, + pDeviceParameter->ulCarrierFrequency, + ulLOFrequency, + pDeviceParameter->ulFrequencyMultiplier, + ulTunerFrequency, + ADV_MOD_DVB_QPSK, + pDeviceParameter->InnerFecRate)); + + ucCommand[0] = (UCHAR)(ulTempSymbolRate & 0xFF); + ucCommand[1] = (UCHAR)((ulTempSymbolRate >> 8) & 0xFF); + ucCommand[2] = (UCHAR)((ulTempSymbolRate >> 16) & 0xFF); + ucCommand[3] = (UCHAR)((ulTempSymbolRate >> 24) & 0xFF); + + ucCommand[4] = (UCHAR)(ulTunerFrequency & 0xFF); + ucCommand[5] = (UCHAR)((ulTunerFrequency >> 8) & 0xFF); + ucCommand[6] = (UCHAR)((ulTunerFrequency >> 16) & 0xFF); + ucCommand[7] = (UCHAR)((ulTunerFrequency >> 24) & 0xFF); + + ucCommand[8] = ADV_MOD_DVB_QPSK; + ucCommand[9] = 0x05; + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Tune Command : Symbol Rate = 0x%02X%02X%02X%02X," + "Frequency = 0x%02X%02X%02X%02X", + ucCommand[3],ucCommand[2],ucCommand[1],ucCommand[0], + ucCommand[7],ucCommand[6],ucCommand[5],ucCommand[4])); + + //gp8psk_usb_out_op(state->d,TUNE_8PSK,0,0,cmd,10); + ntStatus = ControlUsbDevice( pKSDeviceObject, + TUNE_8PSK, + 0, + 0, + ucCommand, + 10, + false); + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; +} + +/***************************************************************************** + Function : SetupTunerPower + Description : Function to used to Setup the SkyWalker1 Device Power + IN PARAM : Pointer to Device Object which needs Power setup + Switch on / Switch Off + OUT PARAM : ntStatus of the SkyWalker1 Power Setup + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Device ready for the Operation + Logic : Linux Method to Setup the Device + 1) Download Firmware (Not done here) + 2) Set Power State to ON + 3) Read 8PSK Config ntStatus + 4) if(Device not Started) then Start it + 5) if(BCM4500 Firmware not loaded) then load it + 6) if (LNB Power not set) then Set it + 7) Set DVB_MODE to 1 + 8) Read Again the 8PSK Config ntStatus + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS SetupTunerPower( IN PKSDEVICE pKSDeviceObject, + IN BOOLEAN bOnOff) +{ + + NTSTATUS ntStatus = STATUS_SUCCESS; + UCHAR ucDeviceConfig = 0; + UCHAR ucBuffer = 0; + + PrintFunctionEntry(__FUNCTION__); + + if (bOnOff) + { + //If Tuner Power On + + //Read the Tuner Configuration First + //gp8psk_usb_in_op(d, GET_8PSK_CONFIG,0,0,&status,1); + ntStatus = ControlUsbDevice( pKSDeviceObject, + GET_8PSK_CONFIG, + 0, + 0, + &ucDeviceConfig, + 1, + true); + + if(!NT_SUCCESS(ntStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Read the Device Configuration\n")); + goto ExitSetupPower; + + } + + SkyWalkerDebugPrint(EXTREME_LEVEL, + ("Device Status Bit0:Device Start,Bit1:Firmware Loaded," \ + "Bit2:LNB Powerup = 0x%02X\n", + ucDeviceConfig)); + + if (!(ucDeviceConfig & bm8pskStarted)) /* Device Start ntStatus BIT-0 */ + { + //Device Not Started + //Send the Boot Command to the Device + //gp8psk_usb_in_op(d, BOOT_8PSK, 1, 0, &buf, 1)) + ntStatus = ControlUsbDevice( pKSDeviceObject, + BOOT_8PSK, + 1, + 0, + &ucBuffer, + 1, + true); + if(!NT_SUCCESS(ntStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Boot the Device\n")); + goto ExitSetupPower; + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Boot Response 0x%02X\n",ucBuffer)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("Device Bootedup\n")); + + } + + if (!(ucDeviceConfig & bm8pskFW_Loaded)) /* Firmware ntStatus BIT-1 */ + { + //Firmware Not Loaded + SkyWalkerDebugPrint(ENTRY_LEVEL,("Firmware not Loaded\n")); + } + + if (!(ucDeviceConfig & bmIntersilOn)) /* LNB Power Status BIT-2 */ + { + //LNB Not powered On + //Sent the Power On Command to the LNB + ucBuffer = 0; + //gp8psk_usb_in_op(d, START_INTERSIL, 1, 0,&buf, 1)) + ntStatus = ControlUsbDevice( pKSDeviceObject, + START_INTERSIL, + 1, + 0, + &ucBuffer, + 1, + true); + if(!NT_SUCCESS(ntStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Powerup the Device\n")); + goto ExitSetupPower; + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("LNB Powerup Response 0x%02X\n",ucBuffer)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("Device Poweredup\n")); + + } + + /* Abort possible TS (if previous tune crashed) */ + //gp8psk_usb_out_op(d, ARM_TRANSFER, 0, 0, NULL, 0) + ntStatus = ControlUsbDevice( pKSDeviceObject, + ARM_TRANSFER, + 0, + 0, + NULL, + 0, + false); + + //Reread the Device Configuration + //gp8psk_usb_in_op(d, GET_8PSK_CONFIG,0,0,&status,1); + ntStatus = ControlUsbDevice( pKSDeviceObject, + GET_8PSK_CONFIG, + 0, + 0, + &ucDeviceConfig, + 1, + true); + + if(!NT_SUCCESS(ntStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Read the Device Configuration\n")); + goto ExitSetupPower; + + } + + SkyWalkerDebugPrint(EXTREME_LEVEL, + ("Device ntStatus Bit0:Device Start,Bit1:Firmware Loaded," \ + "Bit2:LNB Powerup = 0x%02X\n", + ucDeviceConfig)); + + + } + else + { + //Turn Off LNB Power + //gp8psk_usb_in_op(d, START_INTERSIL, 0, 0, &buf, 1) + ucBuffer = 0; + ntStatus = ControlUsbDevice( pKSDeviceObject, + START_INTERSIL, + 0, + 0, + &ucBuffer, + 1, + true); + if(!NT_SUCCESS(ntStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Powerdown the LNB\n")); + goto ExitSetupPower; + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("LNB Powerdown Response 0x%02X\n",ucBuffer)); + + //Turn Off 8PSK Power + //gp8psk_usb_in_op(d, BOOT_8PSK, 0, 0, &buf, 1) + ucBuffer = 0; + ntStatus = ControlUsbDevice( pKSDeviceObject, + BOOT_8PSK, + 0, + 0, + &ucBuffer, + 1, + true); + if(!NT_SUCCESS(ntStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Powerdown the SkyWalker1\n")); + goto ExitSetupPower; + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Tuner Powerdown Response 0x%02X\n",ucBuffer)); + } + +ExitSetupPower: + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; + +} +/***************************************************************************** + Function : SetStreamingControl + Description : This Function Enables / Disables the Streaming + IN PARAM : Pointer to the KSDevice Object + Streaming Control 1 for ON and 0 for OFF + OUT PARAM : STATUS_SUCCESS in case of successful Set + Failure Code in other cases + PreCondition : None + PostCondtion : Controls Streaming in case of successful execution + Logic : NONE + Assumption : NONE + Note : None + Revision History: + *****************************************************************************/ +NTSTATUS SetStreamingControl( IN PKSDEVICE pKSDeviceObject, + IN UCHAR ucOnOff) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Streaming Control (0 = OFF, 1 = ON) = %02d \n",ucOnOff)); + + //gp8psk_usb_out_op(adap->dev, ARM_TRANSFER, onoff, 0 , NULL, 0); + ntStatus = ControlUsbDevice( pKSDeviceObject, + ARM_TRANSFER, + ucOnOff, + 0, + NULL, + 0, + false); + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; +} + +/***************************************************************************** + Function : SetTunerTone + Description : This Function Sets the Tuner Tone + IN PARAM : Pointer to the KSDevice Object + Tuner Tone : 0 for TONE ON and 1 for TONE OFF + OUT PARAM : STATUS_SUCCESS in case of successful Set + Failure Code in other cases + PreCondition : None + PostCondtion : Sets the Tuner Tone in case of successful execution + Logic : NONE + Assumption : NONE + Note : None + Revision History: + *****************************************************************************/ +NTSTATUS SetTunerTone( IN PKSDEVICE pKSDeviceObject, + IN UCHAR ucTone) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Tuner Tone (0 = TONE_ON, 1 = TONE_OFF) = %02d \n",ucTone)); + + //gp8psk_usb_out_op(state->d,SET_22KHZ_TONE, + // (tone == SEC_TONE_ON), 0, NULL, 0) + ntStatus = ControlUsbDevice( pKSDeviceObject, + SET_22KHZ_TONE, + (ucTone == 0), + 0, + NULL, + 0, + false); + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; +} + +/***************************************************************************** + Function : ConfigureTuner + Description : This Function Configures Tuner Frequency, Polarity, + Symbol Rate, Tone etc. + IN PARAM : Pointer to the KSDevice Object + Configuration to tune + OUT PARAM : STATUS_SUCCESS in case of successful Set + Failure Code in other cases + PreCondition : None + PostCondtion : Configures the Tuner with the COnfiguration provided + Logic : NONE + Assumption : NONE + Note : None + Revision History: + *****************************************************************************/ +NTSTATUS ConfigureTuner(IN PKSDEVICE pKSDeviceObject, + IN PBDATUNER_DEVICE_PARAMETER pNewConfiguration) +{ + + NTSTATUS ntStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + //Set the LNB Voltage based on the Polarity + if((pNewConfiguration->Polarity == BDA_POLARISATION_LINEAR_H) || + (pNewConfiguration->Polarity == BDA_POLARISATION_CIRCULAR_L)) + { + //Set the LNB Voltage to 18 Volts + ntStatus = SetLnbVoltage(pKSDeviceObject,SEC_VOLTAGE_18); + } + else + { + //Set the LNB Voltage to 13 Volts + ntStatus = SetLnbVoltage(pKSDeviceObject,SEC_VOLTAGE_13); + } + if(NT_SUCCESS(ntStatus)) + { + ntStatus = SetTunerTone(pKSDeviceObject,SEC_TONE_OFF); + if(NT_SUCCESS(ntStatus)) + { + //Configure the updated resource on the hardware here. + ntStatus = TuneDevice(pKSDeviceObject,pNewConfiguration); + } + } + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; +} + +/***************************************************************************** + Function : DiseqcCommand + Description : This Function Sends Diseqc Command to the Tuner + IN PARAM : Pointer to the KSDevice Object + Command to be sent to the Device + OUT PARAM : STATUS_SUCCESS in case of successful Set + Failure Code in other cases + PreCondition : None + PostCondtion : Diseqc Command sent to the Tuner + Logic : 1) Validate the Diseqc Message + 2) Check the Diseqc Message Length + 3) If length == 1 + Treat the Diseqc Command as the Simple Tone Burst + 4) Else + Treat it as normal Diseqc Command + Assumption : NONE + Note : None + Revision History: + *****************************************************************************/ +NTSTATUS DiseqcCommand( IN PKSDEVICE pKSDeviceObject, + IN PDISEQC_COMMAND pCommand) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + if( !IS_VALID(pCommand) || + (pCommand->ucMessageLength == 0) || + (pCommand->ucMessageLength == 2) || + (pCommand->ucMessageLength > MAX_DISEQC_COMMAND_LENGTH)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid Diseqc Command Received \n")); + ntStatus = STATUS_INVALID_PARAMETER; + goto ExitDiseqcCommand; + } + + if(pCommand->ucMessageLength == 1) + { + //Simple Tone Burst + UCHAR ucBurst = (pCommand->ucMessage[0] == SEC_MINI_A) ? 0x00 : 0x01; + SkyWalkerDebugPrint(EXTREME_LEVEL,("Sending Simple Tone Burst Command = 0x%02X \n",ucBurst)); + //gp8psk_usb_out_op(st->d,SEND_DISEQC_COMMAND, cmd, 0,&cmd, 0) + ntStatus = ControlUsbDevice( pKSDeviceObject, + SEND_DISEQC_COMMAND, + ucBurst, + 0, + &ucBurst, + 0, + false); + + } + else + { + //Normal Diseqc Command + SkyWalkerDebugPrint(EXTREME_LEVEL,("Sending Normal Diseqc Command\n")); + PrintDiseqcCommand(pCommand); + //gp8psk_usb_out_op(st->d,SEND_DISEQC_COMMAND, m->msg[0], 0,m->msg, m->msg_len) + ntStatus = ControlUsbDevice( pKSDeviceObject, + SEND_DISEQC_COMMAND, + pCommand->ucMessage[0], + 0, + pCommand->ucMessage, + pCommand->ucMessageLength, + false); + + } + +ExitDiseqcCommand: + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; +} + +VOID PrintDiseqcCommand(PDISEQC_COMMAND pDiseqcCommand) +{ + if(pDiseqcCommand) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[0] = 0x%02X\n",pDiseqcCommand->ucMessage[0])); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[1] = 0x%02X\n",pDiseqcCommand->ucMessage[1])); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[2] = 0x%02X\n",pDiseqcCommand->ucMessage[2])); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[3] = 0x%02X\n",pDiseqcCommand->ucMessage[3])); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[4] = 0x%02X\n",pDiseqcCommand->ucMessage[4])); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessage[5] = 0x%02X\n",pDiseqcCommand->ucMessage[5])); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pDiseqcCommand->ucMessageLength = %02u\n",pDiseqcCommand->ucMessageLength)); + } } \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Device.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Device.cpp index 9464d05..d5fef8c 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Device.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Device.cpp @@ -1,1142 +1,1142 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1Device.cpp - Author : - Date : - Purpose : Main Skywalker Device level Implementation - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Header for the Tuner related definitions - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -static ULONG ulDeviceInstance = 0; -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -void PrintDMAAdapter(PDMA_ADAPTER pDMAAdapter); -void PrintMappingInfo(IN PKSMAPPING pMapping); - -/* End of Function prototype definitions */ - -/***************************************************************************** - Function : CSkyWalker1Device::Create - Description : This Function is called during the Add Device IRP Processing - IN PARAM : Pointer to the Enumerated Physical Device - KSDEVICE is a WDM Functional Device which is managed by the AVStream - OUT PARAM : ntStatus of the Device Addition - STATUS_SUCCESS when the Device added to the System - Reason for Failure incase of Error - PreCondition : Driver is Loaded without Functional/ Filter Device Objects - PostCondtion : Functional Device Object [FDO] or Filter Device Object [FiDO] are created - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::Create(IN PKSDEVICE pKSDeviceObject) -{ - NTSTATUS ntCreateStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if (pKSDeviceObject) - { - - //Point the KSDEVICE at our device class. - pKSDeviceObject->Context = this; - - m_pKSDevice = pKSDeviceObject; - - //Make the resource available for a filter to use. - m_ulcResourceUsers = 0; - m_ulCurResourceID = 0; - - //Get the instance number of this device. - m_ulDeviceInstance = ulDeviceInstance++; - - //Hold requests until the device is started - QueueState = HoldRequests; - - //Initialize the stop event to signaled. - KeInitializeEvent(&EvDeviceStopOk, //PKEVENT - NotificationEvent, //Type - TRUE); //State - - //Initialize the remove event to not-signaled. - KeInitializeEvent(&EvDeviceRemoveOk, //PKEVENT - NotificationEvent, //Type - FALSE); //State - - //The KeInitializeSpinLock routine initializes a variable of - //type KSPIN_LOCK. - KeInitializeSpinLock(&DeviceStateLock); - INITIALIZE_PNP_STATE(this); - - //OutstandingIo count biased to 1. - //Transition to 0 during remove device means IO is finished. - //Transition to 1 means the device can be stopped - - ulOutStandingIoCount = 1; - KeInitializeSpinLock(&kIoCountLock); - - } - else - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid KS Device Object Received\n")); - ntCreateStatus = STATUS_INVALID_PARAMETER; - } - - PrintFunctionExit(__FUNCTION__,ntCreateStatus); - return ntCreateStatus; -} - -/***************************************************************************** - Function : CSkyWalker1Device::Start - Description : This function Initializes the Tuner Hardware - IN PARAM : Reference to Device to be Started - IoRequest Packet - Resource List - Translated Resource List - OUT PARAM : ntStatus of the Tuner Start - STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : Stopped Device or Device Enumerated for the First Time - PostCondtion : Device Initialized with the Newly allocated Resources, - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ - -NTSTATUS CSkyWalker1Device::Start( - IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket, - IN PCM_RESOURCE_LIST pResourceList OPTIONAL, - IN PCM_RESOURCE_LIST pTranslatedResourceList OPTIONAL - ) -{ - NTSTATUS ntStartStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = NULL; - PKSFILTERFACTORY pKSFilterFactory = NULL; - - PrintFunctionEntry(__FUNCTION__); - - pDevice = reinterpret_cast(pKSDeviceObject->Context); - - //Initialize the Tuner Hardware. - ntStartStatus = pDevice->InitializeTuner(pKSDeviceObject,pIoRequestPacket); - SkyWalkerDebugPrint(EXTREME_LEVEL,("USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE = %d",USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE)); - - if (ntStartStatus == STATUS_SUCCESS) - { - //Create the the Filter Factory. This factory is used to - //create instances of the filter. - ntStartStatus = BdaCreateFilterFactoryEx( - pKSDeviceObject, - &SkyWalker1TunerFilterDescriptor, - &TunerFilterTemplate, - &pKSFilterFactory - ); - } - - if ((ntStartStatus == STATUS_SUCCESS) && pKSFilterFactory) - { - BdaFilterFactoryUpdateCacheData( - pKSFilterFactory, - TunerFilterTemplate.pFilterDescriptor - ); - } - - - PrintFunctionExit(__FUNCTION__,ntStartStatus); - return ntStartStatus; -} - -/***************************************************************************** - Function : CSkyWalker1Device::Stop - Description : This routine is invoked when the device is stopped. - This routine services Irp of minor type IRP_MN_STOP_DEVICE - IN PARAM : Pointer to KS Device Object - STOP DEVICE Irp - OUT PARAM : ntStatus of the Device Stop - STATUS_SUCCESS on Successful execution - else Error - PreCondition : NONE - PostCondtion : USB Device Stopped - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::Stop( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - NTSTATUS ntDeviceStopStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - if(m_pDMAAdapter) - { - m_pDMAAdapter->DmaOperations->PutDmaAdapter(m_pDMAAdapter); - m_pDMAAdapter = NULL; - } - - //Maintain the USB ntStatus i.e Remove the Selected Configuration by sending Null descriptor - ntDeviceStopStatus = StopUsbDevice(pKSDeviceObject,pIoRequestPacket); - - PrintFunctionExit(__FUNCTION__,ntDeviceStopStatus); - - return ntDeviceStopStatus; -} - - -/***************************************************************************** - Function : CSkyWalker1Device::Close - Description : This routine is invoked when the device is Removed. - This routine services Irp of minor type IRP_MN_REMOVE_DEVICE - IN PARAM : Pointer to KS Device Object - STOP DEVICE Irp - OUT PARAM : ntStatus of the Device Remove - STATUS_SUCCESS on Successful execution - else Error - PreCondition : NONE - PostCondtion : USB Device Removed - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::Close( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - NTSTATUS ntDeviceCloseStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - ntDeviceCloseStatus = RemoveUsbDevice(pKSDeviceObject,pIoRequestPacket); - - PrintFunctionExit(__FUNCTION__,ntDeviceCloseStatus); - - return ntDeviceCloseStatus; -} - -/***************************************************************************** - Function : CSkyWalker1Device::SetPower - Description : This routine is invoked when the Power Irp is received - This routine services Irp of minor type IRP_MJ_POWER - IN PARAM : Pointer to KS Device Object - STOP DEVICE Irp - OUT PARAM : ntStatus of the Set POwer - STATUS_SUCCESS on Successful execution - else Error - PreCondition : NONE - PostCondtion : Power Condition Managed - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::SetPower( - IN PKSDEVICE pKSDeviceObject, //Pointer to the device object - //provided by the system. - IN PIRP pIoRequestPacket, //Pointer to the IRP related to this request. - IN DEVICE_POWER_STATE To, //Requested power state. - IN DEVICE_POWER_STATE From //Current power state. -) -{ - NTSTATUS ntSetPowerStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - //Set USB Power condition from here - - PrintFunctionExit(__FUNCTION__,ntSetPowerStatus); - - return ntSetPowerStatus; -} - - -/***************************************************************************** - Function : CSkyWalker1Device::InitializeTuner - Description : This function is used to setup and initialize the Tuner - Interface - IN PARAM : Reference to Device to be Started - IoRequest Packet - OUT PARAM : ntStatus of the Tuner Initializations - STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : Stopped Device or Device Enumerated for the First Time - PostCondtion : Device Initialized with the Newly allocated Resources, - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::InitializeTuner( - IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - NTSTATUS ntInitStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - //Initialize the USB hardware here. - ntInitStatus = InitializeUsbDevice(pKSDeviceObject,pIoRequestPacket); - if(!NT_SUCCESS(ntInitStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Failed to Initialize the USB Device\n")); - goto ExitInitTuner; - } - - //Setup the SkyWalker1 Device - ntInitStatus = SetupTunerPower(pKSDeviceObject,SWITCH_ON_TUNER); - if(!NT_SUCCESS(ntInitStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Failed to Setup the SkyWalker1 Device\n")); - goto ExitInitTuner; - } - - //Initialize Adapter - ntInitStatus = InitializeAdapterStream(pKSDeviceObject); - if(!NT_SUCCESS(ntInitStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Failed to Initialize the Adapter Stream\n")); - goto ExitInitTuner; - } - -ExitInitTuner: - - PrintFunctionExit(__FUNCTION__,ntInitStatus); - return ntInitStatus; -} - -/***************************************************************************** - Function : CSkyWalker1Device::InitializeAdapterStream - Description : This function is used to Register the Adapter Object and - setup the Private Memory for the Streams - IN PARAM : Reference to KS Device Object - OUT PARAM : ntStatus of the Adapter Stream Initialization - STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Adapter Object Registered with the AVStream and Streaming - Memory allocated - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::InitializeAdapterStream( - IN PKSDEVICE pKSDeviceObject) -{ - - NTSTATUS ntInitStatus = STATUS_SUCCESS; - DEVICE_DESCRIPTION DeviceDescription; //Object to hold the Device description - ULONG ulMaxDMAMapRegisters = 0L; - - PrintFunctionEntry(__FUNCTION__); - - //Create DMA adapter - - memset(&DeviceDescription, 0, sizeof(DeviceDescription)); - - DeviceDescription.Version = DEVICE_DESCRIPTION_VERSION; - DeviceDescription.Master = TRUE; - DeviceDescription.ScatterGather = TRUE; - DeviceDescription.Dma32BitAddresses = TRUE; - DeviceDescription.Dma64BitAddresses = FALSE; - DeviceDescription.DmaChannel = ((ULONG) ~0); - DeviceDescription.InterfaceType = PCIBus; - DeviceDescription.MaximumLength = 0xfffffff8; - - //not used - DeviceDescription.IgnoreCount; - DeviceDescription.DemandMode; - DeviceDescription.AutoInitialize; - DeviceDescription.DmaWidth; - DeviceDescription.Reserved1; - DeviceDescription.DmaSpeed; - DeviceDescription.DmaPort; - - //The IoGetDmaAdapter routine returns a pointer to the DMA adapter structure - //for a physical device object. - m_pDMAAdapter = IoGetDmaAdapter( - pKSDeviceObject->PhysicalDeviceObject, //Pointer to Physical Device Object - //requesting the DMA Adapter structure - &DeviceDescription, //Pointer to the Device Descriptor Structure which - //describes the attributes of the Structure - &ulMaxDMAMapRegisters); //Maximum Map registers that driver can allocate for - //Dma transfer - if(!IS_VALID(m_pDMAAdapter)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to get the Dma Adapter for the Device\n")); - ntInitStatus = STATUS_UNSUCCESSFUL; - goto ExitInitAdapter; - - } - - if(!IS_VALID(m_pDMAAdapter->DmaOperations)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("No Dma Operatios found for the DMA Adapter\n")); - ntInitStatus = STATUS_UNSUCCESSFUL; - goto ExitInitAdapter; - } - - //Print the DMA Adapter details - PrintDMAAdapter(m_pDMAAdapter); - SkyWalkerDebugPrint(EXTREME_LEVEL,("Number of Map Registers = %lu\n",ulMaxDMAMapRegisters)); - - //Register the DMA Adapter with the AVStream - //The KsDeviceRegisterAdapterObject function registers a DMA adapter object with - //AVStream for performing scatter/gather DMA on the specified device. - KsDeviceRegisterAdapterObject( - pKSDeviceObject, //Device For which to register an adapter object - m_pDMAAdapter, //Pointer to DMA Adapter (IoGetDmaAdapter) - 0xfffffff8, //Maximum number of bytes that device can handle for a single mapping - sizeof(KSMAPPING)); //Number of bytes each entry in the mapping table requires - - //Allocate the Space for storing the Streaming contents - KeInitializeEvent (&m_HardwareEvent,SynchronizationEvent,FALSE); - - RtlZeroMemory(pUsbStreamIrp,sizeof(pUsbStreamIrp)); - -ExitInitAdapter: - - PrintFunctionExit(__FUNCTION__,ntInitStatus); - return ntInitStatus; -} - -/***************************************************************************** - Function : CSkyWalker1Device::ReadStream - Description : This function is called when streaming data from the USb - is requested to read.This function is called when the streaming - is started and every time after Stream processing to read a - new Stream - IN PARAM : Index of the Stream to Read - OUT PARAM : ntStatus of the Read Stream - STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Stream Read - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::ReadStream(IN ULONG ulStreamIndex) -{ - NTSTATUS ntReadStreamStatus = STATUS_SUCCESS; - PUCHAR pStreamBuffer = NULL; - ULONG ulPacketCount = PACKET_PER_FRAME; - ULONG ulPacketSize = MAX_BULK_PACKET_SIZE ; - - PrintFunctionEntry(__FUNCTION__); - - //Get the Stream buffer related to the Current Stream index - pStreamBuffer = m_SynthesisBuffer[ulStreamIndex]; - //Set the number of Bytes read - m_NumberOfBytesRead[ulStreamIndex] = 0; - - //Send the Read request of the 4K Size Each - for(ULONG ulPacketIndex = 0; ulPacketIndex <= ulPacketCount-1 ; ulPacketIndex++) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamBuffer[%03lu] = 0x%p",ulPacketIndex,pStreamBuffer)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("m_SampleSize = %lu",m_SampleSize)); - if(IS_VALID(pStreamBuffer)) - { - if(ulPacketIndex == 23) - { - ulPacketSize = 2048; - } - ntReadStreamStatus = ReadWriteUsbDevice( m_pKSDevice, - ulStreamIndex, - ulPacketIndex, - pStreamBuffer, - ulPacketSize , - true - ); - - - if(NT_SUCCESS(ntReadStreamStatus)) - { - SkyWalkerDebugPrint(EXTREME_LEVEL, ("Sent the Stream Read Request\n")); - } - else - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Send Stream Read from the Device")); - } - - pStreamBuffer += ulPacketSize; - } - else - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid Stream Aborting Stream Read\n")); - break; - } - } - PrintFunctionExit(__FUNCTION__,ntReadStreamStatus); - - return ntReadStreamStatus; - - -} - -/***************************************************************************** - Function : CSkyWalker1Device::GetStatus - Description : This function is used to Get the Current ntStatus of the - Tuner i.e. Current Carrier Freq. Signal Locked status etc. - IN PARAM : Device ntStatus - OUT PARAM : STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Device ntStatus updated and returned - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::GetStatus(OUT PBDATUNER_DEVICE_STATUS pDeviceStatus) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - pDeviceStatus->fCarrierPresent = FALSE; - pDeviceStatus->fSignalLocked = FALSE; - pDeviceStatus->dwSignalQuality = 0; - pDeviceStatus->dwSignalStrength = 0; - - PrintFunctionEntry(__FUNCTION__); - - if(m_HardwareState != HardwareRunning) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("Streaming is not started yet\n")); - ntStatus = STATUS_UNSUCCESSFUL; - goto ExitGetStatus; - - } - - if(TimeToReadSignalStatus()) - { - //It's Time to read Signal Status - //Get the signal status from the HW here - ReadTunerSignalStrength(m_pKSDevice, - &m_TunerStatus.dwSignalStrength); - - m_TunerStatus.dwSignalQuality = (m_TunerStatus.dwSignalStrength * 100)/65535; - - GetSignalStatus(m_pKSDevice, - &m_TunerStatus.fSignalLocked); - - - if(m_TunerStatus.fSignalLocked) - { - m_TunerStatus.fCarrierPresent = TRUE; - } - } - - *pDeviceStatus = m_TunerStatus; - -#ifdef FAKE_SIGNAL - pDeviceStatus->dwSignalStrength = 2; - pDeviceStatus->dwSignalQuality = 99; - pDeviceStatus->fCarrierPresent = TRUE; - pDeviceStatus->fSignalLocked = TRUE; -#endif - -ExitGetStatus : - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; -} - -/***************************************************************************** - Function : CSkyWalker1Device::TimeToReadSignalStatus - Description : This function is used Check whether time to read - the status from device has occurred or not - IN PARAM : NONE - OUT PARAM : TRUE = Time to read status from Device, - FALSE otherwise - PreCondition : None - PostCondtion : Whether time to read from device has occurred or not - is checked - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -BOOLEAN CSkyWalker1Device::TimeToReadSignalStatus(void) -{ - LARGE_INTEGER CurrentTime; - ULONG ulReadPeriod = 10 * 1000 * 1000; - KeQuerySystemTime (&CurrentTime); - - //If Current time is greater than the last - if(m_TunerStatus.fSignalLocked == FALSE) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Not Locked Reading Status @ 100msec\n")); - ulReadPeriod = 10 * 1000 * 100; - } - if(CurrentTime.QuadPart - m_PreviousStatusReadTime.QuadPart >= ulReadPeriod) - { - m_PreviousStatusReadTime.QuadPart = CurrentTime.QuadPart; - SkyWalkerDebugPrint(EXTREME_LEVEL,("Time to read signal status\n")); - return TRUE; - } - - return FALSE; -} - -/***************************************************************************** - Function : CSkyWalker1Device::Acquire - Description : This function is used to set the Tuner parameters and - called once when the tuner resources are acquired - From this function various device related command are executed - to set the device parameters - IN PARAM : New Device Parameters to be set - Resource ID - OUT PARAM : STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Device ntStatus updated and returned - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::Acquire( - IN PBDATUNER_DEVICE_PARAMETER pNewResource, - OUT PULONG pulAcquiredResourceID - ) -{ - NTSTATUS ntAcquireStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - //Continue only if the Device is acquired currently - if (!m_ulcResourceUsers) - { - m_CurResource = *pNewResource; - - //Generate a new resource ID and hand it back. - m_ulCurResourceID += 25; - *pulAcquiredResourceID = m_ulCurResourceID; - m_ulcResourceUsers += 1; - - //Configure the new resource on the hardware here. - //Send the Tune, SetLnbVoltage etc Commands from here - ConfigureTuner(m_pKSDevice,pNewResource); - - } - else - { - //Only one active filter is allowed - ntAcquireStatus = STATUS_DEVICE_BUSY; - } - - PrintFunctionExit(__FUNCTION__,ntAcquireStatus); - return ntAcquireStatus; -} - -/***************************************************************************** - Function : CSkyWalker1Device::Update - Description : This function is used to set the Tuner parameters and - called everytime after the tuner resources are acquired - From this function various device related command are executed - to set the device parameters - IN PARAM : New Device Parameters to be set - Resource ID - OUT PARAM : STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Device ntStatus updated and returned - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::Update( - IN PBDATUNER_DEVICE_PARAMETER pNewResource, - IN ULONG ulResourceID - ) -{ - NTSTATUS ntUpdateStatus = STATUS_SUCCESS; - LONGLONG ulhzFrequency; - - PrintFunctionEntry(__FUNCTION__); - - //Continue only if the Device is acquired currently - if (m_ulcResourceUsers && (ulResourceID == m_ulCurResourceID)) - { - m_CurResource = *pNewResource; - - //Configure the new resource on the hardware here. - //Send the Tune, SetLnbVoltage etc Commands from here - ConfigureTuner(m_pKSDevice,pNewResource); - - } - else - { - //Only one active filter is allowed - ntUpdateStatus = STATUS_INVALID_DEVICE_REQUEST; - } - - PrintFunctionExit(__FUNCTION__,ntUpdateStatus); - return ntUpdateStatus; -} - -/***************************************************************************** - Function : CSkyWalker1Device::SendDiseqcCommand - Description : This function is used to send the Diseqc Command to the Tuner - IN PARAM : Diseqc Command to be sent to the Tuner - Resource ID - OUT PARAM : STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Diseqc Command sent to the Tuner - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::SendDiseqcCommand( - IN PDISEQC_COMMAND pDiseqcCommand, - IN ULONG ulResourceID - ) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - //Continue only if the Device is acquired currently - //if (m_ulcResourceUsers && (ulResourceID == m_ulCurResourceID)) - { - - SkyWalkerDebugPrint(EXTREME_LEVEL,("m_ulcResourceUsers = %lu\n",m_ulcResourceUsers)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("ulResourceID = %lu\n",ulResourceID)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("m_ulCurResourceID = %lu\n",m_ulCurResourceID)); - //Send the Diseqc Command to the Tuner device - DiseqcCommand(m_pKSDevice,pDiseqcCommand); - - } - //else - //{ - // //Only one active filter is allowed - // ntStatus = STATUS_INVALID_DEVICE_REQUEST; - // } - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; -} - -/***************************************************************************** - Function : CSkyWalker1Device::Release - Description : This function is used to decrement the Resource User count - to allow other filters to use the resources - IN PARAM : Resource ID - OUT PARAM : STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Device ntStatus updated and returned - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::Release(IN ULONG ulResourceID) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - if(m_ulcResourceUsers && (ulResourceID == m_ulCurResourceID)) - { - //Free the resource to be used by another filter. - m_ulcResourceUsers--; - } - else - { - ntStatus = STATUS_INVALID_DEVICE_REQUEST; - } - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; -} - -/***************************************************************************** - Function : CSkyWalker1Device::StartStream - Description : This function is used to Initialize the Prestreaming - parameters and also it sends the Streaming command to - the Tuner - IN PARAM : NONE - OUT PARAM : STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Streaming Started on successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::StartStream () -{ - - NTSTATUS ntStreamStartStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - //Initializing the Streaming Parameters - - m_TimePerFrame = m_TransportInfo->AvgTimePerFrame; - m_SampleSize = m_TransportInfo->ulcbPhyiscalFrame; - m_PacketSize = m_TransportInfo->ulcbPhyiscalPacket; - m_PacketsPerSample = m_TransportInfo->ulcbPhyiscalFrame / m_TransportInfo->ulcbPhyiscalPacket; - m_TunerStatus.fCarrierPresent = FALSE; - m_TunerStatus.fSignalLocked = FALSE; - m_TunerStatus.dwSignalQuality = 0; - m_TunerStatus.dwSignalStrength = 0; - - - //Allocate a scratch buffer for the Streaming Buffer. - - for(int nBufferIndex = 0 ; - nBufferIndex < SIZEOF_ARRAY(m_SynthesisBuffer); - nBufferIndex++) - { - m_SynthesisBuffer[nBufferIndex] = reinterpret_cast ( - ExAllocatePoolWithTag ( - NonPagedPool, - m_SampleSize, - CAPTURE_MEM_TAG - ) - ); - - if (!IS_VALID(m_SynthesisBuffer)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Insufficient Resource for Scatter/Gather DMA\n")); - ntStreamStartStatus = STATUS_INSUFFICIENT_RESOURCES; - goto ExitStreamStart; - } - - } - - //Send Device Streaming Start Control - SetStreamingControl(m_pKSDevice,1); - m_HardwareState = HardwareRunning; - -ExitStreamStart: - PrintFunctionExit(__FUNCTION__,ntStreamStartStatus); - return ntStreamStartStatus; - -} - -/***************************************************************************** - Function : CSkyWalker1Device::PauseStream - Description : This function is used to Pause/Run the Streaming if On/Off. - IN PARAM : True : Pause Streaming , False : Start Streaming - OUT PARAM : STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Streaming Paused/Started on successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::PauseStream (BOOLEAN bPausing) -{ - - NTSTATUS ntStreamPauseStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - //If Streaming is in Progress then Stop the Streaming by - //Stop sending read requests and Cancelling any on going IRP - if (bPausing && (m_HardwareState == HardwareRunning)) - { - m_StopHardware = TRUE; - //Stop Streaming - SetStreamingControl(m_pKSDevice,0); - - for(int ulPacketIndex = 0; - ulPacketIndex < SIZEOF_ARRAY(pUsbStreamIrp) ; - ulPacketIndex++) - { - if(pUsbStreamIrp[ulPacketIndex]) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("Cancelling pUsbStreamIrp[%lu] = 0x%p\n", - ulPacketIndex,pUsbStreamIrp[ulPacketIndex])); - IoCancelIrp(pUsbStreamIrp[ulPacketIndex]); - pUsbStreamIrp[ulPacketIndex] = NULL; - - KeWaitForSingleObject ( - &m_HardwareEvent, - Suspended, - KernelMode, - FALSE, - NULL - ); - - KeResetEvent(&m_HardwareEvent); - m_StopHardware = TRUE; - } - } - - m_HardwareState = HardwarePaused; - - } - else if (!bPausing && (m_HardwareState == HardwarePaused) ) - { - - m_HardwareState = HardwareRunning; - - //Start Streaming - SetStreamingControl(m_pKSDevice,1); - - } - - PrintFunctionExit(__FUNCTION__,ntStreamPauseStatus); - return ntStreamPauseStatus; - -} - -/***************************************************************************** - Function : CSkyWalker1Device::StopStream - Description : This function is used to Stop the Streaming if On. - IN PARAM : NONE - OUT PARAM : STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Streaming Stopped on successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device::StopStream () -{ - - NTSTATUS ntStreamStopStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - //Stop the Streaming in case it's already on - if (m_HardwareState == HardwareRunning) - { - m_StopHardware = TRUE; - //Stop Streaming - SetStreamingControl(m_pKSDevice,0); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("SIZEOF_ARRAY[pUsbStreamIrp] = %d\n", - SIZEOF_ARRAY(pUsbStreamIrp))); - - for(int ulPacketIndex = 0; - ulPacketIndex < SIZEOF_ARRAY(pUsbStreamIrp) ; - ulPacketIndex++) - { - if(pUsbStreamIrp[ulPacketIndex]) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("Cancelling pUsbStreamIrp[%lu] = 0x%p\n", - ulPacketIndex,pUsbStreamIrp[ulPacketIndex])); - IoCancelIrp(pUsbStreamIrp[ulPacketIndex]); - pUsbStreamIrp[ulPacketIndex] = NULL; - - KeWaitForSingleObject ( - &m_HardwareEvent, - Suspended, - KernelMode, - FALSE, - NULL - ); - - KeResetEvent(&m_HardwareEvent); - m_StopHardware = TRUE; - } - } - - } - - m_HardwareState = HardwareStopped; - - for(int nBufferIndex = 0 ; - nBufferIndex < SIZEOF_ARRAY(m_SynthesisBuffer) ; - nBufferIndex++) - { - if (m_SynthesisBuffer[nBufferIndex]) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("Freeing Streaming Buffer m_SynthesisBuffer[%lu] = 0x%p\n", - nBufferIndex,m_SynthesisBuffer[nBufferIndex])); - ExFreePool (m_SynthesisBuffer[nBufferIndex]); - m_SynthesisBuffer[nBufferIndex] = NULL; - } - } - - PrintFunctionExit(__FUNCTION__,ntStreamStopStatus); - return ntStreamStopStatus; - -} - -/***************************************************************************** - Function : CSkyWalker1Device::ProcessStream - Description : This function is used to Process the Streaming Data after - reading it.This function is called from the USB Read Write - Completion routine - IN PARAM : Index of the Stream whose read completed - OUT PARAM : STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Read Stream is processed and Streaming data is filled into the - used Buffer. - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -void CSkyWalker1Device::ProcessStream(ULONG ulStreamIndex) -{ - - PrintFunctionEntry(__FUNCTION__); - - //The hardware can be in a pause state in which case, it issues interrupts - //but does not complete mappings. In this case, don't bother synthesizing - //a frame and doing the work of looking through the mappings table. - - if (m_HardwareState == HardwareRunning) - { - - SkyWalkerDebugPrint(EXTREME_LEVEL,("(%d * %d) = %lu\n", TRANSPORT_PACKET_SIZE, - TRANSPORT_PACKET_COUNT, - m_SampleSize)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("m_NumberOfBytesRead[%lu] = %lu\n", - ulStreamIndex, - m_NumberOfBytesRead[ulStreamIndex])); - - if (InterlockedCompareExchange((LONG *)&m_NumberOfBytesRead[ulStreamIndex], - m_SampleSize, m_SampleSize) == m_SampleSize) - { - //Inform the capture sink - m_CaptureSink->ReleaseStream(ulStreamIndex); - } - - } - - if (m_StopHardware) - { - - //If someone is waiting on the hardware to stop, raise the stop - //event and clear the flag. - SkyWalkerDebugPrint(EXTREME_LEVEL,("Sending the Stop Event\n")); - m_StopHardware = FALSE; - KeSetEvent (&m_HardwareEvent, IO_NO_INCREMENT, FALSE); - } - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); - -} - -/***************************************************************************** - Function : CSkyWalker1Device::SetupCaptureSink - Description : Acquire hardware resources for the capture hardware. - If the resources are already acquired, this will return - an error.The hardware configuration must be passed as - a VideoInfoHeader. - IN PARAM : The capture sink attempting to acquire - resources. When scatter /gather mappings are completed, - the capture sink specified here is what is notified - of the completions. - Information about the capture stream. - This **MUST** remain stable until the caller releases - hardware resources. Note that this could also be guaranteed - by bagging it in the device object bag as well. - OUT PARAM : STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : None - PostCondtion : Capture Pin to the notified and Video Info Header set in case - of successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CSkyWalker1Device:: SetupCaptureSink( - IN ICaptureSink *CaptureSink, - IN PBDA_TRANSPORT_INFO TransportInfo - ) -{ - - NTSTATUS ntStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - //If we're the first pin to go into acquire (remember we can have - //a filter in another graph going simultaneously), grab the resources. - if (InterlockedCompareExchange(&m_PinsWithResources,1,0) == 0) - { - m_TransportInfo = TransportInfo; - m_CaptureSink = CaptureSink; - - } - else - { - - ntStatus = STATUS_SHARING_VIOLATION; - } - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; - -} - -/***************************************************************************** - Function : CSkyWalker1Device::RemoveCaptureSink - Description : Release hardware resources. This should only be called by - an object which has acquired them. - IN PARAM : NONE - OUT PARAM : NONE - PreCondition : None - PostCondtion : Video Info Header and Capture Sink info cleared - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -void CSkyWalker1Device::RemoveCaptureSink() -{ - - PrintFunctionEntry(__FUNCTION__); - - m_TransportInfo = NULL; - m_CaptureSink = NULL; - - //Release our "lock" on hardware resources. This will allow another - //pin (perhaps in another graph) to acquire them. - InterlockedExchange(&m_PinsWithResources,0); - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); - -} - -void PrintDMAAdapter(PDMA_ADAPTER pDMAAdapter) -{ - SkyWalkerDebugPrint(ENTRY_LEVEL,("pDMAAdapter->Version = %u\n",pDMAAdapter->Version)); - SkyWalkerDebugPrint(ENTRY_LEVEL,("pDMAAdapter->Size = %u\n",pDMAAdapter->Size)); - SkyWalkerDebugPrint(ENTRY_LEVEL,("pDMAAdapter->DmaOperations = 0x%p\n",pDMAAdapter->DmaOperations)); -} - -void PrintMappingInfo(IN PKSMAPPING pMapping) -{ - SkyWalkerDebugPrint(ENTRY_LEVEL,("pMapping->PhysicalAddress = %llu\n",pMapping->PhysicalAddress.QuadPart)); - SkyWalkerDebugPrint(ENTRY_LEVEL,("pMapping->ByteCount = %lu\n",pMapping->ByteCount)); - SkyWalkerDebugPrint(ENTRY_LEVEL,("pMapping->Alignment = %lu\n",pMapping->Alignment)); - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1Device.cpp + Author : + Date : + Purpose : Main Skywalker Device level Implementation + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Header for the Tuner related definitions + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +static ULONG ulDeviceInstance = 0; +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +void PrintDMAAdapter(PDMA_ADAPTER pDMAAdapter); +void PrintMappingInfo(IN PKSMAPPING pMapping); + +/* End of Function prototype definitions */ + +/***************************************************************************** + Function : CSkyWalker1Device::Create + Description : This Function is called during the Add Device IRP Processing + IN PARAM : Pointer to the Enumerated Physical Device + KSDEVICE is a WDM Functional Device which is managed by the AVStream + OUT PARAM : ntStatus of the Device Addition + STATUS_SUCCESS when the Device added to the System + Reason for Failure incase of Error + PreCondition : Driver is Loaded without Functional/ Filter Device Objects + PostCondtion : Functional Device Object [FDO] or Filter Device Object [FiDO] are created + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::Create(IN PKSDEVICE pKSDeviceObject) +{ + NTSTATUS ntCreateStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if (pKSDeviceObject) + { + + //Point the KSDEVICE at our device class. + pKSDeviceObject->Context = this; + + m_pKSDevice = pKSDeviceObject; + + //Make the resource available for a filter to use. + m_ulcResourceUsers = 0; + m_ulCurResourceID = 0; + + //Get the instance number of this device. + m_ulDeviceInstance = ulDeviceInstance++; + + //Hold requests until the device is started + QueueState = HoldRequests; + + //Initialize the stop event to signaled. + KeInitializeEvent(&EvDeviceStopOk, //PKEVENT + NotificationEvent, //Type + TRUE); //State + + //Initialize the remove event to not-signaled. + KeInitializeEvent(&EvDeviceRemoveOk, //PKEVENT + NotificationEvent, //Type + FALSE); //State + + //The KeInitializeSpinLock routine initializes a variable of + //type KSPIN_LOCK. + KeInitializeSpinLock(&DeviceStateLock); + INITIALIZE_PNP_STATE(this); + + //OutstandingIo count biased to 1. + //Transition to 0 during remove device means IO is finished. + //Transition to 1 means the device can be stopped + + ulOutStandingIoCount = 1; + KeInitializeSpinLock(&kIoCountLock); + + } + else + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid KS Device Object Received\n")); + ntCreateStatus = STATUS_INVALID_PARAMETER; + } + + PrintFunctionExit(__FUNCTION__,ntCreateStatus); + return ntCreateStatus; +} + +/***************************************************************************** + Function : CSkyWalker1Device::Start + Description : This function Initializes the Tuner Hardware + IN PARAM : Reference to Device to be Started + IoRequest Packet + Resource List + Translated Resource List + OUT PARAM : ntStatus of the Tuner Start + STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : Stopped Device or Device Enumerated for the First Time + PostCondtion : Device Initialized with the Newly allocated Resources, + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ + +NTSTATUS CSkyWalker1Device::Start( + IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket, + IN PCM_RESOURCE_LIST pResourceList OPTIONAL, + IN PCM_RESOURCE_LIST pTranslatedResourceList OPTIONAL + ) +{ + NTSTATUS ntStartStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = NULL; + PKSFILTERFACTORY pKSFilterFactory = NULL; + + PrintFunctionEntry(__FUNCTION__); + + pDevice = reinterpret_cast(pKSDeviceObject->Context); + + //Initialize the Tuner Hardware. + ntStartStatus = pDevice->InitializeTuner(pKSDeviceObject,pIoRequestPacket); + SkyWalkerDebugPrint(EXTREME_LEVEL,("USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE = %d",USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE)); + + if (ntStartStatus == STATUS_SUCCESS) + { + //Create the the Filter Factory. This factory is used to + //create instances of the filter. + ntStartStatus = BdaCreateFilterFactoryEx( + pKSDeviceObject, + &SkyWalker1TunerFilterDescriptor, + &TunerFilterTemplate, + &pKSFilterFactory + ); + } + + if ((ntStartStatus == STATUS_SUCCESS) && pKSFilterFactory) + { + BdaFilterFactoryUpdateCacheData( + pKSFilterFactory, + TunerFilterTemplate.pFilterDescriptor + ); + } + + + PrintFunctionExit(__FUNCTION__,ntStartStatus); + return ntStartStatus; +} + +/***************************************************************************** + Function : CSkyWalker1Device::Stop + Description : This routine is invoked when the device is stopped. + This routine services Irp of minor type IRP_MN_STOP_DEVICE + IN PARAM : Pointer to KS Device Object + STOP DEVICE Irp + OUT PARAM : ntStatus of the Device Stop + STATUS_SUCCESS on Successful execution + else Error + PreCondition : NONE + PostCondtion : USB Device Stopped + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::Stop( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + NTSTATUS ntDeviceStopStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + if(m_pDMAAdapter) + { + m_pDMAAdapter->DmaOperations->PutDmaAdapter(m_pDMAAdapter); + m_pDMAAdapter = NULL; + } + + //Maintain the USB ntStatus i.e Remove the Selected Configuration by sending Null descriptor + ntDeviceStopStatus = StopUsbDevice(pKSDeviceObject,pIoRequestPacket); + + PrintFunctionExit(__FUNCTION__,ntDeviceStopStatus); + + return ntDeviceStopStatus; +} + + +/***************************************************************************** + Function : CSkyWalker1Device::Close + Description : This routine is invoked when the device is Removed. + This routine services Irp of minor type IRP_MN_REMOVE_DEVICE + IN PARAM : Pointer to KS Device Object + STOP DEVICE Irp + OUT PARAM : ntStatus of the Device Remove + STATUS_SUCCESS on Successful execution + else Error + PreCondition : NONE + PostCondtion : USB Device Removed + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::Close( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + NTSTATUS ntDeviceCloseStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + ntDeviceCloseStatus = RemoveUsbDevice(pKSDeviceObject,pIoRequestPacket); + + PrintFunctionExit(__FUNCTION__,ntDeviceCloseStatus); + + return ntDeviceCloseStatus; +} + +/***************************************************************************** + Function : CSkyWalker1Device::SetPower + Description : This routine is invoked when the Power Irp is received + This routine services Irp of minor type IRP_MJ_POWER + IN PARAM : Pointer to KS Device Object + STOP DEVICE Irp + OUT PARAM : ntStatus of the Set POwer + STATUS_SUCCESS on Successful execution + else Error + PreCondition : NONE + PostCondtion : Power Condition Managed + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::SetPower( + IN PKSDEVICE pKSDeviceObject, //Pointer to the device object + //provided by the system. + IN PIRP pIoRequestPacket, //Pointer to the IRP related to this request. + IN DEVICE_POWER_STATE To, //Requested power state. + IN DEVICE_POWER_STATE From //Current power state. +) +{ + NTSTATUS ntSetPowerStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + //Set USB Power condition from here + + PrintFunctionExit(__FUNCTION__,ntSetPowerStatus); + + return ntSetPowerStatus; +} + + +/***************************************************************************** + Function : CSkyWalker1Device::InitializeTuner + Description : This function is used to setup and initialize the Tuner + Interface + IN PARAM : Reference to Device to be Started + IoRequest Packet + OUT PARAM : ntStatus of the Tuner Initializations + STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : Stopped Device or Device Enumerated for the First Time + PostCondtion : Device Initialized with the Newly allocated Resources, + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::InitializeTuner( + IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + NTSTATUS ntInitStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + //Initialize the USB hardware here. + ntInitStatus = InitializeUsbDevice(pKSDeviceObject,pIoRequestPacket); + if(!NT_SUCCESS(ntInitStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Failed to Initialize the USB Device\n")); + goto ExitInitTuner; + } + + //Setup the SkyWalker1 Device + ntInitStatus = SetupTunerPower(pKSDeviceObject,SWITCH_ON_TUNER); + if(!NT_SUCCESS(ntInitStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Failed to Setup the SkyWalker1 Device\n")); + goto ExitInitTuner; + } + + //Initialize Adapter + ntInitStatus = InitializeAdapterStream(pKSDeviceObject); + if(!NT_SUCCESS(ntInitStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Failed to Initialize the Adapter Stream\n")); + goto ExitInitTuner; + } + +ExitInitTuner: + + PrintFunctionExit(__FUNCTION__,ntInitStatus); + return ntInitStatus; +} + +/***************************************************************************** + Function : CSkyWalker1Device::InitializeAdapterStream + Description : This function is used to Register the Adapter Object and + setup the Private Memory for the Streams + IN PARAM : Reference to KS Device Object + OUT PARAM : ntStatus of the Adapter Stream Initialization + STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Adapter Object Registered with the AVStream and Streaming + Memory allocated + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::InitializeAdapterStream( + IN PKSDEVICE pKSDeviceObject) +{ + + NTSTATUS ntInitStatus = STATUS_SUCCESS; + DEVICE_DESCRIPTION DeviceDescription; //Object to hold the Device description + ULONG ulMaxDMAMapRegisters = 0L; + + PrintFunctionEntry(__FUNCTION__); + + //Create DMA adapter + + memset(&DeviceDescription, 0, sizeof(DeviceDescription)); + + DeviceDescription.Version = DEVICE_DESCRIPTION_VERSION; + DeviceDescription.Master = TRUE; + DeviceDescription.ScatterGather = TRUE; + DeviceDescription.Dma32BitAddresses = TRUE; + DeviceDescription.Dma64BitAddresses = FALSE; + DeviceDescription.DmaChannel = ((ULONG) ~0); + DeviceDescription.InterfaceType = PCIBus; + DeviceDescription.MaximumLength = 0xfffffff8; + + //not used + DeviceDescription.IgnoreCount; + DeviceDescription.DemandMode; + DeviceDescription.AutoInitialize; + DeviceDescription.DmaWidth; + DeviceDescription.Reserved1; + DeviceDescription.DmaSpeed; + DeviceDescription.DmaPort; + + //The IoGetDmaAdapter routine returns a pointer to the DMA adapter structure + //for a physical device object. + m_pDMAAdapter = IoGetDmaAdapter( + pKSDeviceObject->PhysicalDeviceObject, //Pointer to Physical Device Object + //requesting the DMA Adapter structure + &DeviceDescription, //Pointer to the Device Descriptor Structure which + //describes the attributes of the Structure + &ulMaxDMAMapRegisters); //Maximum Map registers that driver can allocate for + //Dma transfer + if(!IS_VALID(m_pDMAAdapter)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to get the Dma Adapter for the Device\n")); + ntInitStatus = STATUS_UNSUCCESSFUL; + goto ExitInitAdapter; + + } + + if(!IS_VALID(m_pDMAAdapter->DmaOperations)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("No Dma Operatios found for the DMA Adapter\n")); + ntInitStatus = STATUS_UNSUCCESSFUL; + goto ExitInitAdapter; + } + + //Print the DMA Adapter details + PrintDMAAdapter(m_pDMAAdapter); + SkyWalkerDebugPrint(EXTREME_LEVEL,("Number of Map Registers = %lu\n",ulMaxDMAMapRegisters)); + + //Register the DMA Adapter with the AVStream + //The KsDeviceRegisterAdapterObject function registers a DMA adapter object with + //AVStream for performing scatter/gather DMA on the specified device. + KsDeviceRegisterAdapterObject( + pKSDeviceObject, //Device For which to register an adapter object + m_pDMAAdapter, //Pointer to DMA Adapter (IoGetDmaAdapter) + 0xfffffff8, //Maximum number of bytes that device can handle for a single mapping + sizeof(KSMAPPING)); //Number of bytes each entry in the mapping table requires + + //Allocate the Space for storing the Streaming contents + KeInitializeEvent (&m_HardwareEvent,SynchronizationEvent,FALSE); + + RtlZeroMemory(pUsbStreamIrp,sizeof(pUsbStreamIrp)); + +ExitInitAdapter: + + PrintFunctionExit(__FUNCTION__,ntInitStatus); + return ntInitStatus; +} + +/***************************************************************************** + Function : CSkyWalker1Device::ReadStream + Description : This function is called when streaming data from the USb + is requested to read.This function is called when the streaming + is started and every time after Stream processing to read a + new Stream + IN PARAM : Index of the Stream to Read + OUT PARAM : ntStatus of the Read Stream + STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Stream Read + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::ReadStream(IN ULONG ulStreamIndex) +{ + NTSTATUS ntReadStreamStatus = STATUS_SUCCESS; + PUCHAR pStreamBuffer = NULL; + ULONG ulPacketCount = PACKET_PER_FRAME; + ULONG ulPacketSize = MAX_BULK_PACKET_SIZE ; + + PrintFunctionEntry(__FUNCTION__); + + //Get the Stream buffer related to the Current Stream index + pStreamBuffer = m_SynthesisBuffer[ulStreamIndex]; + //Set the number of Bytes read + m_NumberOfBytesRead[ulStreamIndex] = 0; + + //Send the Read request of the 4K Size Each + for(ULONG ulPacketIndex = 0; ulPacketIndex <= ulPacketCount-1 ; ulPacketIndex++) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("pStreamBuffer[%03lu] = 0x%p",ulPacketIndex,pStreamBuffer)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("m_SampleSize = %lu",m_SampleSize)); + if(IS_VALID(pStreamBuffer)) + { + if(ulPacketIndex == 23) + { + ulPacketSize = 2048; + } + ntReadStreamStatus = ReadWriteUsbDevice( m_pKSDevice, + ulStreamIndex, + ulPacketIndex, + pStreamBuffer, + ulPacketSize , + true + ); + + + if(NT_SUCCESS(ntReadStreamStatus)) + { + SkyWalkerDebugPrint(EXTREME_LEVEL, ("Sent the Stream Read Request\n")); + } + else + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Send Stream Read from the Device")); + } + + pStreamBuffer += ulPacketSize; + } + else + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid Stream Aborting Stream Read\n")); + break; + } + } + PrintFunctionExit(__FUNCTION__,ntReadStreamStatus); + + return ntReadStreamStatus; + + +} + +/***************************************************************************** + Function : CSkyWalker1Device::GetStatus + Description : This function is used to Get the Current ntStatus of the + Tuner i.e. Current Carrier Freq. Signal Locked status etc. + IN PARAM : Device ntStatus + OUT PARAM : STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Device ntStatus updated and returned + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::GetStatus(OUT PBDATUNER_DEVICE_STATUS pDeviceStatus) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + pDeviceStatus->fCarrierPresent = FALSE; + pDeviceStatus->fSignalLocked = FALSE; + pDeviceStatus->dwSignalQuality = 0; + pDeviceStatus->dwSignalStrength = 0; + + PrintFunctionEntry(__FUNCTION__); + + if(m_HardwareState != HardwareRunning) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("Streaming is not started yet\n")); + ntStatus = STATUS_UNSUCCESSFUL; + goto ExitGetStatus; + + } + + if(TimeToReadSignalStatus()) + { + //It's Time to read Signal Status + //Get the signal status from the HW here + ReadTunerSignalStrength(m_pKSDevice, + &m_TunerStatus.dwSignalStrength); + + m_TunerStatus.dwSignalQuality = (m_TunerStatus.dwSignalStrength * 100)/65535; + + GetSignalStatus(m_pKSDevice, + &m_TunerStatus.fSignalLocked); + + + if(m_TunerStatus.fSignalLocked) + { + m_TunerStatus.fCarrierPresent = TRUE; + } + } + + *pDeviceStatus = m_TunerStatus; + +#ifdef FAKE_SIGNAL + pDeviceStatus->dwSignalStrength = 2; + pDeviceStatus->dwSignalQuality = 99; + pDeviceStatus->fCarrierPresent = TRUE; + pDeviceStatus->fSignalLocked = TRUE; +#endif + +ExitGetStatus : + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; +} + +/***************************************************************************** + Function : CSkyWalker1Device::TimeToReadSignalStatus + Description : This function is used Check whether time to read + the status from device has occurred or not + IN PARAM : NONE + OUT PARAM : TRUE = Time to read status from Device, + FALSE otherwise + PreCondition : None + PostCondtion : Whether time to read from device has occurred or not + is checked + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +BOOLEAN CSkyWalker1Device::TimeToReadSignalStatus(void) +{ + LARGE_INTEGER CurrentTime; + ULONG ulReadPeriod = 10 * 1000 * 1000; + KeQuerySystemTime (&CurrentTime); + + //If Current time is greater than the last + if(m_TunerStatus.fSignalLocked == FALSE) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Not Locked Reading Status @ 100msec\n")); + ulReadPeriod = 10 * 1000 * 100; + } + if(CurrentTime.QuadPart - m_PreviousStatusReadTime.QuadPart >= ulReadPeriod) + { + m_PreviousStatusReadTime.QuadPart = CurrentTime.QuadPart; + SkyWalkerDebugPrint(EXTREME_LEVEL,("Time to read signal status\n")); + return TRUE; + } + + return FALSE; +} + +/***************************************************************************** + Function : CSkyWalker1Device::Acquire + Description : This function is used to set the Tuner parameters and + called once when the tuner resources are acquired + From this function various device related command are executed + to set the device parameters + IN PARAM : New Device Parameters to be set + Resource ID + OUT PARAM : STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Device ntStatus updated and returned + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::Acquire( + IN PBDATUNER_DEVICE_PARAMETER pNewResource, + OUT PULONG pulAcquiredResourceID + ) +{ + NTSTATUS ntAcquireStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + //Continue only if the Device is acquired currently + if (!m_ulcResourceUsers) + { + m_CurResource = *pNewResource; + + //Generate a new resource ID and hand it back. + m_ulCurResourceID += 25; + *pulAcquiredResourceID = m_ulCurResourceID; + m_ulcResourceUsers += 1; + + //Configure the new resource on the hardware here. + //Send the Tune, SetLnbVoltage etc Commands from here + ConfigureTuner(m_pKSDevice,pNewResource); + + } + else + { + //Only one active filter is allowed + ntAcquireStatus = STATUS_DEVICE_BUSY; + } + + PrintFunctionExit(__FUNCTION__,ntAcquireStatus); + return ntAcquireStatus; +} + +/***************************************************************************** + Function : CSkyWalker1Device::Update + Description : This function is used to set the Tuner parameters and + called everytime after the tuner resources are acquired + From this function various device related command are executed + to set the device parameters + IN PARAM : New Device Parameters to be set + Resource ID + OUT PARAM : STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Device ntStatus updated and returned + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::Update( + IN PBDATUNER_DEVICE_PARAMETER pNewResource, + IN ULONG ulResourceID + ) +{ + NTSTATUS ntUpdateStatus = STATUS_SUCCESS; + LONGLONG ulhzFrequency; + + PrintFunctionEntry(__FUNCTION__); + + //Continue only if the Device is acquired currently + if (m_ulcResourceUsers && (ulResourceID == m_ulCurResourceID)) + { + m_CurResource = *pNewResource; + + //Configure the new resource on the hardware here. + //Send the Tune, SetLnbVoltage etc Commands from here + ConfigureTuner(m_pKSDevice,pNewResource); + + } + else + { + //Only one active filter is allowed + ntUpdateStatus = STATUS_INVALID_DEVICE_REQUEST; + } + + PrintFunctionExit(__FUNCTION__,ntUpdateStatus); + return ntUpdateStatus; +} + +/***************************************************************************** + Function : CSkyWalker1Device::SendDiseqcCommand + Description : This function is used to send the Diseqc Command to the Tuner + IN PARAM : Diseqc Command to be sent to the Tuner + Resource ID + OUT PARAM : STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Diseqc Command sent to the Tuner + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::SendDiseqcCommand( + IN PDISEQC_COMMAND pDiseqcCommand, + IN ULONG ulResourceID + ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + //Continue only if the Device is acquired currently + //if (m_ulcResourceUsers && (ulResourceID == m_ulCurResourceID)) + { + + SkyWalkerDebugPrint(EXTREME_LEVEL,("m_ulcResourceUsers = %lu\n",m_ulcResourceUsers)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("ulResourceID = %lu\n",ulResourceID)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("m_ulCurResourceID = %lu\n",m_ulCurResourceID)); + //Send the Diseqc Command to the Tuner device + DiseqcCommand(m_pKSDevice,pDiseqcCommand); + + } + //else + //{ + // //Only one active filter is allowed + // ntStatus = STATUS_INVALID_DEVICE_REQUEST; + // } + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; +} + +/***************************************************************************** + Function : CSkyWalker1Device::Release + Description : This function is used to decrement the Resource User count + to allow other filters to use the resources + IN PARAM : Resource ID + OUT PARAM : STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Device ntStatus updated and returned + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::Release(IN ULONG ulResourceID) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + if(m_ulcResourceUsers && (ulResourceID == m_ulCurResourceID)) + { + //Free the resource to be used by another filter. + m_ulcResourceUsers--; + } + else + { + ntStatus = STATUS_INVALID_DEVICE_REQUEST; + } + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; +} + +/***************************************************************************** + Function : CSkyWalker1Device::StartStream + Description : This function is used to Initialize the Prestreaming + parameters and also it sends the Streaming command to + the Tuner + IN PARAM : NONE + OUT PARAM : STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Streaming Started on successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::StartStream () +{ + + NTSTATUS ntStreamStartStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + //Initializing the Streaming Parameters + + m_TimePerFrame = m_TransportInfo->AvgTimePerFrame; + m_SampleSize = m_TransportInfo->ulcbPhyiscalFrame; + m_PacketSize = m_TransportInfo->ulcbPhyiscalPacket; + m_PacketsPerSample = m_TransportInfo->ulcbPhyiscalFrame / m_TransportInfo->ulcbPhyiscalPacket; + m_TunerStatus.fCarrierPresent = FALSE; + m_TunerStatus.fSignalLocked = FALSE; + m_TunerStatus.dwSignalQuality = 0; + m_TunerStatus.dwSignalStrength = 0; + + + //Allocate a scratch buffer for the Streaming Buffer. + + for(int nBufferIndex = 0 ; + nBufferIndex < SIZEOF_ARRAY(m_SynthesisBuffer); + nBufferIndex++) + { + m_SynthesisBuffer[nBufferIndex] = reinterpret_cast ( + ExAllocatePoolWithTag ( + NonPagedPool, + m_SampleSize, + CAPTURE_MEM_TAG + ) + ); + + if (!IS_VALID(m_SynthesisBuffer)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Insufficient Resource for Scatter/Gather DMA\n")); + ntStreamStartStatus = STATUS_INSUFFICIENT_RESOURCES; + goto ExitStreamStart; + } + + } + + //Send Device Streaming Start Control + SetStreamingControl(m_pKSDevice,1); + m_HardwareState = HardwareRunning; + +ExitStreamStart: + PrintFunctionExit(__FUNCTION__,ntStreamStartStatus); + return ntStreamStartStatus; + +} + +/***************************************************************************** + Function : CSkyWalker1Device::PauseStream + Description : This function is used to Pause/Run the Streaming if On/Off. + IN PARAM : True : Pause Streaming , False : Start Streaming + OUT PARAM : STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Streaming Paused/Started on successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::PauseStream (BOOLEAN bPausing) +{ + + NTSTATUS ntStreamPauseStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + //If Streaming is in Progress then Stop the Streaming by + //Stop sending read requests and Cancelling any on going IRP + if (bPausing && (m_HardwareState == HardwareRunning)) + { + m_StopHardware = TRUE; + //Stop Streaming + SetStreamingControl(m_pKSDevice,0); + + for(int ulPacketIndex = 0; + ulPacketIndex < SIZEOF_ARRAY(pUsbStreamIrp) ; + ulPacketIndex++) + { + if(pUsbStreamIrp[ulPacketIndex]) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("Cancelling pUsbStreamIrp[%lu] = 0x%p\n", + ulPacketIndex,pUsbStreamIrp[ulPacketIndex])); + IoCancelIrp(pUsbStreamIrp[ulPacketIndex]); + pUsbStreamIrp[ulPacketIndex] = NULL; + + KeWaitForSingleObject ( + &m_HardwareEvent, + Suspended, + KernelMode, + FALSE, + NULL + ); + + KeResetEvent(&m_HardwareEvent); + m_StopHardware = TRUE; + } + } + + m_HardwareState = HardwarePaused; + + } + else if (!bPausing && (m_HardwareState == HardwarePaused) ) + { + + m_HardwareState = HardwareRunning; + + //Start Streaming + SetStreamingControl(m_pKSDevice,1); + + } + + PrintFunctionExit(__FUNCTION__,ntStreamPauseStatus); + return ntStreamPauseStatus; + +} + +/***************************************************************************** + Function : CSkyWalker1Device::StopStream + Description : This function is used to Stop the Streaming if On. + IN PARAM : NONE + OUT PARAM : STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Streaming Stopped on successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device::StopStream () +{ + + NTSTATUS ntStreamStopStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + //Stop the Streaming in case it's already on + if (m_HardwareState == HardwareRunning) + { + m_StopHardware = TRUE; + //Stop Streaming + SetStreamingControl(m_pKSDevice,0); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("SIZEOF_ARRAY[pUsbStreamIrp] = %d\n", + SIZEOF_ARRAY(pUsbStreamIrp))); + + for(int ulPacketIndex = 0; + ulPacketIndex < SIZEOF_ARRAY(pUsbStreamIrp) ; + ulPacketIndex++) + { + if(pUsbStreamIrp[ulPacketIndex]) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("Cancelling pUsbStreamIrp[%lu] = 0x%p\n", + ulPacketIndex,pUsbStreamIrp[ulPacketIndex])); + IoCancelIrp(pUsbStreamIrp[ulPacketIndex]); + pUsbStreamIrp[ulPacketIndex] = NULL; + + KeWaitForSingleObject ( + &m_HardwareEvent, + Suspended, + KernelMode, + FALSE, + NULL + ); + + KeResetEvent(&m_HardwareEvent); + m_StopHardware = TRUE; + } + } + + } + + m_HardwareState = HardwareStopped; + + for(int nBufferIndex = 0 ; + nBufferIndex < SIZEOF_ARRAY(m_SynthesisBuffer) ; + nBufferIndex++) + { + if (m_SynthesisBuffer[nBufferIndex]) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("Freeing Streaming Buffer m_SynthesisBuffer[%lu] = 0x%p\n", + nBufferIndex,m_SynthesisBuffer[nBufferIndex])); + ExFreePool (m_SynthesisBuffer[nBufferIndex]); + m_SynthesisBuffer[nBufferIndex] = NULL; + } + } + + PrintFunctionExit(__FUNCTION__,ntStreamStopStatus); + return ntStreamStopStatus; + +} + +/***************************************************************************** + Function : CSkyWalker1Device::ProcessStream + Description : This function is used to Process the Streaming Data after + reading it.This function is called from the USB Read Write + Completion routine + IN PARAM : Index of the Stream whose read completed + OUT PARAM : STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Read Stream is processed and Streaming data is filled into the + used Buffer. + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +void CSkyWalker1Device::ProcessStream(ULONG ulStreamIndex) +{ + + PrintFunctionEntry(__FUNCTION__); + + //The hardware can be in a pause state in which case, it issues interrupts + //but does not complete mappings. In this case, don't bother synthesizing + //a frame and doing the work of looking through the mappings table. + + if (m_HardwareState == HardwareRunning) + { + + SkyWalkerDebugPrint(EXTREME_LEVEL,("(%d * %d) = %lu\n", TRANSPORT_PACKET_SIZE, + TRANSPORT_PACKET_COUNT, + m_SampleSize)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("m_NumberOfBytesRead[%lu] = %lu\n", + ulStreamIndex, + m_NumberOfBytesRead[ulStreamIndex])); + + if (InterlockedCompareExchange((LONG *)&m_NumberOfBytesRead[ulStreamIndex], + m_SampleSize, m_SampleSize) == m_SampleSize) + { + //Inform the capture sink + m_CaptureSink->ReleaseStream(ulStreamIndex); + } + + } + + if (m_StopHardware) + { + + //If someone is waiting on the hardware to stop, raise the stop + //event and clear the flag. + SkyWalkerDebugPrint(EXTREME_LEVEL,("Sending the Stop Event\n")); + m_StopHardware = FALSE; + KeSetEvent (&m_HardwareEvent, IO_NO_INCREMENT, FALSE); + } + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); + +} + +/***************************************************************************** + Function : CSkyWalker1Device::SetupCaptureSink + Description : Acquire hardware resources for the capture hardware. + If the resources are already acquired, this will return + an error.The hardware configuration must be passed as + a VideoInfoHeader. + IN PARAM : The capture sink attempting to acquire + resources. When scatter /gather mappings are completed, + the capture sink specified here is what is notified + of the completions. + Information about the capture stream. + This **MUST** remain stable until the caller releases + hardware resources. Note that this could also be guaranteed + by bagging it in the device object bag as well. + OUT PARAM : STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : None + PostCondtion : Capture Pin to the notified and Video Info Header set in case + of successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CSkyWalker1Device:: SetupCaptureSink( + IN ICaptureSink *CaptureSink, + IN PBDA_TRANSPORT_INFO TransportInfo + ) +{ + + NTSTATUS ntStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + //If we're the first pin to go into acquire (remember we can have + //a filter in another graph going simultaneously), grab the resources. + if (InterlockedCompareExchange(&m_PinsWithResources,1,0) == 0) + { + m_TransportInfo = TransportInfo; + m_CaptureSink = CaptureSink; + + } + else + { + + ntStatus = STATUS_SHARING_VIOLATION; + } + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; + +} + +/***************************************************************************** + Function : CSkyWalker1Device::RemoveCaptureSink + Description : Release hardware resources. This should only be called by + an object which has acquired them. + IN PARAM : NONE + OUT PARAM : NONE + PreCondition : None + PostCondtion : Video Info Header and Capture Sink info cleared + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +void CSkyWalker1Device::RemoveCaptureSink() +{ + + PrintFunctionEntry(__FUNCTION__); + + m_TransportInfo = NULL; + m_CaptureSink = NULL; + + //Release our "lock" on hardware resources. This will allow another + //pin (perhaps in another graph) to acquire them. + InterlockedExchange(&m_PinsWithResources,0); + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); + +} + +void PrintDMAAdapter(PDMA_ADAPTER pDMAAdapter) +{ + SkyWalkerDebugPrint(ENTRY_LEVEL,("pDMAAdapter->Version = %u\n",pDMAAdapter->Version)); + SkyWalkerDebugPrint(ENTRY_LEVEL,("pDMAAdapter->Size = %u\n",pDMAAdapter->Size)); + SkyWalkerDebugPrint(ENTRY_LEVEL,("pDMAAdapter->DmaOperations = 0x%p\n",pDMAAdapter->DmaOperations)); +} + +void PrintMappingInfo(IN PKSMAPPING pMapping) +{ + SkyWalkerDebugPrint(ENTRY_LEVEL,("pMapping->PhysicalAddress = %llu\n",pMapping->PhysicalAddress.QuadPart)); + SkyWalkerDebugPrint(ENTRY_LEVEL,("pMapping->ByteCount = %lu\n",pMapping->ByteCount)); + SkyWalkerDebugPrint(ENTRY_LEVEL,("pMapping->Alignment = %lu\n",pMapping->Alignment)); + } \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Installer.inf b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Installer.inf index 1a42430..ea3744c 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Installer.inf +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Installer.inf @@ -1,142 +1,142 @@ -; SkyWalker1Installer.INF -- This file installs SkyWalker1 Driver -; -[Version] -signature="$CHICAGO$" -Class=Media -ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} -Provider=%SGI% -CatalogFile=SkyWalker1Installer.cat -DriverVer= 8/17/2009 - -; F i l e c o p y i n g s e c t i o n s (where the files go to). -; -[DestinationDirs] -DefaultDestDir=10,system32\drivers - -[Manufacturer] -%SGI%=SGI - -[ControlFlags] -;ExcludeFromSelect=* -;ExcludeFromSelect.NT=* - -; =================== Generic ================================== - -[SGI] -%SkyWalker1.DeviceDesc%=Skywalker1.Device,USB\VID_09C0&PID_0203 ;SkyWalker1 - -[Skywalker1.Device] -Include = ks.inf, kscaptur.inf, bda.inf -needs = KS.Registration, KSCAPTUR.Registration, BDA.Installation -AddReg = Skywalker1.AddReg -CopyFiles = Skywalker1.CopyDrivers - -[Skywalker1.Device.NT] -Include = ks.inf, kscaptur.inf, bda.inf -needs = KS.Registration.NT, KSCAPTUR.Registration.NT, BDA.Installation.NT -;AddReg = Skywalker1.AddReg -CopyFiles = Skywalker1.CopyDrivers -; KnownFiles = Skywalker1.KnownFiles - -[Skywalker1.Device.NT.Services] -Addservice=SkyWalker1TVTuner, 0x00000002, Skywalker1.AddService - -[Skywalker1.AddService] -DisplayName=%SkyWalker1.FriendlyName% -ServiceType=1 ; SERVICE_KERNEL_DRIVER -StartType=3 ; SERVICE_DEMAND_START -ErrorControl=1 ; SERVICE_ERROR_NORMAL -ServiceBinary=%10%\System32\Drivers\SkyWalker1TVTuner.sys -LoadOrderGroup=ExtendedBase - -[Skywalker1.CopyDrivers] -SkyWalker1TVTuner.sys - -[Skywalker1.AddReg] -HKR,,DevLoader,,*NTKERN -HKR,,NTMPDriver,,SkyWalker1TVTuner.sys -HKR,,PageOutWhenUnopened,3,01 - -[Skywalker1.Device.Interfaces] -AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,Skywalker1.Receiver.Interfaces -AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,Skywalker1.Tuner.Interfaces - -[Skywalker1.Device.NT.Interfaces] -AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,Skywalker1.Receiver.Interfaces -AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,Skywalker1.Tuner.Interfaces - -[Skywalker1.Tuner.Interfaces] -AddReg=Skywalker1.Tuner.Interfaces.AddReg - -[Skywalker1.Tuner.Interfaces.AddReg] -HKR,,CLSID,,%KSProxy.CLSID% -HKR,,FriendlyName,,%SkyWalker1.Tuner.FriendlyName% - -[Skywalker1.Receiver.Interfaces] -AddReg=Skywalker1.Receiver.Interfaces.AddReg - -[Skywalker1.Receiver.Interfaces.AddReg] -HKR,,CLSID,,%KSProxy.CLSID% -HKR,,FriendlyName,,%SkyWalker1.Receiver.FriendlyName% - - -[Strings] -;non-localizable -SGI="Plethorasoft" -MfgName="SGI" -SkyWalker1.DeviceDesc="SkyWalker1 BDA TVTuner" -SkyWalker1.Tuner.FriendlyName="SkyWalker1 TV Tuner" -SkyWalker1.Receiver.FriendlyName="SkyWalker1 TV Receiver" -SkyWalker1.Tuner="SkyWalker1.Tuner" -KSProxy.CLSID="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" -KSCATEGORY_BDA_NETWORK_TUNER="{71985F48-1CA1-11d3-9CC8-00C04F7971E0}" -KSCATEGORY_BDA_RECEIVER_COMPONENT="{FD0A5AF4-B41D-11d2-9C95-00C04F7971E0}" -SKYWALKER_TUNER="{5C4E764F-AB43-46A9-B21E-8529C70F0A23}" -SKYWALKER_CAPTURE="{0F8F74D9-E524-4D05-BB60-F0C69ACB1756}" - -; -; ServiceType values -SERVICE_KERNEL_DRIVER = 0x00000001 -SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 -SERVICE_ADAPTER = 0x00000004 -SERVICE_RECOGNIZER_DRIVER = 0x00000008 -SERVICE_WIN32_OWN_PROCESS = 0x00000010 -SERVICE_WIN32_SHARE_PROCESS = 0x00000020 -SERVICE_INTERACTIVE_PROCESS = 0x00000100 -SERVICE_INTERACTIVE_SHARE_PROCESS = 0x00000120 - -; StartType values -SERVICE_BOOT_START = 0x00000000 -SERVICE_SYSTEM_START = 0x00000001 -SERVICE_AUTO_START = 0x00000002 -SERVICE_DEMAND_START = 0x00000003 -SERVICE_DISABLED = 0x00000004 - -; ErrorControl values -SERVICE_ERROR_IGNORE = 0x00000000 -SERVICE_ERROR_NORMAL = 0x00000001 -SERVICE_ERROR_SEVERE = 0x00000002 -SERVICE_ERROR_CRITICAL = 0x00000003 - -; Characteristic flags -NCF_VIRTUAL = 0x0001 -NCF_WRAPPER = 0x0002 -NCF_PHYSICAL = 0x0004 -NCF_HIDDEN = 0x0008 -NCF_NO_SERVICE = 0x0010 -NCF_NOT_USER_REMOVABLE = 0x0020 -NCF_HAS_UI = 0x0080 -NCF_MODEM = 0x0100 - -; Registry types -REG_MULTI_SZ = 0x10000 -REG_EXPAND_SZ = 0x20000 -REG_DWORD = 0x10001 - -; Win9x Compatible Types -REG_BINARY = 17 -REG_SZ = 0 - -; Service install flags -SPSVCINST_TAGTOFRONT = 0x1 +; SkyWalker1Installer.INF -- This file installs SkyWalker1 Driver +; +[Version] +signature="$CHICAGO$" +Class=Media +ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} +Provider=%SGI% +CatalogFile=SkyWalker1Installer.cat +DriverVer= 8/17/2009 + +; F i l e c o p y i n g s e c t i o n s (where the files go to). +; +[DestinationDirs] +DefaultDestDir=10,system32\drivers + +[Manufacturer] +%SGI%=SGI + +[ControlFlags] +;ExcludeFromSelect=* +;ExcludeFromSelect.NT=* + +; =================== Generic ================================== + +[SGI] +%SkyWalker1.DeviceDesc%=Skywalker1.Device,USB\VID_09C0&PID_0203 ;SkyWalker1 + +[Skywalker1.Device] +Include = ks.inf, kscaptur.inf, bda.inf +needs = KS.Registration, KSCAPTUR.Registration, BDA.Installation +AddReg = Skywalker1.AddReg +CopyFiles = Skywalker1.CopyDrivers + +[Skywalker1.Device.NT] +Include = ks.inf, kscaptur.inf, bda.inf +needs = KS.Registration.NT, KSCAPTUR.Registration.NT, BDA.Installation.NT +;AddReg = Skywalker1.AddReg +CopyFiles = Skywalker1.CopyDrivers +; KnownFiles = Skywalker1.KnownFiles + +[Skywalker1.Device.NT.Services] +Addservice=SkyWalker1TVTuner, 0x00000002, Skywalker1.AddService + +[Skywalker1.AddService] +DisplayName=%SkyWalker1.FriendlyName% +ServiceType=1 ; SERVICE_KERNEL_DRIVER +StartType=3 ; SERVICE_DEMAND_START +ErrorControl=1 ; SERVICE_ERROR_NORMAL +ServiceBinary=%10%\System32\Drivers\SkyWalker1TVTuner.sys +LoadOrderGroup=ExtendedBase + +[Skywalker1.CopyDrivers] +SkyWalker1TVTuner.sys + +[Skywalker1.AddReg] +HKR,,DevLoader,,*NTKERN +HKR,,NTMPDriver,,SkyWalker1TVTuner.sys +HKR,,PageOutWhenUnopened,3,01 + +[Skywalker1.Device.Interfaces] +AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,Skywalker1.Receiver.Interfaces +AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,Skywalker1.Tuner.Interfaces + +[Skywalker1.Device.NT.Interfaces] +AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,Skywalker1.Receiver.Interfaces +AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,Skywalker1.Tuner.Interfaces + +[Skywalker1.Tuner.Interfaces] +AddReg=Skywalker1.Tuner.Interfaces.AddReg + +[Skywalker1.Tuner.Interfaces.AddReg] +HKR,,CLSID,,%KSProxy.CLSID% +HKR,,FriendlyName,,%SkyWalker1.Tuner.FriendlyName% + +[Skywalker1.Receiver.Interfaces] +AddReg=Skywalker1.Receiver.Interfaces.AddReg + +[Skywalker1.Receiver.Interfaces.AddReg] +HKR,,CLSID,,%KSProxy.CLSID% +HKR,,FriendlyName,,%SkyWalker1.Receiver.FriendlyName% + + +[Strings] +;non-localizable +SGI="Plethorasoft" +MfgName="SGI" +SkyWalker1.DeviceDesc="SkyWalker1 BDA TVTuner" +SkyWalker1.Tuner.FriendlyName="SkyWalker1 TV Tuner" +SkyWalker1.Receiver.FriendlyName="SkyWalker1 TV Receiver" +SkyWalker1.Tuner="SkyWalker1.Tuner" +KSProxy.CLSID="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" +KSCATEGORY_BDA_NETWORK_TUNER="{71985F48-1CA1-11d3-9CC8-00C04F7971E0}" +KSCATEGORY_BDA_RECEIVER_COMPONENT="{FD0A5AF4-B41D-11d2-9C95-00C04F7971E0}" +SKYWALKER_TUNER="{5C4E764F-AB43-46A9-B21E-8529C70F0A23}" +SKYWALKER_CAPTURE="{0F8F74D9-E524-4D05-BB60-F0C69ACB1756}" + +; +; ServiceType values +SERVICE_KERNEL_DRIVER = 0x00000001 +SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 +SERVICE_ADAPTER = 0x00000004 +SERVICE_RECOGNIZER_DRIVER = 0x00000008 +SERVICE_WIN32_OWN_PROCESS = 0x00000010 +SERVICE_WIN32_SHARE_PROCESS = 0x00000020 +SERVICE_INTERACTIVE_PROCESS = 0x00000100 +SERVICE_INTERACTIVE_SHARE_PROCESS = 0x00000120 + +; StartType values +SERVICE_BOOT_START = 0x00000000 +SERVICE_SYSTEM_START = 0x00000001 +SERVICE_AUTO_START = 0x00000002 +SERVICE_DEMAND_START = 0x00000003 +SERVICE_DISABLED = 0x00000004 + +; ErrorControl values +SERVICE_ERROR_IGNORE = 0x00000000 +SERVICE_ERROR_NORMAL = 0x00000001 +SERVICE_ERROR_SEVERE = 0x00000002 +SERVICE_ERROR_CRITICAL = 0x00000003 + +; Characteristic flags +NCF_VIRTUAL = 0x0001 +NCF_WRAPPER = 0x0002 +NCF_PHYSICAL = 0x0004 +NCF_HIDDEN = 0x0008 +NCF_NO_SERVICE = 0x0010 +NCF_NOT_USER_REMOVABLE = 0x0020 +NCF_HAS_UI = 0x0080 +NCF_MODEM = 0x0100 + +; Registry types +REG_MULTI_SZ = 0x10000 +REG_EXPAND_SZ = 0x20000 +REG_DWORD = 0x10001 + +; Win9x Compatible Types +REG_BINARY = 17 +REG_SZ = 0 + +; Service install flags +SPSVCINST_TAGTOFRONT = 0x1 SPSVCINST_ASSOCSERVICE = 0x2 \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Main.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Main.cpp index 467e9bf..ceb1d98 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Main.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Main.cpp @@ -1,137 +1,137 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1Main.cpp - Author : - Date : - Purpose : This file contains the Entry Point of the Device Driver. - The File also defines various Dispatch Routine pointers - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Common For all the Definitions, - //Declarations and Library Routines - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ - -//Device Dispatch Table : Lists the dispatch routines for the -//major events in the life of Device -const KSDEVICE_DISPATCH SkyWalker1DispatchTable = { - /* Add */ SkyWalker1AddDevice, - /* Start */ SkyWalker1Start, - /* PostStart */ NULL, - /* QueryStop */ SkyWalker1QueryStop, - /* CancelStop */ NULL, - /* Stop */ SkyWalker1Stop, - /* QueryRemove */ NULL, /*QueryRemoveUsbDevice,*/ - /* CancelRemove */ NULL, - /* Remove */ SkyWalker1Remove, - /* QueryCapabilities */ NULL, - /* SurpriseRemoval */ NULL, - /* QueryPower */ NULL, - /* SetPower */ SkyWalker1SetPower -}; - -//Array of Filter Descriptors supported by the Current Driver -// Hold all the filter descriptors in an array -DEFINE_KSFILTER_DESCRIPTOR_TABLE(FilterDescriptors) -{ - &SkyWalker1CaptureFilterDescriptor //Only Capture filter is a Kernel Streaming Filter -}; - -//Device Descriptor : It Describes the Device with all it's dispatch -//functions and Filters -const KSDEVICE_DESCRIPTOR SkyWalker1DeviceDescriptor = -{ - &SkyWalker1DispatchTable, - SIZEOF_ARRAY(FilterDescriptors), //Filter Descriptor Count - FilterDescriptors, //Filter Descriptor Table - KSDEVICE_DESCRIPTOR_VERSION -}; - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - -/***************************************************************************** - Function : DriverEntry - Description : This is a Entry Point function of the Windows Device Driver - It defines various dispatch routine Entry Point for the Driver - IN PARAM : Pointer to Driver Object which is called - Pointer to the Registry Entry of the Driver - OUT PARAM : Status of the Driver Entry routine - STATUS_SUCCESS always - PreCondition : Driver is Unloaded - PostCondtion : Driver is Loaded with various Entry Point defined - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, - IN PUNICODE_STRING pRegistryPath) -{ - NTSTATUS ntEntryStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - SkyWalkerDebugPrint(ENTRY_LEVEL, ("SkyWalker1 Driver Compiled on Date = %s, Time = %s\n",__DATE__,__TIME__)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Debug Level = %u\n",nCurrentDebugLevel)); - - //As this is an AVStream Minidriver it should call the KsInitializeDriver() - ntEntryStatus = KsInitializeDriver( - pDriverObject, - pRegistryPath, - &SkyWalker1DeviceDescriptor); - - PrintFunctionExit(__FUNCTION__,ntEntryStatus); - - return ntEntryStatus; -} - -/***************************************************************************** - Function : SkyWalker1DriverUnload - Description : This is a Exit Point function of the Windows Device Driver - It does not usedful job for the PnP Driver but required to - unload the Driver from the Running System. - IN PARAM : Pointer to Driver Object which is to be Unloaded - OUT PARAM : NONE - PreCondition : Driver is Loaded - PostCondtion : Driver is Unloaded - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -VOID SkyWalker1DriverUnload(PDRIVER_OBJECT pDriverObject) -{ - PrintFunctionEntry(__FUNCTION__); - - //No Processing - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); - -} - - - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1Main.cpp + Author : + Date : + Purpose : This file contains the Entry Point of the Device Driver. + The File also defines various Dispatch Routine pointers + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Common For all the Definitions, + //Declarations and Library Routines + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ + +//Device Dispatch Table : Lists the dispatch routines for the +//major events in the life of Device +const KSDEVICE_DISPATCH SkyWalker1DispatchTable = { + /* Add */ SkyWalker1AddDevice, + /* Start */ SkyWalker1Start, + /* PostStart */ NULL, + /* QueryStop */ SkyWalker1QueryStop, + /* CancelStop */ NULL, + /* Stop */ SkyWalker1Stop, + /* QueryRemove */ NULL, /*QueryRemoveUsbDevice,*/ + /* CancelRemove */ NULL, + /* Remove */ SkyWalker1Remove, + /* QueryCapabilities */ NULL, + /* SurpriseRemoval */ NULL, + /* QueryPower */ NULL, + /* SetPower */ SkyWalker1SetPower +}; + +//Array of Filter Descriptors supported by the Current Driver +// Hold all the filter descriptors in an array +DEFINE_KSFILTER_DESCRIPTOR_TABLE(FilterDescriptors) +{ + &SkyWalker1CaptureFilterDescriptor //Only Capture filter is a Kernel Streaming Filter +}; + +//Device Descriptor : It Describes the Device with all it's dispatch +//functions and Filters +const KSDEVICE_DESCRIPTOR SkyWalker1DeviceDescriptor = +{ + &SkyWalker1DispatchTable, + SIZEOF_ARRAY(FilterDescriptors), //Filter Descriptor Count + FilterDescriptors, //Filter Descriptor Table + KSDEVICE_DESCRIPTOR_VERSION +}; + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + +/***************************************************************************** + Function : DriverEntry + Description : This is a Entry Point function of the Windows Device Driver + It defines various dispatch routine Entry Point for the Driver + IN PARAM : Pointer to Driver Object which is called + Pointer to the Registry Entry of the Driver + OUT PARAM : Status of the Driver Entry routine + STATUS_SUCCESS always + PreCondition : Driver is Unloaded + PostCondtion : Driver is Loaded with various Entry Point defined + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, + IN PUNICODE_STRING pRegistryPath) +{ + NTSTATUS ntEntryStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + SkyWalkerDebugPrint(ENTRY_LEVEL, ("SkyWalker1 Driver Compiled on Date = %s, Time = %s\n",__DATE__,__TIME__)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Debug Level = %u\n",nCurrentDebugLevel)); + + //As this is an AVStream Minidriver it should call the KsInitializeDriver() + ntEntryStatus = KsInitializeDriver( + pDriverObject, + pRegistryPath, + &SkyWalker1DeviceDescriptor); + + PrintFunctionExit(__FUNCTION__,ntEntryStatus); + + return ntEntryStatus; +} + +/***************************************************************************** + Function : SkyWalker1DriverUnload + Description : This is a Exit Point function of the Windows Device Driver + It does not usedful job for the PnP Driver but required to + unload the Driver from the Running System. + IN PARAM : Pointer to Driver Object which is to be Unloaded + OUT PARAM : NONE + PreCondition : Driver is Loaded + PostCondtion : Driver is Unloaded + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +VOID SkyWalker1DriverUnload(PDRIVER_OBJECT pDriverObject) +{ + PrintFunctionEntry(__FUNCTION__); + + //No Processing + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); + +} + + + diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1PnP.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1PnP.cpp index dd82709..6a0778e 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1PnP.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1PnP.cpp @@ -1,336 +1,336 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1PnP.cpp - Author : - Date : - Purpose : PnP IRP Message Handler - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ -#include "SkyWalker1Main.h" //Common For all the Definitions, - //Declarations and Library Routines -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -void PrintKSDeviceObject(IN PKSDEVICE pKSDeviceObject); -/* End of Function prototype definitions */ - -/***************************************************************************** - Function : SkyWalker1AddDevice - Description : This Function is called by the PnP Manager for each Device - managed by the Driver.It is called during the System Initialization - and any time a new Device is enumerated while the System is running. - IN PARAM : Pointer to the Enumerated Physical Device - KSDEVICE is a WDM Functional Device which is managed by the AVStream - OUT PARAM : Status of the Device Addition - STATUS_SUCCESS when the Device added to the System - Reason for Failure incase of Error - PreCondition : Driver is Loaded without Functional/ Filter Device Objects - PostCondtion : Functional Device Object [FDO] or Filter Device Object [FiDO] are created - Logic : NONE - Assumption : NONE - Note : AddDevice is Must for the PnP Drivers - This routine runs at PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS SkyWalker1AddDevice(IN PKSDEVICE pKSDeviceObject) -{ - NTSTATUS ntAddDeviceStatus = STATUS_SUCCESS; - PKSFILTERFACTORY pFilterFactory = NULL; - - PrintFunctionEntry(__FUNCTION__); - PrintKSDeviceObject(pKSDeviceObject); - - if(!IS_VALID(pKSDeviceObject)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid KS Device Object Received\n")); - ntAddDeviceStatus = STATUS_UNSUCCESSFUL; - goto FinishAddDevice; - } - - //Allcate Memory for the SkyWalker1 Device - CSkyWalker1Device * pDevice = new(NonPagedPool,TUNER_MEM_TAG)CSkyWalker1Device; - if(!IS_VALID(pDevice)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Can not allocate Memory for the SkyWalker1 Device\n")); - ntAddDeviceStatus = STATUS_INSUFFICIENT_RESOURCES; - goto FinishAddDevice; - } - - ntAddDeviceStatus = pDevice->Create(pKSDeviceObject); - - PrintKSDeviceObject(pKSDeviceObject); - -FinishAddDevice: - - PrintFunctionExit(__FUNCTION__,ntAddDeviceStatus); - - return ntAddDeviceStatus; -} -/***************************************************************************** - Function : SkyWalker1Remove - Description : This function is called when the IRP_MN_REMOVE_DEVICE is sent - by the PnP Manager during Device Removal - IN PARAM : Pointer to the Enumerated Physical Device - KSDEVICE is a WDM Functional Device which is managed by the AVStream - Remove Device Io Request Packet - OUT PARAM : NONE - PreCondition : Started Device - PostCondtion : Device Removed - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -VOID SkyWalker1Remove( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - NTSTATUS ntDeviceRemoveStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = NULL; - PrintFunctionEntry(__FUNCTION__); - - //Get the SkyWalker1 Device Object from the Context Info. - pDevice = reinterpret_cast(pKSDeviceObject->Context); - - if(!IS_VALID(pDevice)) - { - //Unexpected Remove for the Device - SkyWalkerDebugPrint(ENTRY_LEVEL,("No Connection found with SkyWalker Device\n")); - ntDeviceRemoveStatus = STATUS_UNSUCCESSFUL; - goto ExitRemoveDevice; - } - - ntDeviceRemoveStatus = pDevice->Close(pKSDeviceObject,pIoRequestPacket); - - //Deallocate the SkyWalker1 Device Object Memory - delete pDevice; - //Remove Reference of the SkyWalker1 Device from the KS Object - pKSDeviceObject->Context = NULL; - -ExitRemoveDevice: - - PrintFunctionExit(__FUNCTION__,ntDeviceRemoveStatus); - -} - -/***************************************************************************** - Function : SkyWalker1Start - Description : This function is called when the IRP_MN_START_DEVICE is sent - by the PnP Manager after Allocating Resources to the Device. - IRP_MN_START_DEVICE is called once for each device created from - the Driver using the IoCreateDevice() call. - When BDA Device Starts operating Pnp Dispatches the IRP_MN_START_DEVICE to the ks.sys - AvStream class Driver inturn calls the start routine of the BDA minidriver - associated with the BDA Device. This Start Routine retrives information about the - device from the registry, sets information about the Device and then calls the - BdaCreateFilterFactory() support function to - 1) Create Filter Factory from the Initial Filter Descriptor (KSFILTER_DESCRIPTOR) - for the Device.The Initial Filter Descriptor references Dispatch and Automation - tables for the Filter and Input Pins - 2) Associate Filter Factory with the BDA_FILTER_TEMPLATE structure.This structure - references template filter descriptor for the Device and the list of possible - pairs of the input and output pins.The Descriptor and list inturn reference. - a) Static Template Structure that can be used by Network Provider to determine - BDA Driver topology - b) Static Template Structure that can be used by Network Provider to manipulate - BDA Filter - c) Nodes and Pins for a BDA Filter along with possible ways to connect the Filter - d) Routines that a Netwrok provider can use to Create and Close a Filter instance - 3) Register the Static Template structures that are specified by BDA_FILTER_TEMPLATE - with the BDA support library so that the library can provide default handling - for a BDA MiniDriver's Properties and methods. - IN PARAM : Reference to Device to be Started - IoRequest Packet - OUT PARAM : Status of the Tuner Start - STATUS_SUCCESS in case of successful execution - Failure Code in other cases - PreCondition : Stopped Device or Device Enumerated for the First Time - PostCondtion : Device Initialized with the Newly allocated Resources, - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS SkyWalker1Start(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket, - IN PCM_RESOURCE_LIST pResourceList, - IN PCM_RESOURCE_LIST pResourceListTranslated) -{ - - NTSTATUS ntStartStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = NULL; - PrintFunctionEntry(__FUNCTION__); - - //Get the SkyWalker1 Device Object from the Context Info. - pDevice = reinterpret_cast(pKSDeviceObject->Context); - - if(!IS_VALID(pDevice)) - { - //No Device Found - SkyWalkerDebugPrint(ENTRY_LEVEL,("No Connection with SkyWalker Device\n")); - ntStartStatus = STATUS_UNSUCCESSFUL; - goto ExitStartDevice; - } - - //Call the Start device function of the SkyWalker1 Device class - ntStartStatus = pDevice->Start( pKSDeviceObject, - pIoRequestPacket, - pResourceList, - pResourceListTranslated); - -ExitStartDevice: - - PrintFunctionExit(__FUNCTION__,ntStartStatus); - return STATUS_SUCCESS; -} - -/***************************************************************************** - Function : SkyWalker1Stop - Description : This function is called when the IRP_MN_STOP_DEVICE is sent - by the PnP Manager during Device Removal - IN PARAM : Reference to Device to be Removed - Stop Device Io Request Packet - OUT PARAM : NONE - PreCondition : Started Device - PostCondtion : Device Stopped - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -VOID SkyWalker1Stop(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - - NTSTATUS ntStopStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = NULL; - PrintFunctionEntry(__FUNCTION__); - - //Get the SkyWalker1 Device Object from the Context Info. - pDevice = reinterpret_cast(pKSDeviceObject->Context); - - if(!IS_VALID(pDevice)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("No Connection with SkyWalker Device\n")); - goto ExitStopDevice; - } - - //Call the Stop device function of the SkyWalker1 Device class - ntStopStatus = pDevice->Stop( pKSDeviceObject, - pIoRequestPacket - ); -ExitStopDevice: - PrintFunctionExit(__FUNCTION__,ntStopStatus); -} - -/***************************************************************************** - Function : SkyWalker1QueryStop - Description : This function is called when the IRP_MN_QUERY_STOP_DEVICE is sent - by the PnP Manager during Device Stop - IN PARAM : Pointer to the Enumerated Physical Device - KSDEVICE is a WDM Functional Device which is managed by the AVStream - Remove Device Io Request Packet - OUT PARAM : NONE - PreCondition : Started Device - PostCondtion : Query for stopping device is returned - Logic : NONE - Assumption : NONE - Note : All the Devices created by the Driver are connected with Each other - with the NextDevice Member of the Device Object - Revision History: - *****************************************************************************/ -NTSTATUS SkyWalker1QueryStop( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - PrintFunctionEntry(__FUNCTION__); - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); - return STATUS_SUCCESS; - -} - -/***************************************************************************** - Function : SkyWalker1SetPower - Description : This function is called when the IRP_MJ_POWER is sent - by the PnP Manager during Power Management - IN PARAM : Pointer to the Enumerated Physical Device - KSDEVICE is a WDM Functional Device which is managed by the AVStream - Power Device Io Request Packet - OUT PARAM : NONE - PreCondition : Started Device - PostCondtion : Query for stopping device is returned - Logic : NONE - Assumption : NONE - Note : All the Devices created by the Driver are connected with Each other - with the NextDevice Member of the Device Object - Revision History: - *****************************************************************************/ -VOID SkyWalker1SetPower -( - IN PKSDEVICE pKSDeviceObject, //Pointer to the device object - //provided by the system. - IN PIRP pIoRequestPacket,//Pointer to the IRP related to this request. - IN DEVICE_POWER_STATE To, //Requested power state. - IN DEVICE_POWER_STATE From //Current power state. -) -{ - CSkyWalker1Device * pDevice = NULL; - - PrintFunctionEntry(__FUNCTION__); - //Get the SkyWalker1 Device Object from the Context Info. - pDevice = reinterpret_cast(pKSDeviceObject->Context); - - if(!IS_VALID(pDevice)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("No Connection with SkyWalker Device\n")); - goto ExitSetPower; - } - - //Call the Set Power device function of the SkyWalker1 Device class - pDevice->SetPower( pKSDeviceObject, - pIoRequestPacket, - To, - From - ); -ExitSetPower: - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); - -} - - - -void PrintKSDeviceObject(IN PKSDEVICE pKSDeviceObject) -{ - SkyWalkerDebugPrint(ENTRY_LEVEL, (__FUNCTION__"\n")); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->pDeviceDescriptor = 0x%p \n",pKSDeviceObject->Descriptor)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->pDeviceDescriptor->Dispatch = 0x%p \n",pKSDeviceObject->Descriptor->Dispatch)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->pDeviceDescriptor->FilterDescriptorsCount = %lu \n",pKSDeviceObject->Descriptor->FilterDescriptorsCount)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->pDeviceDescriptor->FilterDescriptors = 0x%p \n",pKSDeviceObject->Descriptor->FilterDescriptors)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->pDeviceDescriptor->Version = %lu \n",pKSDeviceObject->Descriptor->Version)); - - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->Bag = 0x%p\n",pKSDeviceObject->Bag)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->Context = 0x%p\n",pKSDeviceObject->Context)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->FunctionalDeviceObject = 0x%p\n",pKSDeviceObject->FunctionalDeviceObject)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->PhysicalDeviceObject = 0x%p\n",pKSDeviceObject->PhysicalDeviceObject)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->NextDeviceObject = 0x%p\n",pKSDeviceObject->NextDeviceObject)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->Started = %d\n",pKSDeviceObject->Started)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->SystemPowerState = %d\n",pKSDeviceObject->SystemPowerState)); - SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->DevicePowerState = %d\n",pKSDeviceObject->DevicePowerState)); +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1PnP.cpp + Author : + Date : + Purpose : PnP IRP Message Handler + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ +#include "SkyWalker1Main.h" //Common For all the Definitions, + //Declarations and Library Routines +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +void PrintKSDeviceObject(IN PKSDEVICE pKSDeviceObject); +/* End of Function prototype definitions */ + +/***************************************************************************** + Function : SkyWalker1AddDevice + Description : This Function is called by the PnP Manager for each Device + managed by the Driver.It is called during the System Initialization + and any time a new Device is enumerated while the System is running. + IN PARAM : Pointer to the Enumerated Physical Device + KSDEVICE is a WDM Functional Device which is managed by the AVStream + OUT PARAM : Status of the Device Addition + STATUS_SUCCESS when the Device added to the System + Reason for Failure incase of Error + PreCondition : Driver is Loaded without Functional/ Filter Device Objects + PostCondtion : Functional Device Object [FDO] or Filter Device Object [FiDO] are created + Logic : NONE + Assumption : NONE + Note : AddDevice is Must for the PnP Drivers + This routine runs at PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS SkyWalker1AddDevice(IN PKSDEVICE pKSDeviceObject) +{ + NTSTATUS ntAddDeviceStatus = STATUS_SUCCESS; + PKSFILTERFACTORY pFilterFactory = NULL; + + PrintFunctionEntry(__FUNCTION__); + PrintKSDeviceObject(pKSDeviceObject); + + if(!IS_VALID(pKSDeviceObject)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid KS Device Object Received\n")); + ntAddDeviceStatus = STATUS_UNSUCCESSFUL; + goto FinishAddDevice; + } + + //Allcate Memory for the SkyWalker1 Device + CSkyWalker1Device * pDevice = new(NonPagedPool,TUNER_MEM_TAG)CSkyWalker1Device; + if(!IS_VALID(pDevice)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Can not allocate Memory for the SkyWalker1 Device\n")); + ntAddDeviceStatus = STATUS_INSUFFICIENT_RESOURCES; + goto FinishAddDevice; + } + + ntAddDeviceStatus = pDevice->Create(pKSDeviceObject); + + PrintKSDeviceObject(pKSDeviceObject); + +FinishAddDevice: + + PrintFunctionExit(__FUNCTION__,ntAddDeviceStatus); + + return ntAddDeviceStatus; +} +/***************************************************************************** + Function : SkyWalker1Remove + Description : This function is called when the IRP_MN_REMOVE_DEVICE is sent + by the PnP Manager during Device Removal + IN PARAM : Pointer to the Enumerated Physical Device + KSDEVICE is a WDM Functional Device which is managed by the AVStream + Remove Device Io Request Packet + OUT PARAM : NONE + PreCondition : Started Device + PostCondtion : Device Removed + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +VOID SkyWalker1Remove( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + NTSTATUS ntDeviceRemoveStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = NULL; + PrintFunctionEntry(__FUNCTION__); + + //Get the SkyWalker1 Device Object from the Context Info. + pDevice = reinterpret_cast(pKSDeviceObject->Context); + + if(!IS_VALID(pDevice)) + { + //Unexpected Remove for the Device + SkyWalkerDebugPrint(ENTRY_LEVEL,("No Connection found with SkyWalker Device\n")); + ntDeviceRemoveStatus = STATUS_UNSUCCESSFUL; + goto ExitRemoveDevice; + } + + ntDeviceRemoveStatus = pDevice->Close(pKSDeviceObject,pIoRequestPacket); + + //Deallocate the SkyWalker1 Device Object Memory + delete pDevice; + //Remove Reference of the SkyWalker1 Device from the KS Object + pKSDeviceObject->Context = NULL; + +ExitRemoveDevice: + + PrintFunctionExit(__FUNCTION__,ntDeviceRemoveStatus); + +} + +/***************************************************************************** + Function : SkyWalker1Start + Description : This function is called when the IRP_MN_START_DEVICE is sent + by the PnP Manager after Allocating Resources to the Device. + IRP_MN_START_DEVICE is called once for each device created from + the Driver using the IoCreateDevice() call. + When BDA Device Starts operating Pnp Dispatches the IRP_MN_START_DEVICE to the ks.sys + AvStream class Driver inturn calls the start routine of the BDA minidriver + associated with the BDA Device. This Start Routine retrives information about the + device from the registry, sets information about the Device and then calls the + BdaCreateFilterFactory() support function to + 1) Create Filter Factory from the Initial Filter Descriptor (KSFILTER_DESCRIPTOR) + for the Device.The Initial Filter Descriptor references Dispatch and Automation + tables for the Filter and Input Pins + 2) Associate Filter Factory with the BDA_FILTER_TEMPLATE structure.This structure + references template filter descriptor for the Device and the list of possible + pairs of the input and output pins.The Descriptor and list inturn reference. + a) Static Template Structure that can be used by Network Provider to determine + BDA Driver topology + b) Static Template Structure that can be used by Network Provider to manipulate + BDA Filter + c) Nodes and Pins for a BDA Filter along with possible ways to connect the Filter + d) Routines that a Netwrok provider can use to Create and Close a Filter instance + 3) Register the Static Template structures that are specified by BDA_FILTER_TEMPLATE + with the BDA support library so that the library can provide default handling + for a BDA MiniDriver's Properties and methods. + IN PARAM : Reference to Device to be Started + IoRequest Packet + OUT PARAM : Status of the Tuner Start + STATUS_SUCCESS in case of successful execution + Failure Code in other cases + PreCondition : Stopped Device or Device Enumerated for the First Time + PostCondtion : Device Initialized with the Newly allocated Resources, + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS SkyWalker1Start(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket, + IN PCM_RESOURCE_LIST pResourceList, + IN PCM_RESOURCE_LIST pResourceListTranslated) +{ + + NTSTATUS ntStartStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = NULL; + PrintFunctionEntry(__FUNCTION__); + + //Get the SkyWalker1 Device Object from the Context Info. + pDevice = reinterpret_cast(pKSDeviceObject->Context); + + if(!IS_VALID(pDevice)) + { + //No Device Found + SkyWalkerDebugPrint(ENTRY_LEVEL,("No Connection with SkyWalker Device\n")); + ntStartStatus = STATUS_UNSUCCESSFUL; + goto ExitStartDevice; + } + + //Call the Start device function of the SkyWalker1 Device class + ntStartStatus = pDevice->Start( pKSDeviceObject, + pIoRequestPacket, + pResourceList, + pResourceListTranslated); + +ExitStartDevice: + + PrintFunctionExit(__FUNCTION__,ntStartStatus); + return STATUS_SUCCESS; +} + +/***************************************************************************** + Function : SkyWalker1Stop + Description : This function is called when the IRP_MN_STOP_DEVICE is sent + by the PnP Manager during Device Removal + IN PARAM : Reference to Device to be Removed + Stop Device Io Request Packet + OUT PARAM : NONE + PreCondition : Started Device + PostCondtion : Device Stopped + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +VOID SkyWalker1Stop(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + + NTSTATUS ntStopStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = NULL; + PrintFunctionEntry(__FUNCTION__); + + //Get the SkyWalker1 Device Object from the Context Info. + pDevice = reinterpret_cast(pKSDeviceObject->Context); + + if(!IS_VALID(pDevice)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("No Connection with SkyWalker Device\n")); + goto ExitStopDevice; + } + + //Call the Stop device function of the SkyWalker1 Device class + ntStopStatus = pDevice->Stop( pKSDeviceObject, + pIoRequestPacket + ); +ExitStopDevice: + PrintFunctionExit(__FUNCTION__,ntStopStatus); +} + +/***************************************************************************** + Function : SkyWalker1QueryStop + Description : This function is called when the IRP_MN_QUERY_STOP_DEVICE is sent + by the PnP Manager during Device Stop + IN PARAM : Pointer to the Enumerated Physical Device + KSDEVICE is a WDM Functional Device which is managed by the AVStream + Remove Device Io Request Packet + OUT PARAM : NONE + PreCondition : Started Device + PostCondtion : Query for stopping device is returned + Logic : NONE + Assumption : NONE + Note : All the Devices created by the Driver are connected with Each other + with the NextDevice Member of the Device Object + Revision History: + *****************************************************************************/ +NTSTATUS SkyWalker1QueryStop( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + PrintFunctionEntry(__FUNCTION__); + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); + return STATUS_SUCCESS; + +} + +/***************************************************************************** + Function : SkyWalker1SetPower + Description : This function is called when the IRP_MJ_POWER is sent + by the PnP Manager during Power Management + IN PARAM : Pointer to the Enumerated Physical Device + KSDEVICE is a WDM Functional Device which is managed by the AVStream + Power Device Io Request Packet + OUT PARAM : NONE + PreCondition : Started Device + PostCondtion : Query for stopping device is returned + Logic : NONE + Assumption : NONE + Note : All the Devices created by the Driver are connected with Each other + with the NextDevice Member of the Device Object + Revision History: + *****************************************************************************/ +VOID SkyWalker1SetPower +( + IN PKSDEVICE pKSDeviceObject, //Pointer to the device object + //provided by the system. + IN PIRP pIoRequestPacket,//Pointer to the IRP related to this request. + IN DEVICE_POWER_STATE To, //Requested power state. + IN DEVICE_POWER_STATE From //Current power state. +) +{ + CSkyWalker1Device * pDevice = NULL; + + PrintFunctionEntry(__FUNCTION__); + //Get the SkyWalker1 Device Object from the Context Info. + pDevice = reinterpret_cast(pKSDeviceObject->Context); + + if(!IS_VALID(pDevice)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("No Connection with SkyWalker Device\n")); + goto ExitSetPower; + } + + //Call the Set Power device function of the SkyWalker1 Device class + pDevice->SetPower( pKSDeviceObject, + pIoRequestPacket, + To, + From + ); +ExitSetPower: + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); + +} + + + +void PrintKSDeviceObject(IN PKSDEVICE pKSDeviceObject) +{ + SkyWalkerDebugPrint(ENTRY_LEVEL, (__FUNCTION__"\n")); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->pDeviceDescriptor = 0x%p \n",pKSDeviceObject->Descriptor)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->pDeviceDescriptor->Dispatch = 0x%p \n",pKSDeviceObject->Descriptor->Dispatch)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->pDeviceDescriptor->FilterDescriptorsCount = %lu \n",pKSDeviceObject->Descriptor->FilterDescriptorsCount)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->pDeviceDescriptor->FilterDescriptors = 0x%p \n",pKSDeviceObject->Descriptor->FilterDescriptors)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->pDeviceDescriptor->Version = %lu \n",pKSDeviceObject->Descriptor->Version)); + + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->Bag = 0x%p\n",pKSDeviceObject->Bag)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->Context = 0x%p\n",pKSDeviceObject->Context)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->FunctionalDeviceObject = 0x%p\n",pKSDeviceObject->FunctionalDeviceObject)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->PhysicalDeviceObject = 0x%p\n",pKSDeviceObject->PhysicalDeviceObject)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->NextDeviceObject = 0x%p\n",pKSDeviceObject->NextDeviceObject)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->Started = %d\n",pKSDeviceObject->Started)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->SystemPowerState = %d\n",pKSDeviceObject->SystemPowerState)); + SkyWalkerDebugPrint(ENTRY_LEVEL, ("pKsDeviceObject->DevicePowerState = %d\n",pKSDeviceObject->DevicePowerState)); } \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TransportPin.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TransportPin.cpp index 41689f9..517f75e 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TransportPin.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TransportPin.cpp @@ -1,414 +1,414 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1TransportPin.cpp - Author : - Date : - Purpose : This file contains header for the Transport pin on the Tuner - filter. - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Common For all the Definitions, - //Declarations and Library Routines - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -VOID PrintBdaTransport(PKS_DATARANGE_BDA_TRANSPORT pBdaTransport); -VOID PrintBdaTransportInfo(PBDA_TRANSPORT_INFO pBdaTransportInfo); -VOID PrintKsDataFormat(PKSDATAFORMAT pKsDataFormat); -PCHAR GetDemodPropertyString(ULONG ulDemodProperty); -PCHAR GetExtendedPropertyString(ULONG ulTunerExtendedProperty); -/* End of Function prototype definitions */ - -/***************************************************************************** - Function : CTransportPin::IntersectDataFormat - Description : Enables connection of the output pin with a downstream filter. - IN PARAM : - OUT PARAM : Status of the IntersectDataFormat - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS CTransportPin::IntersectDataFormat( - IN PVOID pContext, - IN PIRP pIoRequestPacket, - IN PKSP_PIN Pin, - IN PKSDATARANGE pDataRange, - IN PKSDATARANGE pMatchingDataRange, - IN ULONG ulDataBufferSize, - OUT PVOID pData OPTIONAL, - OUT PULONG pulDataSize - ) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if ( ulDataBufferSize < sizeof(KS_DATARANGE_BDA_TRANSPORT) ) - { - *pulDataSize = sizeof( KS_DATARANGE_BDA_TRANSPORT ); - ntStatus = STATUS_BUFFER_OVERFLOW; - goto ExitDataFormat; - } - else if (pDataRange->FormatSize < sizeof (KS_DATARANGE_BDA_TRANSPORT)) - { - ntStatus = STATUS_NO_MATCH; - goto ExitDataFormat; - } - else - { - *pulDataSize = sizeof( KS_DATARANGE_BDA_TRANSPORT ); - RtlCopyMemory( pData, (PVOID)pDataRange, sizeof(KS_DATARANGE_BDA_TRANSPORT)); - ntStatus = STATUS_SUCCESS; - PrintBdaTransport((PKS_DATARANGE_BDA_TRANSPORT)pDataRange); - } - -ExitDataFormat: - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; - -} - -/***************************************************************************** - Function : CTransportPin::SetDigitalDemodProperty - Description : Sets the value of the digital demodulator node properties. - IN PARAM : - OUT PARAM : Status SUCCESS in case Valid Property Set request - STATUS_INVALID_PARAMETER in case of Invalid property set request - PreCondition : None - PostCondtion : Demodulator propery Set in case of successful execution - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS CTransportPin::SetDigitalDemodProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - CTransportPin* pPin; - CTunerFilter* pFilter; - ModulationType NewModulationType; - BinaryConvolutionCodeRate NewFecRate; - ULONG ulNewSymbolRate; - - - PrintFunctionEntry(__FUNCTION__); - - // Call the BDA support library to - // validate that the node type is associated with this pin. - // - ntSetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); - if (NT_SUCCESS( ntSetStatus)) - { - // Obtain a pointer to the pin object. - // - // Because the property dispatch table calls the CTransportPin::SetDigitalDemodProperty() - // method directly, the method must retrieve a pointer to the underlying pin object. - // - pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); - - // Retrieve the filter context from the pin context. - // - pFilter = pPin->GetFilter(); - SkyWalkerDebugPrint(EXTREME_LEVEL,("Set : %s : %lu(%l)",GetDemodPropertyString(pKSProperty->Id),*pulProperty,*((LONG*)(pulProperty)))); - - switch (pKSProperty->Id) - { - case KSPROPERTY_BDA_MODULATION_TYPE: - ntSetStatus = pFilter->SetModulatorType((ModulationType)*pulProperty); - break; - case KSPROPERTY_BDA_INNER_FEC_TYPE: - ntSetStatus = pFilter->SetInnerFecType(*pulProperty); - break; - case KSPROPERTY_BDA_INNER_FEC_RATE: - ntSetStatus = pFilter->SetInnerFecRate((BinaryConvolutionCodeRate)*pulProperty); - break; - case KSPROPERTY_BDA_OUTER_FEC_TYPE: - ntSetStatus = pFilter->SetOuterFecType(*pulProperty); - break; - case KSPROPERTY_BDA_OUTER_FEC_RATE: - ntSetStatus = pFilter->SetOuterFecRate((BinaryConvolutionCodeRate)*pulProperty); - break; - case KSPROPERTY_BDA_SYMBOL_RATE: - ntSetStatus = pFilter->SetSymbolRate(*pulProperty); - break; - case KSPROPERTY_BDA_SPECTRAL_INVERSION: - ntSetStatus = pFilter->SetSpectralInversion((SpectralInversion)*pulProperty); - break; - case KSPROPERTY_BDA_GUARD_INTERVAL: - ntSetStatus = pFilter->SetGuardInterval((GuardInterval)*pulProperty); - break; - case KSPROPERTY_BDA_TRANSMISSION_MODE: - ntSetStatus = pFilter->SetTransmissionMode((TransmissionMode)*pulProperty); - break; - default: - ntSetStatus = STATUS_INVALID_PARAMETER; - break; - } - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - - return ntSetStatus; -} - -/***************************************************************************** - Function : CTransportPin::GetDigitalDemodProperty - Description : Gets the value of the digital demodulator node properties. - IN PARAM : - OUT PARAM : Status SUCCESS in case Valid Property Get request - STATUS_INVALID_PARAMETER in case of Invalid property Get request - PreCondition : None - PostCondtion : Demodulator propery returned in case of successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTransportPin::GetDigitalDemodProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ) -{ - NTSTATUS ntGetStatus = STATUS_SUCCESS; - CTransportPin* pPin; - CTunerFilter* pFilter; - BDATUNER_DEVICE_PARAMETER DemodProperty; - - PrintFunctionEntry(__FUNCTION__); - - // Call the BDA support library to - // validate that the node type is associated with this pin. - // - ntGetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); - if (NT_SUCCESS( ntGetStatus)) - { - // Obtain a pointer to the pin object. - // - // Because the property dispatch table calls the CTransportPin::GetDigitalDemodProperty() - // method directly, the method must retrieve a pointer to the underlying pin object. - // - pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); - - // Retrieve the filter context from the pin context. - // - pFilter = pPin->GetFilter(); - - ntGetStatus = pFilter->GetDemodProperty(&DemodProperty); - - switch (pKSProperty->Id) - { - case KSPROPERTY_BDA_MODULATION_TYPE: - *pulProperty = (ModulationType)DemodProperty.CurrentModulationType; - break; - case KSPROPERTY_BDA_INNER_FEC_TYPE: - *pulProperty = BDA_FEC_VITERBI; - break; - case KSPROPERTY_BDA_INNER_FEC_RATE: - *pulProperty = (BinaryConvolutionCodeRate)DemodProperty.InnerFecRate; - break; - case KSPROPERTY_BDA_OUTER_FEC_TYPE: - *pulProperty = BDA_FEC_VITERBI; - break; - case KSPROPERTY_BDA_OUTER_FEC_RATE: - *pulProperty = (BinaryConvolutionCodeRate)DemodProperty.OuterFecRate; - break; - case KSPROPERTY_BDA_SYMBOL_RATE: - *pulProperty = DemodProperty.ulSymbolRate; - break; - case KSPROPERTY_BDA_SPECTRAL_INVERSION: - *pulProperty = (SpectralInversion) DemodProperty.CurrentSpectralInversion; - break; - case KSPROPERTY_BDA_GUARD_INTERVAL: - *pulProperty = (GuardInterval) DemodProperty.CurrentGuardInterval; - break; - case KSPROPERTY_BDA_TRANSMISSION_MODE: - *pulProperty = (TransmissionMode) DemodProperty.CurrentTransmissionMode; - break; - default: - ntGetStatus = STATUS_INVALID_PARAMETER; - break; - } - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Get : %s : %ul",GetDemodPropertyString(pKSProperty->Id),*pulProperty)); - - PrintFunctionExit(__FUNCTION__,ntGetStatus); - return ntGetStatus; -} - -/***************************************************************************** - Function : CTransportPin::SetExtendedProperty - Description : Sets the Extended Property of the Tuner - IN PARAM : IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - OUT PARAM : Status SUCCESS in case Valid Property request - STATUS_INVALID_PARAMETER in case of Invalid property request - Else error from the lower device - PreCondition : None - PostCondtion : Extended Property Set in case of successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTransportPin::SetExtendedProperty( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - IN PULONG pulProperty - ) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - CTransportPin * pPin = NULL; - CTunerFilter* pFilter = NULL; - - PrintFunctionEntry(__FUNCTION__); - //Call the BDA support library to - //validate that the node type is associated with the pin. - - //The BdaValidateNodeProperty function validates that a node property - //request is associated with a specific pin. - ntSetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); - if (NT_SUCCESS( ntSetStatus)) - { - //Obtain a pointer to the pin object. - - //Because the property dispatch table calls the CTransportPin::SetExtendedProperty() - //method directly, the method must retrieve a pointer to the underlying pin object. - - pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); - - //Retrieve the filter context from the pin context. - pFilter = pPin->GetFilter(); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Set : %s : %lu(%l)", - GetExtendedPropertyString(pKSProperty->Id), - *pulProperty, - *((LONG*)(pulProperty)))); - - //Retrieve the actual filter parameter. - switch (pKSProperty->Id) - { - case KSPROPERTY_BDA_DISEQC: - ntSetStatus = pFilter->SendDiseqcCommand((PDISEQC_COMMAND) pulProperty); - break; - default: - ntSetStatus = STATUS_INVALID_PARAMETER; - break; - } - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -//Debug Functions -VOID PrintBdaTransport(PKS_DATARANGE_BDA_TRANSPORT pBdaTransport) -{ - PrintBdaTransportInfo(&pBdaTransport->BdaTransportInfo); - PrintKsDataFormat(&pBdaTransport->DataRange); - -} - -VOID PrintBdaTransportInfo(PBDA_TRANSPORT_INFO pBdaTransportInfo) -{ - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pBdaTransportInfo->ulcbPhyiscalPacket = %lu Bytes\n", - pBdaTransportInfo->ulcbPhyiscalPacket)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pBdaTransportInfo->ulcbPhyiscalFrame = %lu Bytes\n", - pBdaTransportInfo->ulcbPhyiscalFrame)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pBdaTransportInfo->ulcbPhyiscalFrameAlignment = %lu\n", - pBdaTransportInfo->ulcbPhyiscalFrameAlignment)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pBdaTransportInfo->ulcbPhyiscalPacket = %ll (Normal Active Movie units)\n", - pBdaTransportInfo->ulcbPhyiscalPacket)); - -} - -VOID PrintKsDataFormat(PKSDATAFORMAT pKsDataFormat) -{ - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pKsDataFormat->FormatSize = %lu\n", - pKsDataFormat->FormatSize)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pKsDataFormat->Flags = %lu\n", - pKsDataFormat->Flags)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pKsDataFormat->SampleSize = %lu\n", - pKsDataFormat->SampleSize)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pKsDataFormat->Reserved = %lu\n", - pKsDataFormat->Reserved)); - -} - -PCHAR GetDemodPropertyString(ULONG ulDemodProperty) -{ - switch(ulDemodProperty) - { - case KSPROPERTY_BDA_MODULATION_TYPE: - return "KSPROPERTY_BDA_MODULATION_TYPE"; - - case KSPROPERTY_BDA_INNER_FEC_TYPE: - return "KSPROPERTY_BDA_INNER_FEC_TYPE"; - - case KSPROPERTY_BDA_INNER_FEC_RATE: - return "KSPROPERTY_BDA_INNER_FEC_RATE"; - - case KSPROPERTY_BDA_OUTER_FEC_TYPE: - return "KSPROPERTY_BDA_OUTER_FEC_TYPE"; - - case KSPROPERTY_BDA_OUTER_FEC_RATE: - return "KSPROPERTY_BDA_OUTER_FEC_RATE"; - - case KSPROPERTY_BDA_SYMBOL_RATE: - return "KSPROPERTY_BDA_SYMBOL_RATE"; - - case KSPROPERTY_BDA_SPECTRAL_INVERSION: - return "KSPROPERTY_BDA_SPECTRAL_INVERSION"; - - case KSPROPERTY_BDA_GUARD_INTERVAL: - return "KSPROPERTY_BDA_GUARD_INTERVAL"; - - case KSPROPERTY_BDA_TRANSMISSION_MODE: - return "KSPROPERTY_BDA_TRANSMISSION_MODE"; - - default: - return "KSPROPERTY_BDA_INVALID_PROPERTY"; - } -} - -PCHAR GetExtendedPropertyString(ULONG ulTunerExtendedProperty) -{ - switch(ulTunerExtendedProperty) - { - case KSPROPERTY_BDA_DISEQC: - return "KSPROPERTY_BDA_DISEQC"; - default: - return "KSPROPERTY_BDA_INVALID_PROPERTY"; - } -} +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1TransportPin.cpp + Author : + Date : + Purpose : This file contains header for the Transport pin on the Tuner + filter. + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Common For all the Definitions, + //Declarations and Library Routines + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +VOID PrintBdaTransport(PKS_DATARANGE_BDA_TRANSPORT pBdaTransport); +VOID PrintBdaTransportInfo(PBDA_TRANSPORT_INFO pBdaTransportInfo); +VOID PrintKsDataFormat(PKSDATAFORMAT pKsDataFormat); +PCHAR GetDemodPropertyString(ULONG ulDemodProperty); +PCHAR GetExtendedPropertyString(ULONG ulTunerExtendedProperty); +/* End of Function prototype definitions */ + +/***************************************************************************** + Function : CTransportPin::IntersectDataFormat + Description : Enables connection of the output pin with a downstream filter. + IN PARAM : + OUT PARAM : Status of the IntersectDataFormat + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS CTransportPin::IntersectDataFormat( + IN PVOID pContext, + IN PIRP pIoRequestPacket, + IN PKSP_PIN Pin, + IN PKSDATARANGE pDataRange, + IN PKSDATARANGE pMatchingDataRange, + IN ULONG ulDataBufferSize, + OUT PVOID pData OPTIONAL, + OUT PULONG pulDataSize + ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if ( ulDataBufferSize < sizeof(KS_DATARANGE_BDA_TRANSPORT) ) + { + *pulDataSize = sizeof( KS_DATARANGE_BDA_TRANSPORT ); + ntStatus = STATUS_BUFFER_OVERFLOW; + goto ExitDataFormat; + } + else if (pDataRange->FormatSize < sizeof (KS_DATARANGE_BDA_TRANSPORT)) + { + ntStatus = STATUS_NO_MATCH; + goto ExitDataFormat; + } + else + { + *pulDataSize = sizeof( KS_DATARANGE_BDA_TRANSPORT ); + RtlCopyMemory( pData, (PVOID)pDataRange, sizeof(KS_DATARANGE_BDA_TRANSPORT)); + ntStatus = STATUS_SUCCESS; + PrintBdaTransport((PKS_DATARANGE_BDA_TRANSPORT)pDataRange); + } + +ExitDataFormat: + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; + +} + +/***************************************************************************** + Function : CTransportPin::SetDigitalDemodProperty + Description : Sets the value of the digital demodulator node properties. + IN PARAM : + OUT PARAM : Status SUCCESS in case Valid Property Set request + STATUS_INVALID_PARAMETER in case of Invalid property set request + PreCondition : None + PostCondtion : Demodulator propery Set in case of successful execution + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS CTransportPin::SetDigitalDemodProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + CTransportPin* pPin; + CTunerFilter* pFilter; + ModulationType NewModulationType; + BinaryConvolutionCodeRate NewFecRate; + ULONG ulNewSymbolRate; + + + PrintFunctionEntry(__FUNCTION__); + + // Call the BDA support library to + // validate that the node type is associated with this pin. + // + ntSetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); + if (NT_SUCCESS( ntSetStatus)) + { + // Obtain a pointer to the pin object. + // + // Because the property dispatch table calls the CTransportPin::SetDigitalDemodProperty() + // method directly, the method must retrieve a pointer to the underlying pin object. + // + pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); + + // Retrieve the filter context from the pin context. + // + pFilter = pPin->GetFilter(); + SkyWalkerDebugPrint(EXTREME_LEVEL,("Set : %s : %lu(%l)",GetDemodPropertyString(pKSProperty->Id),*pulProperty,*((LONG*)(pulProperty)))); + + switch (pKSProperty->Id) + { + case KSPROPERTY_BDA_MODULATION_TYPE: + ntSetStatus = pFilter->SetModulatorType((ModulationType)*pulProperty); + break; + case KSPROPERTY_BDA_INNER_FEC_TYPE: + ntSetStatus = pFilter->SetInnerFecType(*pulProperty); + break; + case KSPROPERTY_BDA_INNER_FEC_RATE: + ntSetStatus = pFilter->SetInnerFecRate((BinaryConvolutionCodeRate)*pulProperty); + break; + case KSPROPERTY_BDA_OUTER_FEC_TYPE: + ntSetStatus = pFilter->SetOuterFecType(*pulProperty); + break; + case KSPROPERTY_BDA_OUTER_FEC_RATE: + ntSetStatus = pFilter->SetOuterFecRate((BinaryConvolutionCodeRate)*pulProperty); + break; + case KSPROPERTY_BDA_SYMBOL_RATE: + ntSetStatus = pFilter->SetSymbolRate(*pulProperty); + break; + case KSPROPERTY_BDA_SPECTRAL_INVERSION: + ntSetStatus = pFilter->SetSpectralInversion((SpectralInversion)*pulProperty); + break; + case KSPROPERTY_BDA_GUARD_INTERVAL: + ntSetStatus = pFilter->SetGuardInterval((GuardInterval)*pulProperty); + break; + case KSPROPERTY_BDA_TRANSMISSION_MODE: + ntSetStatus = pFilter->SetTransmissionMode((TransmissionMode)*pulProperty); + break; + default: + ntSetStatus = STATUS_INVALID_PARAMETER; + break; + } + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + + return ntSetStatus; +} + +/***************************************************************************** + Function : CTransportPin::GetDigitalDemodProperty + Description : Gets the value of the digital demodulator node properties. + IN PARAM : + OUT PARAM : Status SUCCESS in case Valid Property Get request + STATUS_INVALID_PARAMETER in case of Invalid property Get request + PreCondition : None + PostCondtion : Demodulator propery returned in case of successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTransportPin::GetDigitalDemodProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ) +{ + NTSTATUS ntGetStatus = STATUS_SUCCESS; + CTransportPin* pPin; + CTunerFilter* pFilter; + BDATUNER_DEVICE_PARAMETER DemodProperty; + + PrintFunctionEntry(__FUNCTION__); + + // Call the BDA support library to + // validate that the node type is associated with this pin. + // + ntGetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); + if (NT_SUCCESS( ntGetStatus)) + { + // Obtain a pointer to the pin object. + // + // Because the property dispatch table calls the CTransportPin::GetDigitalDemodProperty() + // method directly, the method must retrieve a pointer to the underlying pin object. + // + pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); + + // Retrieve the filter context from the pin context. + // + pFilter = pPin->GetFilter(); + + ntGetStatus = pFilter->GetDemodProperty(&DemodProperty); + + switch (pKSProperty->Id) + { + case KSPROPERTY_BDA_MODULATION_TYPE: + *pulProperty = (ModulationType)DemodProperty.CurrentModulationType; + break; + case KSPROPERTY_BDA_INNER_FEC_TYPE: + *pulProperty = BDA_FEC_VITERBI; + break; + case KSPROPERTY_BDA_INNER_FEC_RATE: + *pulProperty = (BinaryConvolutionCodeRate)DemodProperty.InnerFecRate; + break; + case KSPROPERTY_BDA_OUTER_FEC_TYPE: + *pulProperty = BDA_FEC_VITERBI; + break; + case KSPROPERTY_BDA_OUTER_FEC_RATE: + *pulProperty = (BinaryConvolutionCodeRate)DemodProperty.OuterFecRate; + break; + case KSPROPERTY_BDA_SYMBOL_RATE: + *pulProperty = DemodProperty.ulSymbolRate; + break; + case KSPROPERTY_BDA_SPECTRAL_INVERSION: + *pulProperty = (SpectralInversion) DemodProperty.CurrentSpectralInversion; + break; + case KSPROPERTY_BDA_GUARD_INTERVAL: + *pulProperty = (GuardInterval) DemodProperty.CurrentGuardInterval; + break; + case KSPROPERTY_BDA_TRANSMISSION_MODE: + *pulProperty = (TransmissionMode) DemodProperty.CurrentTransmissionMode; + break; + default: + ntGetStatus = STATUS_INVALID_PARAMETER; + break; + } + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Get : %s : %ul",GetDemodPropertyString(pKSProperty->Id),*pulProperty)); + + PrintFunctionExit(__FUNCTION__,ntGetStatus); + return ntGetStatus; +} + +/***************************************************************************** + Function : CTransportPin::SetExtendedProperty + Description : Sets the Extended Property of the Tuner + IN PARAM : IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + OUT PARAM : Status SUCCESS in case Valid Property request + STATUS_INVALID_PARAMETER in case of Invalid property request + Else error from the lower device + PreCondition : None + PostCondtion : Extended Property Set in case of successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTransportPin::SetExtendedProperty( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + IN PULONG pulProperty + ) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + CTransportPin * pPin = NULL; + CTunerFilter* pFilter = NULL; + + PrintFunctionEntry(__FUNCTION__); + //Call the BDA support library to + //validate that the node type is associated with the pin. + + //The BdaValidateNodeProperty function validates that a node property + //request is associated with a specific pin. + ntSetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); + if (NT_SUCCESS( ntSetStatus)) + { + //Obtain a pointer to the pin object. + + //Because the property dispatch table calls the CTransportPin::SetExtendedProperty() + //method directly, the method must retrieve a pointer to the underlying pin object. + + pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); + + //Retrieve the filter context from the pin context. + pFilter = pPin->GetFilter(); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Set : %s : %lu(%l)", + GetExtendedPropertyString(pKSProperty->Id), + *pulProperty, + *((LONG*)(pulProperty)))); + + //Retrieve the actual filter parameter. + switch (pKSProperty->Id) + { + case KSPROPERTY_BDA_DISEQC: + ntSetStatus = pFilter->SendDiseqcCommand((PDISEQC_COMMAND) pulProperty); + break; + default: + ntSetStatus = STATUS_INVALID_PARAMETER; + break; + } + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +//Debug Functions +VOID PrintBdaTransport(PKS_DATARANGE_BDA_TRANSPORT pBdaTransport) +{ + PrintBdaTransportInfo(&pBdaTransport->BdaTransportInfo); + PrintKsDataFormat(&pBdaTransport->DataRange); + +} + +VOID PrintBdaTransportInfo(PBDA_TRANSPORT_INFO pBdaTransportInfo) +{ + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pBdaTransportInfo->ulcbPhyiscalPacket = %lu Bytes\n", + pBdaTransportInfo->ulcbPhyiscalPacket)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pBdaTransportInfo->ulcbPhyiscalFrame = %lu Bytes\n", + pBdaTransportInfo->ulcbPhyiscalFrame)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pBdaTransportInfo->ulcbPhyiscalFrameAlignment = %lu\n", + pBdaTransportInfo->ulcbPhyiscalFrameAlignment)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pBdaTransportInfo->ulcbPhyiscalPacket = %ll (Normal Active Movie units)\n", + pBdaTransportInfo->ulcbPhyiscalPacket)); + +} + +VOID PrintKsDataFormat(PKSDATAFORMAT pKsDataFormat) +{ + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pKsDataFormat->FormatSize = %lu\n", + pKsDataFormat->FormatSize)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pKsDataFormat->Flags = %lu\n", + pKsDataFormat->Flags)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pKsDataFormat->SampleSize = %lu\n", + pKsDataFormat->SampleSize)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pKsDataFormat->Reserved = %lu\n", + pKsDataFormat->Reserved)); + +} + +PCHAR GetDemodPropertyString(ULONG ulDemodProperty) +{ + switch(ulDemodProperty) + { + case KSPROPERTY_BDA_MODULATION_TYPE: + return "KSPROPERTY_BDA_MODULATION_TYPE"; + + case KSPROPERTY_BDA_INNER_FEC_TYPE: + return "KSPROPERTY_BDA_INNER_FEC_TYPE"; + + case KSPROPERTY_BDA_INNER_FEC_RATE: + return "KSPROPERTY_BDA_INNER_FEC_RATE"; + + case KSPROPERTY_BDA_OUTER_FEC_TYPE: + return "KSPROPERTY_BDA_OUTER_FEC_TYPE"; + + case KSPROPERTY_BDA_OUTER_FEC_RATE: + return "KSPROPERTY_BDA_OUTER_FEC_RATE"; + + case KSPROPERTY_BDA_SYMBOL_RATE: + return "KSPROPERTY_BDA_SYMBOL_RATE"; + + case KSPROPERTY_BDA_SPECTRAL_INVERSION: + return "KSPROPERTY_BDA_SPECTRAL_INVERSION"; + + case KSPROPERTY_BDA_GUARD_INTERVAL: + return "KSPROPERTY_BDA_GUARD_INTERVAL"; + + case KSPROPERTY_BDA_TRANSMISSION_MODE: + return "KSPROPERTY_BDA_TRANSMISSION_MODE"; + + default: + return "KSPROPERTY_BDA_INVALID_PROPERTY"; + } +} + +PCHAR GetExtendedPropertyString(ULONG ulTunerExtendedProperty) +{ + switch(ulTunerExtendedProperty) + { + case KSPROPERTY_BDA_DISEQC: + return "KSPROPERTY_BDA_DISEQC"; + default: + return "KSPROPERTY_BDA_INVALID_PROPERTY"; + } +} diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerFilter.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerFilter.cpp index e6de863..0959721 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerFilter.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerFilter.cpp @@ -1,1402 +1,1402 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1TunerFilter.cpp - Author : - Date : - Purpose : Tuner Filter Class Definition - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Main Header file -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - -/***************************************************************************** - Function : CTunerFilter - Description : Constructor of the CTunerFilter Class - Creates the filter object,associates it with the device - object, and initializes member variables for it. - IN PARAM : NONE - OUT PARAM : NONE - PreCondition : Filter Object is not created - PostCondtion : Filter Object is created and Initialzed on successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -CTunerFilter::CTunerFilter() -{ - PrintFunctionEntry(__FUNCTION__); - - //Setting the Tuner Parameters Default - m_CurResource.ulCarrierFrequency = TUNER_FREQ_MIN; - m_CurResource.ulFrequencyMultiplier = 1000; - m_CurResource.ulBandWidth = 0; - m_CurResource.Polarity = BDA_POLARISATION_LINEAR_H; - m_CurResource.ulInnerFecType = BDA_FEC_VITERBI; - m_CurResource.InnerFecRate = BDA_BCC_RATE_1_2; - m_CurResource.ulOuterFecType = BDA_FEC_VITERBI; - m_CurResource.OuterFecRate = BDA_BCC_RATE_1_2; - m_CurResource.CurrentModulationType = BDA_MOD_QPSK; - m_CurResource.CurrentTransmissionMode = BDA_XMIT_MODE_2K; - m_CurResource.CurrentGuardInterval = BDA_GUARD_NOT_DEFINED; - m_CurResource.CurrentSpectralInversion = BDA_SPECTRAL_INVERSION_NOT_DEFINED; - m_CurResource.ulSymbolRate = 20000; - m_NewResource = m_CurResource; - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); -} - -/***************************************************************************** - Function : CTunerFilter - Description : Destructor of the CTunerFilter Class - Destroys the filter object - IN PARAM : NONE - OUT PARAM : NONE - PreCondition : Filter Object is created - PostCondtion : Filter Object is Removed and Memory freed - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -CTunerFilter::~CTunerFilter() -{ - PrintFunctionEntry(__FUNCTION__); - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); -} - -/***************************************************************************** - Function : CTunerFilter::Create() - Description : This function is called when a Filter is Created.MiniDriver - uses this function to Initialize the Context and resources - associted with the Filter. - Creates the filter object,associates it with the device object, and - initializes member variables for it. - IN PARAM : Pointer to KSFILTER that just created - Pointer to IRP_MJ_CREATE for Filter - OUT PARAM : Status of the Filter Create routine - STATUS_SUCCESS on Routine success - Else Error code from the attempt to create the Filter - PreCondition : Filter is not created - PostCondtion : Filter is created and Initialzed on successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::Create( IN OUT PKSFILTER pKSFilter, - IN PIRP pIoRequestPacket) -{ - NTSTATUS ntFilterCreationStatus = STATUS_SUCCESS; - PKSDEVICE pKSDeviceObject = NULL; - CSkyWalker1Device * pDevice = NULL; - - PrintFunctionEntry(__FUNCTION__); - - //Create a filter object for the filter instance. - CTunerFilter* pFilter = new(PagedPool,TUNER_MEM_TAG) CTunerFilter; // Tags the allocated memory - if (!IS_VALID(pFilter)) - { - //Exit if the Filter Memory could not be allocated - SkyWalkerDebugPrint(ENTRY_LEVEL,("Low on Memory Resource to Create the Filter\n")); - ntFilterCreationStatus = STATUS_INSUFFICIENT_RESOURCES; - goto ErrorFilterCreate; - } - - //Link the filter context to the passed in pointer to the KSFILTER structure. - pKSFilter->Context = pFilter; - - //Set the Back reference to the Streaming Device - //The KsFilterGetDevice function returns the AVStream device to which Filter belongs. - pKSDeviceObject = KsFilterGetDevice(pKSFilter); - - if( (!(IS_VALID(pKSDeviceObject))) || - (!(IS_VALID(pKSDeviceObject->Context)))) - { - //if the Filter does not belong to any Streaming Device or - //Streaming Device Extension is NULL then the Device is not connected - SkyWalkerDebugPrint(ENTRY_LEVEL,("No Device for the Filter\n")); - ntFilterCreationStatus = STATUS_DEVICE_NOT_CONNECTED; - goto ErrorFilterCreate; - } - // Get the device object from the retrieved pointer to the KSDevice for this filter. - pDevice = reinterpret_cast(pKSDeviceObject->Context); - - // Link the filter context to the device context. - // That is, set the filter's device pointer data member to the obtained device pointer. - pFilter->m_pDevice = pDevice; - - // Initialize member variables. - pFilter->m_KsState = KSSTATE_STOP; - pFilter->m_BdaChangeState = BDA_CHANGES_COMPLETE; - pFilter->m_ulResourceID = 0; - - // Configure the initial resource for DVBS . - pFilter->m_CurResource.ulCarrierFrequency = TUNER_FREQ_MIN; - pFilter->m_CurResource.ulFrequencyMultiplier = 1000; - pFilter->m_CurResource.Polarity = BDA_POLARISATION_LINEAR_H; - pFilter->m_CurResource.ulSymbolRate = 20000; - pFilter->m_fResourceAcquired = FALSE; - - //Initialize the Filter Context i.e. Extension - - //The BdaInitFilter() function initializes the BDA filter context - //associated with a filter instance. - ntFilterCreationStatus = BdaInitFilter( pKSFilter, //Pointer to Filter inwhich Context to be initialize - &TunerFilterTemplate); //Filter Template Description for the BDA Device - if(NT_ERROR(ntFilterCreationStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Failed to Initialize the Printer\n")); - goto ErrorFilterCreate; - } - else - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("Successfully Created the Filter\n")); - ntFilterCreationStatus = STATUS_SUCCESS; - } - -CompleteFilterCreate : - - PrintFunctionExit(__FUNCTION__,ntFilterCreationStatus); - return ntFilterCreationStatus; - -ErrorFilterCreate: - if (IS_VALID(pFilter)) - { - delete pFilter; - } - pKSFilter->Context = NULL; - - goto CompleteFilterCreate; -} - -/***************************************************************************** - Function : CTunerFilter::FilterClose - Description : This function is called when a Filter is Closed.MiniDriver - uses this function to uninitialize the Context and resources - associted with the Filter - Deletes the previously created filter object. - IN PARAM : Pointer to KSFILTER that just closed - Pointer to IRP_MJ_CLOSE for Filter - OUT PARAM : Status of the Filter Close routine - STATUS_SUCCESS Always - PreCondition : Filter is created - PostCondtion : Filter is Closed on successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::FilterClose( IN OUT PKSFILTER pKSFilter, - IN PIRP pIoRequestPacket) -{ - NTSTATUS ntFilterCloseStatus = STATUS_SUCCESS; - PrintFunctionEntry(__FUNCTION__); - - CTunerFilter* pFilter = reinterpret_cast(pKSFilter->Context); - - if(IS_VALID(pFilter)) - { - delete pFilter; - pFilter = NULL; - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Closed the Filter\n")); - PrintFunctionExit(__FUNCTION__,ntFilterCloseStatus); - return ntFilterCloseStatus; -} - -/***************************************************************************** - Function : CTunerFilter::StartChanges - Description : This is called when the BDA Topology Change is requested - by Network Provider - IN PARAM : Pointer to the IRP request - Pointer to PKSMETHOD for Filter - Ignored - OUT PARAM : Status of the BdaStartChanges() routine - STATUS_SUCCESS Always - PreCondition : None - PostCondtion : Puts the filter into change state. All changes to BDA topology - and properties changed after this will be in effect only after - a call to the CTunerFilter::CommitChanges() method. - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::StartChanges( IN PIRP pIoRequestPacket, - IN PKSMETHOD pKSMethod, - OPTIONAL PVOID pvIgnored - ) -{ - NTSTATUS ntFilterChangeStatus = STATUS_SUCCESS; - CTunerFilter * pFilter; - - PrintFunctionEntry(__FUNCTION__); - - // Obtain a "this" pointer to the filter object. - // - // Because the property dispatch table calls the CTunerFilter::StartChanges() method - // directly, the method must retrieve a pointer to the underlying filter object. - // - pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); - - // Call the BDA support library to - // reset any pending BDA topolgoy changes. - ntFilterChangeStatus = BdaStartChanges( pIoRequestPacket); - if(!NT_SUCCESS(ntFilterChangeStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Start Changes Failed\n")); - goto ExitStartChanges; - } - - // Reset any pending resource changes. - pFilter->m_NewResource = pFilter->m_CurResource; - pFilter->m_BdaChangeState = BDA_CHANGES_COMPLETE; - -ExitStartChanges: - PrintFunctionExit(__FUNCTION__,ntFilterChangeStatus); - return ntFilterChangeStatus; -} - -/***************************************************************************** - Function : CTunerFilter::CheckChanges - Description : This is called after completion of the BDA Topology Change - to verify the new topology before committing the change - IN PARAM : Pointer to the IRP request - Pointer to PKSMETHOD for Filter - Ignored - OUT PARAM : Status of the BdaCheckChanges() routine - STATUS_SUCCESS Always - PreCondition : None - PostCondtion : Checks the changes to BDA interfaces that have occured since the - last call to the CTunerFilter::StartChanges() method. Returns the identical - result that the CTunerFilter::CommitChanges() method returns. - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::CheckChanges( IN PIRP pIoRequestPacket, - IN PKSMETHOD pKSMethod, - OPTIONAL PVOID pvIgnored - ) -{ - NTSTATUS ntFilterChangeStatus = STATUS_SUCCESS; - CTunerFilter * pFilter; - - PrintFunctionEntry(__FUNCTION__); - - // Obtain a "this" pointer to the filter object. - // - // Because the property dispatch table calls the CTunerFilter::StartChanges() method - // directly, the method must retrieve a pointer to the underlying filter object. - // - pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); - - // Call the BDA support library to - // verify a new set of BDA topology changes. - ntFilterChangeStatus = BdaCheckChanges(pIoRequestPacket); - if (!NT_SUCCESS(ntFilterChangeStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Check Changes Failed\n")); - goto ExitCheckChanges; - } - - // Validate the new resource list here. - // In this driver the new resource list is always valid. - -ExitCheckChanges: - PrintFunctionExit(__FUNCTION__,ntFilterChangeStatus); - return ntFilterChangeStatus; - -} -/***************************************************************************** - Function : CTunerFilter::CommitChanges - Description : This is called after validation of the New Topology to - commit the changes - IN PARAM : Pointer to the IRP request - Pointer to PKSMETHOD for Filter - Ignored - OUT PARAM : Status of the Commit Changes - STATUS_SUCCESS Always - PreCondition : None - PostCondtion : Checks and commits the changes to BDA interfaces that have - occured since the last call to the CTunerFilter::StartChanges() - method. - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::CommitChanges( IN PIRP pIoRequestPacket, - IN PKSMETHOD pKSMethod, - OPTIONAL PVOID pvIgnored - ) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - CTunerFilter * pFilter; - - PrintFunctionEntry(__FUNCTION__); - - // Obtain a "this" pointer to the filter object. - // - // Because the property dispatch table calls the CTunerFilter::CommitChanges() method - // directly, the method must retrieve a pointer to the underlying filter object. - // - pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); - - // - // Validate the new resource list here. - // In this driver the new resource list is always valid. - // - - // Mark the changes as having been made. - // - pFilter->m_CurResource = pFilter->m_NewResource; - pFilter->m_BdaChangeState = BDA_CHANGES_COMPLETE; - - if (pFilter->m_KsState != KSSTATE_STOP) - { - // Commit the resources on the underlying device - // - ntStatus = pFilter->AcquireResources(); - } - - // Call the BDA support library to - // commit a new set of BDA topology changes. - // - ntStatus = BdaCommitChanges( pIoRequestPacket); - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; -} - -/***************************************************************************** - Function : CTunerFilter::GetChangeState - Description : This function is called to know the Current state - change of the Topology - IN PARAM : Pointer to the IRP request - Pointer to PKSMETHOD for Filter - Status of the Change State - OUT PARAM : Status of the Change State - PreCondition : None - PostCondtion : Returns the current BDA change state - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::GetChangeState( IN PIRP pIoRequestPacket, - IN PKSMETHOD pKSMethod, - OUT PULONG pulChangeState - ) -{ - NTSTATUS ntFilterChangeStatus = STATUS_SUCCESS; - CTunerFilter * pFilter; - BDA_CHANGE_STATE TopologyChangeState; - - PrintFunctionEntry(__FUNCTION__); - - // pulChangeState needs to be verified because minData is zero - // in the KSMETHOD_ITEM definition in bdamedia.h - if (!IS_VALID(pulChangeState)) - { - pIoRequestPacket->IoStatus.Information = sizeof(ULONG); - ntFilterChangeStatus = STATUS_MORE_ENTRIES; - goto ExitGetChangeState; - } - - // Obtain a "this" pointer to the filter object. - // - // Because the property dispatch table calls the CTunerFilter::GetChangeState() method - // directly, the method must retrieve a pointer to the underlying filter object. - // - pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); - - // Call the BDA support library to - // verify for any pending BDA topology changes. - ntFilterChangeStatus = BdaGetChangeState( pIoRequestPacket, &TopologyChangeState); - - if (NT_SUCCESS(ntFilterChangeStatus)) - { - // Figure out if there are changes pending. - // - if ( (TopologyChangeState == BDA_CHANGES_PENDING) - || (pFilter->m_BdaChangeState == BDA_CHANGES_PENDING) - ) - { - *pulChangeState = BDA_CHANGES_PENDING; - } - else - { - *pulChangeState = BDA_CHANGES_COMPLETE; - } - } - -ExitGetChangeState: - - PrintFunctionExit(__FUNCTION__,ntFilterChangeStatus); - return ntFilterChangeStatus; -} - -/***************************************************************************** - Function : CTunerFilter::CreateTopology - Description : Keeps track of the topology association between input and output pins - IN PARAM : Pointer to the IRP request - Pointer to PKSMETHOD for Filter - Pounter to the Medium Object - OUT PARAM : Status of the Create topology - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::CreateTopology( IN PIRP pIoRequestPacket, - IN PKSMETHOD pKSMethod, - PVOID pvIgnored - ) -{ - NTSTATUS ntCreateStatus = STATUS_SUCCESS; - CTunerFilter * pFilter; - ULONG ulPinType; - PKSFILTER pKSFilter; - KSP_PIN * pKSPPin = (KSP_PIN *) pKSMethod; - - PrintFunctionEntry(__FUNCTION__); - - // Obtain a "this" pointer to the filter object. - // - // Because the property dispatch table calls the CTunerFilter::CreateTopology() method - // directly, the method must retrieve a pointer to the underlying filter object. - // - pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); - - // - // Configure the hardware to complete its internal connection between - // the input pin and output pin here. - // - - // Call the BDA support library to create the standard topology and - // validate the method, instance count, etc. - // - ntCreateStatus = BdaMethodCreateTopology( pIoRequestPacket, pKSMethod, pvIgnored); - - PrintFunctionExit(__FUNCTION__,ntCreateStatus); - return ntCreateStatus; -} - -/***************************************************************************** - Function : CTunerFilter::GetStatus - Description : Gets the current device status for this filter instance - IN PARAM : Pointer to the GUID of Demodulator - OUT PARAM : Status of the Set Demodulator - STATUS_INVALID_PARAMETER -> In case of NULL GUID - STATUS_NOT_SUPPORTED -> In case of GUID other than QPSK Demod. - STATUS_SUCCESS -> On successful execution - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::GetStatus(OUT PBDATUNER_DEVICE_STATUS pDeviceStatus) -{ - NTSTATUS ntGetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if (m_KsState == KSSTATE_STOP) - { - // If we're in stop state then the device status - // doesn't reflect our resource list. - // - pDeviceStatus->fCarrierPresent = FALSE; - pDeviceStatus->fSignalLocked = FALSE; - ntGetStatus = STATUS_SUCCESS; - } - else - { - ntGetStatus = m_pDevice->GetStatus( pDeviceStatus); - } - - PrintFunctionExit(__FUNCTION__,ntGetStatus); - return ntGetStatus; - -} - - -/***************************************************************************** - Function : CTunerFilter::AcquireResources - Description : Acquires resources for the underlying device. - IN PARAM : None - OUT PARAM : Status of Acquire resources - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::AcquireResources() -{ - NTSTATUS ntAcquireStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if (m_fResourceAcquired) - { - ntAcquireStatus = m_pDevice->Update(&m_CurResource,m_ulResourceID); - } - else - { - - //Commit the resources on the underlying device - ntAcquireStatus = m_pDevice->Acquire(&m_CurResource,&m_ulResourceID); - m_fResourceAcquired = NT_SUCCESS(ntAcquireStatus); - } - - PrintFunctionExit(__FUNCTION__,ntAcquireStatus); - return ntAcquireStatus; -} - -/***************************************************************************** - Function : CTunerFilter::ReleaseResources - Description : Releases resources from the underlying device. - IN PARAM : None - OUT PARAM : Status of Release resources - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::ReleaseResources() -{ - NTSTATUS ntReleaseStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - // Release the resources on the underlying device - if (m_fResourceAcquired) - { - ntReleaseStatus = m_pDevice->Release(m_ulResourceID); - m_ulResourceID = 0; - m_fResourceAcquired = FALSE; - } - - PrintFunctionExit(__FUNCTION__,ntReleaseStatus); - return ntReleaseStatus; -} - -/***************************************************************************** - Function : CTunerFilter::GetTunerProperty - Description : Get the Current Tuner Properties - IN PARAM : Will hold the Tuner Properties - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::GetTunerProperty(OUT PBDATUNER_DEVICE_PARAMETER pTunerParameter) -{ - NTSTATUS ntGetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - *pTunerParameter = m_CurResource; - - PrintFunctionExit(__FUNCTION__,ntGetStatus); - return ntGetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetFrequency - Description : Set the New Frequency for the Tuner - IN PARAM : New frequency - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetFrequency(IN ULONG ulBdaParameter) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (ulBdaParameter == BDA_FREQUENCY_NOT_SET)|| - (ulBdaParameter == BDA_FREQUENCY_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - else if(m_CurResource.ulCarrierFrequency != ulBdaParameter) - { - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Set Frequency = %lu\n", - ulBdaParameter)); - - //Set the Frequency - m_NewResource.ulCarrierFrequency = ulBdaParameter; - m_BdaChangeState = BDA_CHANGES_PENDING; - - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetMultiplier - Description : Set the Frequency Multiplier of the Tuner - IN PARAM : New Frequency Multiplier - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetMultiplier(IN ULONG ulBdaParameter) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (ulBdaParameter == BDA_FREQUENCY_MULTIPLIER_NOT_SET)|| - (ulBdaParameter == BDA_FREQUENCY_MULTIPLIER_NOT_DEFINED)) - { - m_NewResource.ulFrequencyMultiplier = 1000; //Default Multiplier - } - else if(m_CurResource.ulFrequencyMultiplier != ulBdaParameter) - { - //Set the Frequency Multiplier - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Set Frequency Multipler = %lu\n", - ulBdaParameter)); - m_NewResource.ulFrequencyMultiplier = ulBdaParameter; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //Update the device Parameters too - //Send 10 bytes mentioning the Parameters - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetBandwidth - Description : Set the Bandwidth of the Tuner - IN PARAM : New Bandwidth - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetBandwidth(IN ULONG ulBdaParameter) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (ulBdaParameter == BDA_CHAN_BANDWITH_NOT_SET)|| - (ulBdaParameter == BDA_CHAN_BANDWITH_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - else if(m_CurResource.ulBandWidth != ulBdaParameter) - { - //Set the Bandwidth - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Set BandWidth = %lu\n", - ulBdaParameter)); - m_NewResource.ulBandWidth = ulBdaParameter; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //Not Supported by Device - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetPolarity - Description : Set the Polarity of the Tuner - IN PARAM : New Polarity - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetPolarity(IN Polarisation NewPolarity) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (NewPolarity == BDA_POLARISATION_NOT_SET )|| - (NewPolarity == BDA_POLARISATION_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - //Tuner does not have Polarity, it can set LNB voltage which in tern selects LNB Polarity. - //SEC_VOLTAGE_13 is for Verical/Right polarization, SEC_VOLTAGE_18 is for Horizintal/Left - //polarization - else if ((NewPolarity >= BDA_POLARISATION_LINEAR_H) && - (NewPolarity <= BDA_POLARISATION_CIRCULAR_R)) - { - //Change the Polarization only if new setting is requested - if(m_CurResource.Polarity != NewPolarity) - { - //Set the Polarity - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Set Polarity = %lu\n", - NewPolarity)); - - m_NewResource.Polarity = NewPolarity; - - m_BdaChangeState = BDA_CHANGES_PENDING; - - } - } - else - { - ntSetStatus = STATUS_INVALID_PARAMETER; - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetRange - Description : Set the Tuner Range - IN PARAM : Tuner Range - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetRange(IN ULONG ulRange) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (ulRange == BDA_RANGE_NOT_SET )|| - (ulRange == BDA_RANGE_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - else if(m_CurResource.ulRange != ulRange) - { - m_NewResource.ulRange = ulRange; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //not supported by device - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetLowLOFrequency - Description : Set the Low LOF - IN PARAM : Low LOF - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetLowLOFrequency(IN ULONG ulLowLOF) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if(m_CurResource.ulLnbLowLOFrequency != ulLowLOF) - { - m_NewResource.ulLnbLowLOFrequency = ulLowLOF; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //not supported by device - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetHighLOFrequency - Description : Set the High LOF - IN PARAM : High LOF - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetHighLOFrequency(IN ULONG ulHighLOF) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if(m_CurResource.ulLnbHighLOFrequency != ulHighLOF) - { - m_NewResource.ulLnbHighLOFrequency = ulHighLOF; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //not supported by device - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetSwitchFrequency - Description : Set the Switch Frequency - IN PARAM : Switch Frequency - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetSwitchFrequency(IN ULONG ulSwitchFrequency) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if(m_CurResource.ulLnbSwitchFrequency != ulSwitchFrequency) - { - m_NewResource.ulLnbSwitchFrequency = ulSwitchFrequency; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //not supported by device - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetTransponder - Description : Set the Transponder - IN PARAM : Transponder - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetTransponder(IN ULONG ulTransponder) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if(m_CurResource.ulTransponder != ulTransponder) - { - m_NewResource.ulRange = ulTransponder; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //not supported by device - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - - -/***************************************************************************** - Function : CTunerFilter::GetDemodProperties - Description : Get the Current Demodulator Properties - IN PARAM : Will hold the Demodulator Properties - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::GetDemodProperty(OUT PBDATUNER_DEVICE_PARAMETER pDemodParameter) -{ - NTSTATUS ntGetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - *pDemodParameter = m_CurResource; - - PrintFunctionExit(__FUNCTION__,ntGetStatus); - return ntGetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetModulatorType - Description : Set the Modulation Type - IN PARAM : New Modulation Type - OUT PARAM : STATUS_SUCCESS in case of QPSK Modulation type - else STATUS_INVALID_PARAMETER - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetModulatorType(IN ModulationType NewModulationType) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (NewModulationType == BDA_MOD_NOT_SET )|| - (NewModulationType == BDA_MOD_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - else if(NewModulationType == BDA_MOD_QPSK) - { - if(m_CurResource.CurrentModulationType != NewModulationType) - { - //Only supporting QPSK - m_NewResource.CurrentModulationType = NewModulationType; - - m_BdaChangeState = BDA_CHANGES_PENDING; - - //Send the Modulation update request to the Device. - //Send 10 bytes mentioning the Parameters - //Byte 8 of the Tuner Command but fixed thus not needed the below command still - //sending the same - - } - } - else - { - ntSetStatus = STATUS_INVALID_PARAMETER; - } - - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetInnerFecType - Description : Set the Inner Fec Type - IN PARAM : New Inner Fec Type - OUT PARAM : STATUS_SUCCESS in case of BDA_FEC_VITERBI - else STATUS_INVALID_PARAMETER - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetInnerFecType(IN ULONG ulNewInnerFecType) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (ulNewInnerFecType == BDA_FEC_METHOD_NOT_SET )|| - (ulNewInnerFecType == BDA_FEC_METHOD_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - //Only supported FEC VITERBI Type Error Correction - else if(ulNewInnerFecType == BDA_FEC_VITERBI) - { - if(m_CurResource.ulInnerFecType != ulNewInnerFecType) - { - m_NewResource.ulInnerFecType = ulNewInnerFecType; - m_BdaChangeState = BDA_CHANGES_PENDING; - } - } - else - { - ntSetStatus = STATUS_INVALID_PARAMETER; - } - - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetInnerFecRate - Description : Set the Inner Fec Rate - IN PARAM : New Inner Fec Rate - OUT PARAM : STATUS_SUCCESS in case of supported Fec Rate - else STATUS_INVALID_PARAMETER - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetInnerFecRate(IN BinaryConvolutionCodeRate NewFecRate) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (NewFecRate == BDA_BCC_RATE_NOT_SET )|| - (NewFecRate == BDA_BCC_RATE_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - else if((NewFecRate!=BDA_BCC_RATE_1_2)&& - (NewFecRate!=BDA_BCC_RATE_2_3)&& - (NewFecRate!=BDA_BCC_RATE_3_4)&& - (NewFecRate!=BDA_BCC_RATE_5_6)&& - (NewFecRate!=BDA_BCC_RATE_7_8)) - { - ntSetStatus = STATUS_INVALID_PARAMETER; - } - else - { - if(m_CurResource.InnerFecRate != NewFecRate) - { - m_NewResource.InnerFecRate = NewFecRate; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //Update the Device Parameters too - //Byte 9 of the Tuner setting command - - } - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetOuterFecType - Description : Set the Outer Fec Type - IN PARAM : New Outer Fec Type - OUT PARAM : STATUS_SUCCESS in case of BDA_FEC_VITERBI - else STATUS_INVALID_PARAMETER - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetOuterFecType(IN ULONG ulNewOuterFecType) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (ulNewOuterFecType == BDA_FEC_METHOD_NOT_SET )|| - (ulNewOuterFecType == BDA_FEC_METHOD_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - //Only supported FEC VITERBI Type Error Correction - else if(ulNewOuterFecType == BDA_FEC_VITERBI) - { - if(m_CurResource.ulOuterFecType != ulNewOuterFecType) - { - m_NewResource.ulOuterFecType = ulNewOuterFecType; - m_BdaChangeState = BDA_CHANGES_PENDING; - } - } - else - { - ntSetStatus = STATUS_INVALID_PARAMETER; - } - - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetOuterFecRate - Description : Set the Outer Fec Rate - IN PARAM : New Outer Fec Rate - OUT PARAM : STATUS_SUCCESS in case of supported Fec Rate - else STATUS_INVALID_PARAMETER - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetOuterFecRate(IN BinaryConvolutionCodeRate NewFecRate) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (NewFecRate == BDA_BCC_RATE_NOT_SET )|| - (NewFecRate == BDA_BCC_RATE_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - else if((NewFecRate!=BDA_BCC_RATE_1_2)&& - (NewFecRate!=BDA_BCC_RATE_2_3)&& - (NewFecRate!=BDA_BCC_RATE_3_4)&& - (NewFecRate!=BDA_BCC_RATE_5_6)&& - (NewFecRate!=BDA_BCC_RATE_7_8)) - { - ntSetStatus = STATUS_INVALID_PARAMETER; - } - else - { - if(m_CurResource.OuterFecRate != NewFecRate) - { - m_NewResource.OuterFecRate = NewFecRate; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //Not supported by SkyWalker1 thus not udpating the Device Parameter - } - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetSymbolRate - Description : Set the Symbol Rate - IN PARAM : New Symbol Rate - OUT PARAM : STATUS_SUCCESS in case of Valid Symbol Rate - else STATUS_INVALID_PARAMETER - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetSymbolRate(IN ULONG ulNewSymbolRate) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (ulNewSymbolRate >= SYMBOL_RATE_MIN) && - (ulNewSymbolRate <= SYMBOL_RATE_MAX)) - { - - if(m_CurResource.ulSymbolRate != ulNewSymbolRate) - { - m_NewResource.ulSymbolRate = ulNewSymbolRate; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //Update the device Parameters too - //Send 10 bytes mentioning the Parameters - } - } - else - { - ntSetStatus = STATUS_INVALID_PARAMETER; - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetSpectralInversion - Description : Set the Spectral Inversion - IN PARAM : Spectral Inversion - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetSpectralInversion(IN SpectralInversion SpecInv) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (SpecInv == BDA_SPECTRAL_INVERSION_NOT_SET )|| - (SpecInv == BDA_SPECTRAL_INVERSION_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - else if(m_CurResource.CurrentSpectralInversion != SpecInv) - { - m_NewResource.CurrentSpectralInversion = SpecInv; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //not supported by device - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetGuardInterval - Description : Set the Guard Interval - IN PARAM : Guard Interval - OUT PARAM : STATUS_SUCCESS in case of Valid Symbol Rate - else STATUS_INVALID_PARAMETER - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetGuardInterval(IN GuardInterval GuardInt) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (GuardInt == BDA_GUARD_NOT_SET )|| - (GuardInt == BDA_GUARD_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - else if(m_CurResource.CurrentGuardInterval != GuardInt) - { - m_NewResource.CurrentGuardInterval = GuardInt; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //not supported by device - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - -/***************************************************************************** - Function : CTunerFilter::SetTransmissionMode - Description : Set the Transmission Mode - IN PARAM : Transmission Mode - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SetTransmissionMode(IN TransmissionMode TransMode) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - if( (TransMode == BDA_XMIT_MODE_NOT_SET )|| - (TransMode == BDA_XMIT_MODE_NOT_DEFINED)) - { - //Do nothing return SUCCESS - } - if(m_CurResource.CurrentTransmissionMode != TransMode) - { - m_NewResource.CurrentTransmissionMode = TransMode; - m_BdaChangeState = BDA_CHANGES_PENDING; - - //not supported by device - } - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; -} - - -/***************************************************************************** - Function : CTunerFilter::SendDiseqcCommand - Description : Send Diseqc Command to the Tuner - IN PARAM : Diseqc Command to be sent to the Tuner - OUT PARAM : STATUS_SUCCESS always - PreCondition : None - PostCondtion : None - Logic : 1) Validate the Input Parameters - 2) Check which command is been sent by the application - 3) Send the Command to the Tuner Immediately - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerFilter::SendDiseqcCommand(IN PDISEQC_COMMAND pDiseqcCommand) -{ - NTSTATUS ntSetStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - //Validate the Diseqc Parameters here - - //Send the Diseqc Command to the Device - ntSetStatus = m_pDevice->SendDiseqcCommand(pDiseqcCommand,m_ulResourceID); - - PrintFunctionExit(__FUNCTION__,ntSetStatus); - return ntSetStatus; +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1TunerFilter.cpp + Author : + Date : + Purpose : Tuner Filter Class Definition + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Main Header file +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + +/***************************************************************************** + Function : CTunerFilter + Description : Constructor of the CTunerFilter Class + Creates the filter object,associates it with the device + object, and initializes member variables for it. + IN PARAM : NONE + OUT PARAM : NONE + PreCondition : Filter Object is not created + PostCondtion : Filter Object is created and Initialzed on successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +CTunerFilter::CTunerFilter() +{ + PrintFunctionEntry(__FUNCTION__); + + //Setting the Tuner Parameters Default + m_CurResource.ulCarrierFrequency = TUNER_FREQ_MIN; + m_CurResource.ulFrequencyMultiplier = 1000; + m_CurResource.ulBandWidth = 0; + m_CurResource.Polarity = BDA_POLARISATION_LINEAR_H; + m_CurResource.ulInnerFecType = BDA_FEC_VITERBI; + m_CurResource.InnerFecRate = BDA_BCC_RATE_1_2; + m_CurResource.ulOuterFecType = BDA_FEC_VITERBI; + m_CurResource.OuterFecRate = BDA_BCC_RATE_1_2; + m_CurResource.CurrentModulationType = BDA_MOD_QPSK; + m_CurResource.CurrentTransmissionMode = BDA_XMIT_MODE_2K; + m_CurResource.CurrentGuardInterval = BDA_GUARD_NOT_DEFINED; + m_CurResource.CurrentSpectralInversion = BDA_SPECTRAL_INVERSION_NOT_DEFINED; + m_CurResource.ulSymbolRate = 20000; + m_NewResource = m_CurResource; + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); +} + +/***************************************************************************** + Function : CTunerFilter + Description : Destructor of the CTunerFilter Class + Destroys the filter object + IN PARAM : NONE + OUT PARAM : NONE + PreCondition : Filter Object is created + PostCondtion : Filter Object is Removed and Memory freed + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +CTunerFilter::~CTunerFilter() +{ + PrintFunctionEntry(__FUNCTION__); + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); +} + +/***************************************************************************** + Function : CTunerFilter::Create() + Description : This function is called when a Filter is Created.MiniDriver + uses this function to Initialize the Context and resources + associted with the Filter. + Creates the filter object,associates it with the device object, and + initializes member variables for it. + IN PARAM : Pointer to KSFILTER that just created + Pointer to IRP_MJ_CREATE for Filter + OUT PARAM : Status of the Filter Create routine + STATUS_SUCCESS on Routine success + Else Error code from the attempt to create the Filter + PreCondition : Filter is not created + PostCondtion : Filter is created and Initialzed on successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::Create( IN OUT PKSFILTER pKSFilter, + IN PIRP pIoRequestPacket) +{ + NTSTATUS ntFilterCreationStatus = STATUS_SUCCESS; + PKSDEVICE pKSDeviceObject = NULL; + CSkyWalker1Device * pDevice = NULL; + + PrintFunctionEntry(__FUNCTION__); + + //Create a filter object for the filter instance. + CTunerFilter* pFilter = new(PagedPool,TUNER_MEM_TAG) CTunerFilter; // Tags the allocated memory + if (!IS_VALID(pFilter)) + { + //Exit if the Filter Memory could not be allocated + SkyWalkerDebugPrint(ENTRY_LEVEL,("Low on Memory Resource to Create the Filter\n")); + ntFilterCreationStatus = STATUS_INSUFFICIENT_RESOURCES; + goto ErrorFilterCreate; + } + + //Link the filter context to the passed in pointer to the KSFILTER structure. + pKSFilter->Context = pFilter; + + //Set the Back reference to the Streaming Device + //The KsFilterGetDevice function returns the AVStream device to which Filter belongs. + pKSDeviceObject = KsFilterGetDevice(pKSFilter); + + if( (!(IS_VALID(pKSDeviceObject))) || + (!(IS_VALID(pKSDeviceObject->Context)))) + { + //if the Filter does not belong to any Streaming Device or + //Streaming Device Extension is NULL then the Device is not connected + SkyWalkerDebugPrint(ENTRY_LEVEL,("No Device for the Filter\n")); + ntFilterCreationStatus = STATUS_DEVICE_NOT_CONNECTED; + goto ErrorFilterCreate; + } + // Get the device object from the retrieved pointer to the KSDevice for this filter. + pDevice = reinterpret_cast(pKSDeviceObject->Context); + + // Link the filter context to the device context. + // That is, set the filter's device pointer data member to the obtained device pointer. + pFilter->m_pDevice = pDevice; + + // Initialize member variables. + pFilter->m_KsState = KSSTATE_STOP; + pFilter->m_BdaChangeState = BDA_CHANGES_COMPLETE; + pFilter->m_ulResourceID = 0; + + // Configure the initial resource for DVBS . + pFilter->m_CurResource.ulCarrierFrequency = TUNER_FREQ_MIN; + pFilter->m_CurResource.ulFrequencyMultiplier = 1000; + pFilter->m_CurResource.Polarity = BDA_POLARISATION_LINEAR_H; + pFilter->m_CurResource.ulSymbolRate = 20000; + pFilter->m_fResourceAcquired = FALSE; + + //Initialize the Filter Context i.e. Extension + + //The BdaInitFilter() function initializes the BDA filter context + //associated with a filter instance. + ntFilterCreationStatus = BdaInitFilter( pKSFilter, //Pointer to Filter inwhich Context to be initialize + &TunerFilterTemplate); //Filter Template Description for the BDA Device + if(NT_ERROR(ntFilterCreationStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Failed to Initialize the Printer\n")); + goto ErrorFilterCreate; + } + else + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("Successfully Created the Filter\n")); + ntFilterCreationStatus = STATUS_SUCCESS; + } + +CompleteFilterCreate : + + PrintFunctionExit(__FUNCTION__,ntFilterCreationStatus); + return ntFilterCreationStatus; + +ErrorFilterCreate: + if (IS_VALID(pFilter)) + { + delete pFilter; + } + pKSFilter->Context = NULL; + + goto CompleteFilterCreate; +} + +/***************************************************************************** + Function : CTunerFilter::FilterClose + Description : This function is called when a Filter is Closed.MiniDriver + uses this function to uninitialize the Context and resources + associted with the Filter + Deletes the previously created filter object. + IN PARAM : Pointer to KSFILTER that just closed + Pointer to IRP_MJ_CLOSE for Filter + OUT PARAM : Status of the Filter Close routine + STATUS_SUCCESS Always + PreCondition : Filter is created + PostCondtion : Filter is Closed on successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::FilterClose( IN OUT PKSFILTER pKSFilter, + IN PIRP pIoRequestPacket) +{ + NTSTATUS ntFilterCloseStatus = STATUS_SUCCESS; + PrintFunctionEntry(__FUNCTION__); + + CTunerFilter* pFilter = reinterpret_cast(pKSFilter->Context); + + if(IS_VALID(pFilter)) + { + delete pFilter; + pFilter = NULL; + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Closed the Filter\n")); + PrintFunctionExit(__FUNCTION__,ntFilterCloseStatus); + return ntFilterCloseStatus; +} + +/***************************************************************************** + Function : CTunerFilter::StartChanges + Description : This is called when the BDA Topology Change is requested + by Network Provider + IN PARAM : Pointer to the IRP request + Pointer to PKSMETHOD for Filter + Ignored + OUT PARAM : Status of the BdaStartChanges() routine + STATUS_SUCCESS Always + PreCondition : None + PostCondtion : Puts the filter into change state. All changes to BDA topology + and properties changed after this will be in effect only after + a call to the CTunerFilter::CommitChanges() method. + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::StartChanges( IN PIRP pIoRequestPacket, + IN PKSMETHOD pKSMethod, + OPTIONAL PVOID pvIgnored + ) +{ + NTSTATUS ntFilterChangeStatus = STATUS_SUCCESS; + CTunerFilter * pFilter; + + PrintFunctionEntry(__FUNCTION__); + + // Obtain a "this" pointer to the filter object. + // + // Because the property dispatch table calls the CTunerFilter::StartChanges() method + // directly, the method must retrieve a pointer to the underlying filter object. + // + pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); + + // Call the BDA support library to + // reset any pending BDA topolgoy changes. + ntFilterChangeStatus = BdaStartChanges( pIoRequestPacket); + if(!NT_SUCCESS(ntFilterChangeStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Start Changes Failed\n")); + goto ExitStartChanges; + } + + // Reset any pending resource changes. + pFilter->m_NewResource = pFilter->m_CurResource; + pFilter->m_BdaChangeState = BDA_CHANGES_COMPLETE; + +ExitStartChanges: + PrintFunctionExit(__FUNCTION__,ntFilterChangeStatus); + return ntFilterChangeStatus; +} + +/***************************************************************************** + Function : CTunerFilter::CheckChanges + Description : This is called after completion of the BDA Topology Change + to verify the new topology before committing the change + IN PARAM : Pointer to the IRP request + Pointer to PKSMETHOD for Filter + Ignored + OUT PARAM : Status of the BdaCheckChanges() routine + STATUS_SUCCESS Always + PreCondition : None + PostCondtion : Checks the changes to BDA interfaces that have occured since the + last call to the CTunerFilter::StartChanges() method. Returns the identical + result that the CTunerFilter::CommitChanges() method returns. + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::CheckChanges( IN PIRP pIoRequestPacket, + IN PKSMETHOD pKSMethod, + OPTIONAL PVOID pvIgnored + ) +{ + NTSTATUS ntFilterChangeStatus = STATUS_SUCCESS; + CTunerFilter * pFilter; + + PrintFunctionEntry(__FUNCTION__); + + // Obtain a "this" pointer to the filter object. + // + // Because the property dispatch table calls the CTunerFilter::StartChanges() method + // directly, the method must retrieve a pointer to the underlying filter object. + // + pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); + + // Call the BDA support library to + // verify a new set of BDA topology changes. + ntFilterChangeStatus = BdaCheckChanges(pIoRequestPacket); + if (!NT_SUCCESS(ntFilterChangeStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Check Changes Failed\n")); + goto ExitCheckChanges; + } + + // Validate the new resource list here. + // In this driver the new resource list is always valid. + +ExitCheckChanges: + PrintFunctionExit(__FUNCTION__,ntFilterChangeStatus); + return ntFilterChangeStatus; + +} +/***************************************************************************** + Function : CTunerFilter::CommitChanges + Description : This is called after validation of the New Topology to + commit the changes + IN PARAM : Pointer to the IRP request + Pointer to PKSMETHOD for Filter + Ignored + OUT PARAM : Status of the Commit Changes + STATUS_SUCCESS Always + PreCondition : None + PostCondtion : Checks and commits the changes to BDA interfaces that have + occured since the last call to the CTunerFilter::StartChanges() + method. + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::CommitChanges( IN PIRP pIoRequestPacket, + IN PKSMETHOD pKSMethod, + OPTIONAL PVOID pvIgnored + ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + CTunerFilter * pFilter; + + PrintFunctionEntry(__FUNCTION__); + + // Obtain a "this" pointer to the filter object. + // + // Because the property dispatch table calls the CTunerFilter::CommitChanges() method + // directly, the method must retrieve a pointer to the underlying filter object. + // + pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); + + // + // Validate the new resource list here. + // In this driver the new resource list is always valid. + // + + // Mark the changes as having been made. + // + pFilter->m_CurResource = pFilter->m_NewResource; + pFilter->m_BdaChangeState = BDA_CHANGES_COMPLETE; + + if (pFilter->m_KsState != KSSTATE_STOP) + { + // Commit the resources on the underlying device + // + ntStatus = pFilter->AcquireResources(); + } + + // Call the BDA support library to + // commit a new set of BDA topology changes. + // + ntStatus = BdaCommitChanges( pIoRequestPacket); + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; +} + +/***************************************************************************** + Function : CTunerFilter::GetChangeState + Description : This function is called to know the Current state + change of the Topology + IN PARAM : Pointer to the IRP request + Pointer to PKSMETHOD for Filter + Status of the Change State + OUT PARAM : Status of the Change State + PreCondition : None + PostCondtion : Returns the current BDA change state + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::GetChangeState( IN PIRP pIoRequestPacket, + IN PKSMETHOD pKSMethod, + OUT PULONG pulChangeState + ) +{ + NTSTATUS ntFilterChangeStatus = STATUS_SUCCESS; + CTunerFilter * pFilter; + BDA_CHANGE_STATE TopologyChangeState; + + PrintFunctionEntry(__FUNCTION__); + + // pulChangeState needs to be verified because minData is zero + // in the KSMETHOD_ITEM definition in bdamedia.h + if (!IS_VALID(pulChangeState)) + { + pIoRequestPacket->IoStatus.Information = sizeof(ULONG); + ntFilterChangeStatus = STATUS_MORE_ENTRIES; + goto ExitGetChangeState; + } + + // Obtain a "this" pointer to the filter object. + // + // Because the property dispatch table calls the CTunerFilter::GetChangeState() method + // directly, the method must retrieve a pointer to the underlying filter object. + // + pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); + + // Call the BDA support library to + // verify for any pending BDA topology changes. + ntFilterChangeStatus = BdaGetChangeState( pIoRequestPacket, &TopologyChangeState); + + if (NT_SUCCESS(ntFilterChangeStatus)) + { + // Figure out if there are changes pending. + // + if ( (TopologyChangeState == BDA_CHANGES_PENDING) + || (pFilter->m_BdaChangeState == BDA_CHANGES_PENDING) + ) + { + *pulChangeState = BDA_CHANGES_PENDING; + } + else + { + *pulChangeState = BDA_CHANGES_COMPLETE; + } + } + +ExitGetChangeState: + + PrintFunctionExit(__FUNCTION__,ntFilterChangeStatus); + return ntFilterChangeStatus; +} + +/***************************************************************************** + Function : CTunerFilter::CreateTopology + Description : Keeps track of the topology association between input and output pins + IN PARAM : Pointer to the IRP request + Pointer to PKSMETHOD for Filter + Pounter to the Medium Object + OUT PARAM : Status of the Create topology + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::CreateTopology( IN PIRP pIoRequestPacket, + IN PKSMETHOD pKSMethod, + PVOID pvIgnored + ) +{ + NTSTATUS ntCreateStatus = STATUS_SUCCESS; + CTunerFilter * pFilter; + ULONG ulPinType; + PKSFILTER pKSFilter; + KSP_PIN * pKSPPin = (KSP_PIN *) pKSMethod; + + PrintFunctionEntry(__FUNCTION__); + + // Obtain a "this" pointer to the filter object. + // + // Because the property dispatch table calls the CTunerFilter::CreateTopology() method + // directly, the method must retrieve a pointer to the underlying filter object. + // + pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); + + // + // Configure the hardware to complete its internal connection between + // the input pin and output pin here. + // + + // Call the BDA support library to create the standard topology and + // validate the method, instance count, etc. + // + ntCreateStatus = BdaMethodCreateTopology( pIoRequestPacket, pKSMethod, pvIgnored); + + PrintFunctionExit(__FUNCTION__,ntCreateStatus); + return ntCreateStatus; +} + +/***************************************************************************** + Function : CTunerFilter::GetStatus + Description : Gets the current device status for this filter instance + IN PARAM : Pointer to the GUID of Demodulator + OUT PARAM : Status of the Set Demodulator + STATUS_INVALID_PARAMETER -> In case of NULL GUID + STATUS_NOT_SUPPORTED -> In case of GUID other than QPSK Demod. + STATUS_SUCCESS -> On successful execution + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::GetStatus(OUT PBDATUNER_DEVICE_STATUS pDeviceStatus) +{ + NTSTATUS ntGetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if (m_KsState == KSSTATE_STOP) + { + // If we're in stop state then the device status + // doesn't reflect our resource list. + // + pDeviceStatus->fCarrierPresent = FALSE; + pDeviceStatus->fSignalLocked = FALSE; + ntGetStatus = STATUS_SUCCESS; + } + else + { + ntGetStatus = m_pDevice->GetStatus( pDeviceStatus); + } + + PrintFunctionExit(__FUNCTION__,ntGetStatus); + return ntGetStatus; + +} + + +/***************************************************************************** + Function : CTunerFilter::AcquireResources + Description : Acquires resources for the underlying device. + IN PARAM : None + OUT PARAM : Status of Acquire resources + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::AcquireResources() +{ + NTSTATUS ntAcquireStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if (m_fResourceAcquired) + { + ntAcquireStatus = m_pDevice->Update(&m_CurResource,m_ulResourceID); + } + else + { + + //Commit the resources on the underlying device + ntAcquireStatus = m_pDevice->Acquire(&m_CurResource,&m_ulResourceID); + m_fResourceAcquired = NT_SUCCESS(ntAcquireStatus); + } + + PrintFunctionExit(__FUNCTION__,ntAcquireStatus); + return ntAcquireStatus; +} + +/***************************************************************************** + Function : CTunerFilter::ReleaseResources + Description : Releases resources from the underlying device. + IN PARAM : None + OUT PARAM : Status of Release resources + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::ReleaseResources() +{ + NTSTATUS ntReleaseStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + // Release the resources on the underlying device + if (m_fResourceAcquired) + { + ntReleaseStatus = m_pDevice->Release(m_ulResourceID); + m_ulResourceID = 0; + m_fResourceAcquired = FALSE; + } + + PrintFunctionExit(__FUNCTION__,ntReleaseStatus); + return ntReleaseStatus; +} + +/***************************************************************************** + Function : CTunerFilter::GetTunerProperty + Description : Get the Current Tuner Properties + IN PARAM : Will hold the Tuner Properties + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::GetTunerProperty(OUT PBDATUNER_DEVICE_PARAMETER pTunerParameter) +{ + NTSTATUS ntGetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + *pTunerParameter = m_CurResource; + + PrintFunctionExit(__FUNCTION__,ntGetStatus); + return ntGetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetFrequency + Description : Set the New Frequency for the Tuner + IN PARAM : New frequency + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetFrequency(IN ULONG ulBdaParameter) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (ulBdaParameter == BDA_FREQUENCY_NOT_SET)|| + (ulBdaParameter == BDA_FREQUENCY_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + else if(m_CurResource.ulCarrierFrequency != ulBdaParameter) + { + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Set Frequency = %lu\n", + ulBdaParameter)); + + //Set the Frequency + m_NewResource.ulCarrierFrequency = ulBdaParameter; + m_BdaChangeState = BDA_CHANGES_PENDING; + + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetMultiplier + Description : Set the Frequency Multiplier of the Tuner + IN PARAM : New Frequency Multiplier + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetMultiplier(IN ULONG ulBdaParameter) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (ulBdaParameter == BDA_FREQUENCY_MULTIPLIER_NOT_SET)|| + (ulBdaParameter == BDA_FREQUENCY_MULTIPLIER_NOT_DEFINED)) + { + m_NewResource.ulFrequencyMultiplier = 1000; //Default Multiplier + } + else if(m_CurResource.ulFrequencyMultiplier != ulBdaParameter) + { + //Set the Frequency Multiplier + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Set Frequency Multipler = %lu\n", + ulBdaParameter)); + m_NewResource.ulFrequencyMultiplier = ulBdaParameter; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //Update the device Parameters too + //Send 10 bytes mentioning the Parameters + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetBandwidth + Description : Set the Bandwidth of the Tuner + IN PARAM : New Bandwidth + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetBandwidth(IN ULONG ulBdaParameter) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (ulBdaParameter == BDA_CHAN_BANDWITH_NOT_SET)|| + (ulBdaParameter == BDA_CHAN_BANDWITH_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + else if(m_CurResource.ulBandWidth != ulBdaParameter) + { + //Set the Bandwidth + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Set BandWidth = %lu\n", + ulBdaParameter)); + m_NewResource.ulBandWidth = ulBdaParameter; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //Not Supported by Device + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetPolarity + Description : Set the Polarity of the Tuner + IN PARAM : New Polarity + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetPolarity(IN Polarisation NewPolarity) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (NewPolarity == BDA_POLARISATION_NOT_SET )|| + (NewPolarity == BDA_POLARISATION_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + //Tuner does not have Polarity, it can set LNB voltage which in tern selects LNB Polarity. + //SEC_VOLTAGE_13 is for Verical/Right polarization, SEC_VOLTAGE_18 is for Horizintal/Left + //polarization + else if ((NewPolarity >= BDA_POLARISATION_LINEAR_H) && + (NewPolarity <= BDA_POLARISATION_CIRCULAR_R)) + { + //Change the Polarization only if new setting is requested + if(m_CurResource.Polarity != NewPolarity) + { + //Set the Polarity + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Set Polarity = %lu\n", + NewPolarity)); + + m_NewResource.Polarity = NewPolarity; + + m_BdaChangeState = BDA_CHANGES_PENDING; + + } + } + else + { + ntSetStatus = STATUS_INVALID_PARAMETER; + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetRange + Description : Set the Tuner Range + IN PARAM : Tuner Range + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetRange(IN ULONG ulRange) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (ulRange == BDA_RANGE_NOT_SET )|| + (ulRange == BDA_RANGE_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + else if(m_CurResource.ulRange != ulRange) + { + m_NewResource.ulRange = ulRange; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //not supported by device + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetLowLOFrequency + Description : Set the Low LOF + IN PARAM : Low LOF + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetLowLOFrequency(IN ULONG ulLowLOF) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if(m_CurResource.ulLnbLowLOFrequency != ulLowLOF) + { + m_NewResource.ulLnbLowLOFrequency = ulLowLOF; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //not supported by device + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetHighLOFrequency + Description : Set the High LOF + IN PARAM : High LOF + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetHighLOFrequency(IN ULONG ulHighLOF) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if(m_CurResource.ulLnbHighLOFrequency != ulHighLOF) + { + m_NewResource.ulLnbHighLOFrequency = ulHighLOF; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //not supported by device + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetSwitchFrequency + Description : Set the Switch Frequency + IN PARAM : Switch Frequency + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetSwitchFrequency(IN ULONG ulSwitchFrequency) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if(m_CurResource.ulLnbSwitchFrequency != ulSwitchFrequency) + { + m_NewResource.ulLnbSwitchFrequency = ulSwitchFrequency; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //not supported by device + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetTransponder + Description : Set the Transponder + IN PARAM : Transponder + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetTransponder(IN ULONG ulTransponder) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if(m_CurResource.ulTransponder != ulTransponder) + { + m_NewResource.ulRange = ulTransponder; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //not supported by device + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + + +/***************************************************************************** + Function : CTunerFilter::GetDemodProperties + Description : Get the Current Demodulator Properties + IN PARAM : Will hold the Demodulator Properties + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::GetDemodProperty(OUT PBDATUNER_DEVICE_PARAMETER pDemodParameter) +{ + NTSTATUS ntGetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + *pDemodParameter = m_CurResource; + + PrintFunctionExit(__FUNCTION__,ntGetStatus); + return ntGetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetModulatorType + Description : Set the Modulation Type + IN PARAM : New Modulation Type + OUT PARAM : STATUS_SUCCESS in case of QPSK Modulation type + else STATUS_INVALID_PARAMETER + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetModulatorType(IN ModulationType NewModulationType) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (NewModulationType == BDA_MOD_NOT_SET )|| + (NewModulationType == BDA_MOD_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + else if(NewModulationType == BDA_MOD_QPSK) + { + if(m_CurResource.CurrentModulationType != NewModulationType) + { + //Only supporting QPSK + m_NewResource.CurrentModulationType = NewModulationType; + + m_BdaChangeState = BDA_CHANGES_PENDING; + + //Send the Modulation update request to the Device. + //Send 10 bytes mentioning the Parameters + //Byte 8 of the Tuner Command but fixed thus not needed the below command still + //sending the same + + } + } + else + { + ntSetStatus = STATUS_INVALID_PARAMETER; + } + + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetInnerFecType + Description : Set the Inner Fec Type + IN PARAM : New Inner Fec Type + OUT PARAM : STATUS_SUCCESS in case of BDA_FEC_VITERBI + else STATUS_INVALID_PARAMETER + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetInnerFecType(IN ULONG ulNewInnerFecType) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (ulNewInnerFecType == BDA_FEC_METHOD_NOT_SET )|| + (ulNewInnerFecType == BDA_FEC_METHOD_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + //Only supported FEC VITERBI Type Error Correction + else if(ulNewInnerFecType == BDA_FEC_VITERBI) + { + if(m_CurResource.ulInnerFecType != ulNewInnerFecType) + { + m_NewResource.ulInnerFecType = ulNewInnerFecType; + m_BdaChangeState = BDA_CHANGES_PENDING; + } + } + else + { + ntSetStatus = STATUS_INVALID_PARAMETER; + } + + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetInnerFecRate + Description : Set the Inner Fec Rate + IN PARAM : New Inner Fec Rate + OUT PARAM : STATUS_SUCCESS in case of supported Fec Rate + else STATUS_INVALID_PARAMETER + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetInnerFecRate(IN BinaryConvolutionCodeRate NewFecRate) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (NewFecRate == BDA_BCC_RATE_NOT_SET )|| + (NewFecRate == BDA_BCC_RATE_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + else if((NewFecRate!=BDA_BCC_RATE_1_2)&& + (NewFecRate!=BDA_BCC_RATE_2_3)&& + (NewFecRate!=BDA_BCC_RATE_3_4)&& + (NewFecRate!=BDA_BCC_RATE_5_6)&& + (NewFecRate!=BDA_BCC_RATE_7_8)) + { + ntSetStatus = STATUS_INVALID_PARAMETER; + } + else + { + if(m_CurResource.InnerFecRate != NewFecRate) + { + m_NewResource.InnerFecRate = NewFecRate; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //Update the Device Parameters too + //Byte 9 of the Tuner setting command + + } + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetOuterFecType + Description : Set the Outer Fec Type + IN PARAM : New Outer Fec Type + OUT PARAM : STATUS_SUCCESS in case of BDA_FEC_VITERBI + else STATUS_INVALID_PARAMETER + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetOuterFecType(IN ULONG ulNewOuterFecType) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (ulNewOuterFecType == BDA_FEC_METHOD_NOT_SET )|| + (ulNewOuterFecType == BDA_FEC_METHOD_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + //Only supported FEC VITERBI Type Error Correction + else if(ulNewOuterFecType == BDA_FEC_VITERBI) + { + if(m_CurResource.ulOuterFecType != ulNewOuterFecType) + { + m_NewResource.ulOuterFecType = ulNewOuterFecType; + m_BdaChangeState = BDA_CHANGES_PENDING; + } + } + else + { + ntSetStatus = STATUS_INVALID_PARAMETER; + } + + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetOuterFecRate + Description : Set the Outer Fec Rate + IN PARAM : New Outer Fec Rate + OUT PARAM : STATUS_SUCCESS in case of supported Fec Rate + else STATUS_INVALID_PARAMETER + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetOuterFecRate(IN BinaryConvolutionCodeRate NewFecRate) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (NewFecRate == BDA_BCC_RATE_NOT_SET )|| + (NewFecRate == BDA_BCC_RATE_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + else if((NewFecRate!=BDA_BCC_RATE_1_2)&& + (NewFecRate!=BDA_BCC_RATE_2_3)&& + (NewFecRate!=BDA_BCC_RATE_3_4)&& + (NewFecRate!=BDA_BCC_RATE_5_6)&& + (NewFecRate!=BDA_BCC_RATE_7_8)) + { + ntSetStatus = STATUS_INVALID_PARAMETER; + } + else + { + if(m_CurResource.OuterFecRate != NewFecRate) + { + m_NewResource.OuterFecRate = NewFecRate; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //Not supported by SkyWalker1 thus not udpating the Device Parameter + } + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetSymbolRate + Description : Set the Symbol Rate + IN PARAM : New Symbol Rate + OUT PARAM : STATUS_SUCCESS in case of Valid Symbol Rate + else STATUS_INVALID_PARAMETER + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetSymbolRate(IN ULONG ulNewSymbolRate) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (ulNewSymbolRate >= SYMBOL_RATE_MIN) && + (ulNewSymbolRate <= SYMBOL_RATE_MAX)) + { + + if(m_CurResource.ulSymbolRate != ulNewSymbolRate) + { + m_NewResource.ulSymbolRate = ulNewSymbolRate; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //Update the device Parameters too + //Send 10 bytes mentioning the Parameters + } + } + else + { + ntSetStatus = STATUS_INVALID_PARAMETER; + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetSpectralInversion + Description : Set the Spectral Inversion + IN PARAM : Spectral Inversion + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetSpectralInversion(IN SpectralInversion SpecInv) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (SpecInv == BDA_SPECTRAL_INVERSION_NOT_SET )|| + (SpecInv == BDA_SPECTRAL_INVERSION_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + else if(m_CurResource.CurrentSpectralInversion != SpecInv) + { + m_NewResource.CurrentSpectralInversion = SpecInv; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //not supported by device + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetGuardInterval + Description : Set the Guard Interval + IN PARAM : Guard Interval + OUT PARAM : STATUS_SUCCESS in case of Valid Symbol Rate + else STATUS_INVALID_PARAMETER + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetGuardInterval(IN GuardInterval GuardInt) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (GuardInt == BDA_GUARD_NOT_SET )|| + (GuardInt == BDA_GUARD_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + else if(m_CurResource.CurrentGuardInterval != GuardInt) + { + m_NewResource.CurrentGuardInterval = GuardInt; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //not supported by device + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + +/***************************************************************************** + Function : CTunerFilter::SetTransmissionMode + Description : Set the Transmission Mode + IN PARAM : Transmission Mode + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SetTransmissionMode(IN TransmissionMode TransMode) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + if( (TransMode == BDA_XMIT_MODE_NOT_SET )|| + (TransMode == BDA_XMIT_MODE_NOT_DEFINED)) + { + //Do nothing return SUCCESS + } + if(m_CurResource.CurrentTransmissionMode != TransMode) + { + m_NewResource.CurrentTransmissionMode = TransMode; + m_BdaChangeState = BDA_CHANGES_PENDING; + + //not supported by device + } + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; +} + + +/***************************************************************************** + Function : CTunerFilter::SendDiseqcCommand + Description : Send Diseqc Command to the Tuner + IN PARAM : Diseqc Command to be sent to the Tuner + OUT PARAM : STATUS_SUCCESS always + PreCondition : None + PostCondtion : None + Logic : 1) Validate the Input Parameters + 2) Check which command is been sent by the application + 3) Send the Command to the Tuner Immediately + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerFilter::SendDiseqcCommand(IN PDISEQC_COMMAND pDiseqcCommand) +{ + NTSTATUS ntSetStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + //Validate the Diseqc Parameters here + + //Send the Diseqc Command to the Device + ntSetStatus = m_pDevice->SendDiseqcCommand(pDiseqcCommand,m_ulResourceID); + + PrintFunctionExit(__FUNCTION__,ntSetStatus); + return ntSetStatus; } \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerFilterDefinitions.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerFilterDefinitions.cpp index 4c250bf..2201040 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerFilterDefinitions.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerFilterDefinitions.cpp @@ -1,717 +1,717 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1TunerFilterDefinitions.cpp - Author : - Date : - Purpose : Tuner Filter Definition - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Main Header file - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ - -//BDA Tuner Frequency Property Set -DEFINE_KSPROPERTY_TABLE(SkyWalker1TunerFrequencyProperties) -{ - DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_FREQUENCY( - CAntennaPin::GetTunerProperty, - CAntennaPin::SetTunerProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_FREQUENCY_MULTIPLIER( - CAntennaPin::GetTunerProperty, - CAntennaPin::SetTunerProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_POLARITY( - CAntennaPin::GetTunerProperty, - CAntennaPin::SetTunerProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_RANGE( - CAntennaPin::GetTunerProperty, - CAntennaPin::SetTunerProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_BANDWIDTH( - CAntennaPin::GetTunerProperty, - CAntennaPin::SetTunerProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_TRANSPONDER( - CAntennaPin::GetTunerProperty, - CAntennaPin::SetTunerProperty - ), -}; - -//BDA LNB Info Property Set -DEFINE_KSPROPERTY_TABLE(SkyWalker1TunerLnbProperties) -{ - DEFINE_KSPROPERTY_ITEM_BDA_LNB_LOF_HIGH_BAND( - CAntennaPin::GetTunerLnbProperty, - CAntennaPin::SetTunerLnbProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_LNB_LOF_LOW_BAND( - CAntennaPin::GetTunerLnbProperty, - CAntennaPin::SetTunerLnbProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_LNB_SWITCH_FREQUENCY( - CAntennaPin::GetTunerLnbProperty, - CAntennaPin::SetTunerLnbProperty - ), -}; - -//BDA Signal Statistics Properties -// -//Defines the dispatch routines for the Signal Statistics Properties -//on the RF Tuner, Demodulator, and PID Filter Nodes -// -DEFINE_KSPROPERTY_TABLE(SkyWalker1TunerSignalProperties) -{ - - DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_STRENGTH( - CAntennaPin::GetSignalStatus, - NULL - ), - DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_QUALITY( - CAntennaPin::GetSignalStatus, - NULL - ), - DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_PRESENT( - CAntennaPin::GetSignalStatus, - NULL - ), - DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_LOCKED( - CAntennaPin::GetSignalStatus, - NULL - ), -#ifdef LOCK_CODE - DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_LOCK_CAPS( - CAntennaPin::GetSignalStatus - ), - DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_LOCK_TYPE( - CAntennaPin::GetSignalStatus - ), - DEFINE_KSPROPERTY_ITEM_BDA_SAMPLE_TIME( - CTransportPin::GetSignalStatus, - NULL - ), -#endif - - -}; - -DEFINE_KSPROPERTY_SET_TABLE(SkyWalker1TunerAutomationProperties) -{ - DEFINE_KSPROPERTY_SET - ( - &KSPROPSETID_BdaFrequencyFilter, //Property Set defined elsewhere - SIZEOF_ARRAY(SkyWalker1TunerFrequencyProperties), //Number of properties in the array - SkyWalker1TunerFrequencyProperties, //Property set array - 0, //FastIoCount - NULL //FastIoTable - ), - DEFINE_KSPROPERTY_SET - ( - &KSPROPSETID_BdaLNBInfo, //Property Set defined elsewhere - SIZEOF_ARRAY(SkyWalker1TunerLnbProperties), //Number of properties in the array - SkyWalker1TunerLnbProperties, //Property set array - 0, //FastIoCount - NULL //FastIoTable - ), - DEFINE_KSPROPERTY_SET - ( - &KSPROPSETID_BdaSignalStats, //Property Set defined elsewhere - SIZEOF_ARRAY(SkyWalker1TunerSignalProperties), //Number of properties in the array - SkyWalker1TunerSignalProperties, //Property set array - 0, //FastIoCount - NULL //FastIoTable - ), -}; - -//Tuner Automation Table.Used to get and tuner related Methods, -//Events and Properties -DEFINE_KSAUTOMATION_TABLE(SkyWalker1TunerAutomation) -{ - DEFINE_KSAUTOMATION_PROPERTIES(SkyWalker1TunerAutomationProperties), - DEFINE_KSAUTOMATION_METHODS_NULL, - DEFINE_KSAUTOMATION_EVENTS_NULL -}; - -//BDA Signal Statistics Properties for Demodulator Node - -//Defines the dispatch routines for the Signal Statistics Properties -//on the Demodulator Node. -DEFINE_KSPROPERTY_TABLE(SkyWalker1DemodulatorSignalStats) -{ - DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_QUALITY( - CTransportPin::GetSignalStatus, - NULL - ), - DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_LOCKED( - CTransportPin::GetSignalStatus, - NULL - ), - DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_PRESENT( - CTransportPin::GetSignalStatus, - NULL - ), - DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_STRENGTH( - CTransportPin::GetSignalStatus, - NULL - ), -}; - -// -//BDA Digital Demodulator Property Set for Demodulator Node -// -//Defines the dispatch routines for the Digital Demodulator Properties -//on the Demodulator Node. -// -DEFINE_KSPROPERTY_TABLE(SkyWalker1DemodulatorProps) -{ - DEFINE_KSPROPERTY_ITEM_BDA_MODULATION_TYPE( - CTransportPin::GetDigitalDemodProperty, - CTransportPin::SetDigitalDemodProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_INNER_FEC_TYPE( - CTransportPin::GetDigitalDemodProperty, - CTransportPin::SetDigitalDemodProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_INNER_FEC_RATE( - CTransportPin::GetDigitalDemodProperty, - CTransportPin::SetDigitalDemodProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_OUTER_FEC_TYPE( - CTransportPin::GetDigitalDemodProperty, - CTransportPin::SetDigitalDemodProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_OUTER_FEC_RATE( - CTransportPin::GetDigitalDemodProperty, - CTransportPin::SetDigitalDemodProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_SYMBOL_RATE( - CTransportPin::GetDigitalDemodProperty, - CTransportPin::SetDigitalDemodProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_SPECTRAL_INVERSION( - CTransportPin::GetDigitalDemodProperty, - CTransportPin::SetDigitalDemodProperty - ), - DEFINE_KSPROPERTY_ITEM_BDA_TRANSMISSION_MODE( - CTransportPin::GetDigitalDemodProperty, - CTransportPin::SetDigitalDemodProperty - ), -}; - -//BDA Extended Property Set -DEFINE_KSPROPERTY_TABLE(SkyWalker1ExtendedProperties) -{ - DEFINE_KSPROPERTY_ITEM_BDA_DISEQC( - NULL, - CTransportPin::SetExtendedProperty - ), -}; - - -/*****************************************************************************************/ - -//Demodulator Node Property Sets supported -// -//This table defines all property sets supported by the -//Demodulator Node associated with the transport output pin. -// -DEFINE_KSPROPERTY_SET_TABLE(SkyWalker1DemodulatorProperties) -{ - - DEFINE_KSPROPERTY_SET - ( - &KSPROPSETID_BdaDigitalDemodulator, //Set - SIZEOF_ARRAY(SkyWalker1DemodulatorProps), //PropertiesCount - SkyWalker1DemodulatorProps, //PropertyItems - 0, //FastIoCount - NULL //FastIoTable - ), - DEFINE_KSPROPERTY_SET - ( - &KSPROPSETID_BdaExtendedProperty, //Set - SIZEOF_ARRAY(SkyWalker1ExtendedProperties), //PropertiesCount - SkyWalker1ExtendedProperties, //PropertyItems - 0, //FastIoCount - NULL //FastIoTable - ), - DEFINE_KSPROPERTY_SET - ( - &KSPROPSETID_BdaSignalStats, //Set - SIZEOF_ARRAY(SkyWalker1DemodulatorSignalStats), //PropertiesCount - SkyWalker1DemodulatorSignalStats, //PropertyItems - 0, //FastIoCount - NULL //FastIoTable - ), - - // - //Additional property sets for the node can be added here. - // -}; - - -DEFINE_KSAUTOMATION_TABLE(SkyWalker1DemodulatorAutomation) { - DEFINE_KSAUTOMATION_PROPERTIES(SkyWalker1DemodulatorProperties), - DEFINE_KSAUTOMATION_METHODS_NULL, - DEFINE_KSAUTOMATION_EVENTS_NULL -}; - -/*****************************************************************************************/ -//Template Node Descriptors -// -//Define an array that contains all the node types that are available in the template -//topology of the filter. -//These node types must be supported by BDA and -//defined elsewhere (for example, in Bdamedia.h). -// - -const KSNODE_DESCRIPTOR TunerFilterNodeDescriptors[] = -{ - DEFINE_NODE_DESCRIPTOR( - &SkyWalker1TunerAutomation, //Point to KSAUTOMATION_TABLE structure - //for the node's automation table - &KSNODE_BDA_RF_TUNER, //Point to the guid that defines function - //of the node - NULL //Point to the guid that represents the - //name of the topology node - ), - DEFINE_NODE_DESCRIPTOR( - &SkyWalker1DemodulatorAutomation,//Point to KSAUTOMATION_TABLE structure - //for the node's automation table - &KSNODE_BDA_QPSK_DEMODULATOR,//Point to the guid that defines function - //of the node - NULL //Point to the guid that represents the - //name of the topology node - ), -}; - -/*****************************************************************************************/ -//Define BDA Template Topology Connections -// -//Lists the Connections that are possible between pin types and -//node types. This, together with the Template Filter Descriptor, and -//the Pin Pairings, describe how topologies can be created in the filter. -// -// =========== ============ -//AntennaPin ----| RF Node |--Joint--|Demod Node|----TransportPin -// =========== ============ -// -//The RF Node of this filter is controlled by the Antenna input pin. -//RF properties will be set as NODE properties (with NodeType == 0) -//on the filter's Antenna Pin -// -//The Demodulator Node of this filter is controlled by the Transport output pin. -//Demod properties will be set as NODE properties (with NodeType == 1) -//on the filter's Transport Pin - -const KSTOPOLOGY_CONNECTION TunerFilterConnections[]={ - {KSFILTER_NODE, 0, 0, KSNODEPIN_STANDARD_IN}, //Antenna pin -> Tuner pin 0 - {0, KSNODEPIN_STANDARD_OUT, 1, KSNODEPIN_STANDARD_IN}, //Tuner pin 1 -> Demodulator pin 0 - {1, KSNODEPIN_STANDARD_OUT, KSFILTER_NODE, 1}, //Demodulator pin 1 -> Transport pin -}; - -//Lists the template joints between the Antenna Input Pin Type and -//the Transport Output Pin Type. -// -//In this case the RF Node is considered to belong to the antennea input -//pin and the 8VSB Demodulator Node is considered to belong to the -//tranport stream output pin. -// -const ULONG InterNodeJoints[] = -{ - 1 //joint occurs between the two node types (second element in array) - //indicates that 1st node is controlled by input pin and 2nd node by output pin -}; - - -//Array of BDA_PIN_PAIRING structures that are used to determine -//which nodes get duplicated when more than one output pin type is -//connected to a single input pin type or when more that one input pin -//type is connected to a single output pin type. -// -const BDA_PIN_PAIRING TunerFilterPinPairings[] = -{ - //Input pin to Output pin Topology Joints - { - 0, //ulInputPin; 0 element in the TemplatePinDescriptors array. - 1, //ulOutputPin; 1 element in the TemplatePinDescriptors array. - 1, //ulcMaxInputsPerOutput - 1, //ulcMinInputsPerOutput - 1, //ulcMaxOutputsPerInput - 1, //ulcMinOutputsPerInput - SIZEOF_ARRAY(InterNodeJoints), //ulcTopologyJoints - InterNodeJoints //pTopologyJoints; array of joints - } - //If applicable, list topology of joints between other pins. -}; - -/*****************************************************************************************/ - -const KSCOMPONENTID TunerFilterComponentId={ - NULL, - NULL, - NULL, - NULL, - 1, //Version - 0 //Revision -}; -/**********************************************************************************/ - -// -//Dispatch Table for the antenna pin. -// - -const KSPIN_DISPATCH AntennaPinDispatch={ - /* Create */ CAntennaPin::PinCreate, - /* Close */ CAntennaPin::PinClose, - /* Process */ NULL, - /* Reset */ NULL, - /* SetDataFormat */ NULL, - /* SetDeviceState */ CAntennaPin::PinSetDeviceState, - /* Connect */ NULL, - /* Disconnect */ NULL, - /* Allocator */ NULL -}; - -DEFINE_KSAUTOMATION_TABLE(NullAutomation) -{ - DEFINE_KSAUTOMATION_PROPERTIES_NULL, - DEFINE_KSAUTOMATION_METHODS_NULL, - DEFINE_KSAUTOMATION_EVENTS_NULL -}; - -const KS_DATARANGE_BDA_ANTENNA AntennaPinRange = -{ - //insert the KSDATARANGE and KSDATAFORMAT here - { - sizeof( KS_DATARANGE_BDA_ANTENNA), //FormatSize - 0, //Flags - (N/A) - 0, //SampleSize - (N/A) - 0, //Reserved - { STATIC_KSDATAFORMAT_TYPE_BDA_ANTENNA }, //MajorFormat - { STATIC_KSDATAFORMAT_SUBTYPE_NONE }, //SubFormat - { STATIC_KSDATAFORMAT_SPECIFIER_NONE } //Specifier - } -}; - -const PKSDATARANGE AntennaPinRanges[]={ - (PKSDATARANGE)&AntennaPinRange, -}; - -// -//Dispatch Table for the transport Output pin. -// -//Since data on the transport is actually delivered to the -//PCI bridge in hardware, this pin does not process data. -// -//Connection of, and state transitions on, this pin help the -//driver to determine when to allocate hardware resources for -//each node. -// -const KSPIN_DISPATCH TransportPinDispatch = -{ - CTransportPin::PinCreate, //Create - CTransportPin::PinClose, //Close - NULL, //Process - NULL, //Reset - NULL, //SetDataFormat - /*AntennaPinSetDeviceState*/ NULL, //SetDeviceState - NULL, //Connect - NULL, //Disconnect - NULL, //Clock - NULL //Allocator -}; - - -const KSPIN_INTERFACE StreamInterface[]={ - { - STATICGUIDOF(KSINTERFACESETID_Standard), - KSINTERFACE_STANDARD_STREAMING, - 0 - }, -}; - -//Medium GUIDs for the Transport Output Pin. -// -//Pin Medium descriptor containing all medium accepted to be connected to -//the tuner output pin.This insures contection to the correct Capture Filter pin. -// -//{2AEB4A94-FBB7-4FB1-8D74-243B91886EAB} - -const KSPIN_MEDIUM TransportPinMediums[] = -{ - { - GUID_SKYWALKER_TUNER_OUT_MEDIUM, - 0, - 0 - } -}; - -const KS_DATARANGE_BDA_TRANSPORT TransportPinRange = -{ - //insert the KSDATARANGE and KSDATAFORMAT here - { - sizeof( KS_DATARANGE_BDA_TRANSPORT), //FormatSize - 0, //Flags - (N/A) - 0, //SampleSize - (N/A) - 0, //Reserved - { STATIC_KSDATAFORMAT_TYPE_STREAM }, //MajorFormat - { STATIC_KSDATAFORMAT_TYPE_MPEG2_TRANSPORT }, //SubFormat - { STATIC_KSDATAFORMAT_SPECIFIER_BDA_TRANSPORT } //Specifier - }, - //BDA_TRANSPORT_INFO - { - TRANSPORT_PACKET_SIZE, //Bytes in Line - TRANSPORT_PACKET_SIZE * TRANSPORT_PACKET_COUNT, //Frame Size - 0, //ulcbPhysicalFrameAlignment (no requirement) - 0 //AvgTimePerFrame, Time / Sample (not known) - } -}; - -//Format Ranges of Transport Output Pin. -// -static PKSDATAFORMAT TransportPinRanges[] = -{ - (PKSDATAFORMAT) &TransportPinRange, - - //Add more formats here if additional transport formats are supported. - // -}; - -DECLARE_SIMPLE_FRAMING_EX(TransportAllocator, - STATICGUIDOF(KSMEMORY_TYPE_KERNEL_NONPAGED), - KSALLOCATOR_REQUIREMENTF_SYSTEM_MEMORY/*|KSALLOCATOR_REQUIREMENTF_PREFERENCES_ONLY*/, - 8, - 0, - TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE, - TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE); - - -//Template Pin Descriptors - -//This data structure defines the pin types available in the filters -//template topology. These structures will be used to create a -//KDPinFactory for a pin type when BdaCreatePin or BdaMethodCreatePin -//are called. -// -//This structure defines ALL pins the filter is capable of supporting, -//including those pins which may only be created dynamically by a ring -//3 component such as a Network Provider. - -const KSPIN_DESCRIPTOR_EX TunerFilterPinDescriptors[]={ - { //Antenna input pin - &AntennaPinDispatch, //Dispatch Table - &NullAutomation, //Automation Table - { - 0, //Interfaces - NULL, - 0, //Mediums - NULL, - SIZEOF_ARRAY(AntennaPinRanges), - AntennaPinRanges, - KSPIN_DATAFLOW_IN, //Specifies that data flow is into the pin - KSPIN_COMMUNICATION_BOTH, //Specifies that the pin factory instantiates pins - //that are both IRP sinks and IRP sources - NULL, //Category - NULL, //Name - 0 - }, - KSPIN_FLAG_DO_NOT_USE_STANDARD_TRANSPORT| - KSPIN_FLAG_FRAMES_NOT_REQUIRED_FOR_PROCESSING| - KSPIN_FLAG_FIXED_FORMAT, - 1, //Maximum Possible Instances of the Pin - 1, //Mandatory Instances of this for the Filter function - NULL, - CAntennaPin::IntersectDataFormat //Data Interaction Handler - }, - //Tranport Output Pin - { - &TransportPinDispatch, //Point to the dispatch table for the output pin - &NullAutomation, //Point to the automation table for the output pin - { //Specify members of a KSPIN_DESCRIPTOR structure for the output pin - 0, //Interface Count - NULL, //Interfaces - SIZEOF_ARRAY(TransportPinMediums), //Medium Count - TransportPinMediums, //Medium - SIZEOF_ARRAY(TransportPinRanges), //Range Count - TransportPinRanges, //Ranges - KSPIN_DATAFLOW_OUT, //specifies that data flow is out of the pin - KSPIN_COMMUNICATION_BOTH, //specifies that the pin factory instantiates pins - (GUID *) &PINNAME_BDA_TRANSPORT, //Category GUID - (GUID *) &PINNAME_BDA_TRANSPORT, //GUID of the localized Unicode string //name for the pin type - 0 - }, //Specify flags - KSPIN_FLAG_DO_NOT_USE_STANDARD_TRANSPORT | - KSPIN_FLAG_FRAMES_NOT_REQUIRED_FOR_PROCESSING | - KSPIN_FLAG_FIXED_FORMAT, - 1, //Specify the maximum number of possible instances of the output pin - 1, //Specify the number of instances of this pin type that are necessary for proper functioning of this filter - NULL, //Allocator Framing - CTransportPin::IntersectDataFormat //Point to the data intersection handler function - } -}; - -/**********************************************************************************/ - -//BDA Device Topology Property Set - -//The BDA Support Library supplies a default implementation of the -//BDA Device Topology Property Set. If the driver needs to override -//this default implemenation, the definitions for the override properties -//will be defined here. - - -//BDA Device Configuration Method Set - -//The BDA Support Library provides a default implementation of -//the BDA Device Configuration Method Set. In this , the -//driver overrides the CreateTopology method. Note that the -//support libraries CreateTopology method is called before the -//driver's implementation returns. -// -DEFINE_KSMETHOD_TABLE(TunerFilterConfiguration) -{ - DEFINE_KSMETHOD_ITEM_BDA_CREATE_TOPOLOGY( - CTunerFilter::CreateTopology, //Calls BdaMethodCreateTopology - NULL - ) -}; - - -//BDA Change Sync Method Set - -//The Change Sync Method Set is required on BDA filters. Setting a -//node property should not become effective on the underlying device -//until CommitChanges is called. -//The BDA Support Library provides routines that handle committing -//changes to topology. The BDA Support Library routines should be -//called from the driver's implementation before the driver implementation -//returns. - -DEFINE_KSMETHOD_TABLE(TunerFilterChangeSync) -{ - DEFINE_KSMETHOD_ITEM_BDA_START_CHANGES( - CTunerFilter::StartChanges, //Calls BdaStartChanges - NULL - ), - DEFINE_KSMETHOD_ITEM_BDA_CHECK_CHANGES( - CTunerFilter::CheckChanges, //Calls BdaCheckChanges - NULL - ), - DEFINE_KSMETHOD_ITEM_BDA_COMMIT_CHANGES( - CTunerFilter::CommitChanges, //Calls BdaCommitChanges - NULL - ), - DEFINE_KSMETHOD_ITEM_BDA_GET_CHANGE_STATE( - CTunerFilter::GetChangeState, //Calls BdaGetChangeState - NULL - ) -}; - - -//Array of Method sets supported by filter -DEFINE_KSMETHOD_SET_TABLE(TunerFilterMethods) -{ - DEFINE_KSMETHOD_SET - ( - &KSMETHODSETID_BdaChangeSync, //Method set GUID - SIZEOF_ARRAY(TunerFilterChangeSync), //Number of methods - TunerFilterChangeSync, //Array of KSMETHOD_ITEM structures - 0, //FastIoCount - NULL //FastIoTable - ), - - DEFINE_KSMETHOD_SET - ( - &KSMETHODSETID_BdaDeviceConfiguration, //Method set GUID - SIZEOF_ARRAY(TunerFilterConfiguration), //Number of methods - TunerFilterConfiguration, //Array of KSMETHOD_ITEM structures - 0, //FastIoCount - NULL //FastIoTable - ) - -}; - -//Supporting only Filter Methods;Properties and Events are not supported -DEFINE_KSAUTOMATION_TABLE(TunerFilterAutomationTable) -{ - DEFINE_KSAUTOMATION_PROPERTIES_NULL, - DEFINE_KSAUTOMATION_METHODS(TunerFilterMethods), - DEFINE_KSAUTOMATION_EVENTS_NULL -}; -/**********************************************************************************/ -//Dispatch table for the Filter Processing -const KSFILTER_DISPATCH TunerFilterDispatchTable = -{ - /* Create */ CTunerFilter::Create, //Routine called when the Filter is created - /* Close */ CTunerFilter::FilterClose, //Routine called when the Filter is closed - /* Process */ NULL, - /* Reset */ NULL -}; - -/*****************************************************************************************/ - -//Define the Filter Factory Descriptor for the filter -//This structure brings together all of the structures that define -//the tuner filter as it appears when it is first instantiated. -//Note that not all of the template pin and node types may be exposed as -//pin and node factories when the filter is first instanciated. - -//The KSFILTER_DESCRIPTOR structure describes the characteristics of a filter created by a given filter factory. -DEFINE_KSFILTER_DESCRIPTOR(SkyWalker1TunerFilterDescriptor) -{ - &TunerFilterDispatchTable, //Dispatch (Filter Specific Driver) - &TunerFilterAutomationTable, //AutomationTable - KSFILTER_DESCRIPTOR_VERSION, //Version - 0, //Flags - &SKYWALKER_TUNER_FILTER, //ReferenceGuid - DEFINE_KSFILTER_PIN_DESCRIPTORS(TunerFilterPinDescriptors), - //PinDescriptorsCount; must expose at least one pin - //PinDescriptorSize; size of each item - //PinDescriptors; table of pin descriptors - DEFINE_KSFILTER_CATEGORY(KSCATEGORY_BDA_NETWORK_TUNER), - //CategoriesCount; number of categories in the table - //Categories; table of categories - DEFINE_KSFILTER_NODE_DESCRIPTORS(TunerFilterNodeDescriptors), - //NodeDescriptorsCount; - //NodeDescriptorSize; - //NodeDescriptors; - DEFINE_KSFILTER_CONNECTIONS(TunerFilterConnections), - //Automatically fills in the connections table for a filter which defines no explicit connections - //ConnectionsCount; number of connections in the table - //Connections; table of connections - &TunerFilterComponentId //ComponentId; -}; - -//BDA_FILTER_TEMPLATE structure describes the template topology for BDA Driver -const BDA_FILTER_TEMPLATE TunerFilterTemplate = -{ - &SkyWalker1TunerFilterDescriptor,//Pointer to KS_FILTER_DESCRIPTOR which describes the Filter for BDA Device - SIZEOF_ARRAY(TunerFilterPinPairings), //Number of PAIRS of pins in BDA_PIN_PAIRING Array - TunerFilterPinPairings //Array of Pin Pairing describes topology between a pair of Filter's Input and Output Pins -}; - -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1TunerFilterDefinitions.cpp + Author : + Date : + Purpose : Tuner Filter Definition + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Main Header file + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ + +//BDA Tuner Frequency Property Set +DEFINE_KSPROPERTY_TABLE(SkyWalker1TunerFrequencyProperties) +{ + DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_FREQUENCY( + CAntennaPin::GetTunerProperty, + CAntennaPin::SetTunerProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_FREQUENCY_MULTIPLIER( + CAntennaPin::GetTunerProperty, + CAntennaPin::SetTunerProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_POLARITY( + CAntennaPin::GetTunerProperty, + CAntennaPin::SetTunerProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_RANGE( + CAntennaPin::GetTunerProperty, + CAntennaPin::SetTunerProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_BANDWIDTH( + CAntennaPin::GetTunerProperty, + CAntennaPin::SetTunerProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_RF_TUNER_TRANSPONDER( + CAntennaPin::GetTunerProperty, + CAntennaPin::SetTunerProperty + ), +}; + +//BDA LNB Info Property Set +DEFINE_KSPROPERTY_TABLE(SkyWalker1TunerLnbProperties) +{ + DEFINE_KSPROPERTY_ITEM_BDA_LNB_LOF_HIGH_BAND( + CAntennaPin::GetTunerLnbProperty, + CAntennaPin::SetTunerLnbProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_LNB_LOF_LOW_BAND( + CAntennaPin::GetTunerLnbProperty, + CAntennaPin::SetTunerLnbProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_LNB_SWITCH_FREQUENCY( + CAntennaPin::GetTunerLnbProperty, + CAntennaPin::SetTunerLnbProperty + ), +}; + +//BDA Signal Statistics Properties +// +//Defines the dispatch routines for the Signal Statistics Properties +//on the RF Tuner, Demodulator, and PID Filter Nodes +// +DEFINE_KSPROPERTY_TABLE(SkyWalker1TunerSignalProperties) +{ + + DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_STRENGTH( + CAntennaPin::GetSignalStatus, + NULL + ), + DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_QUALITY( + CAntennaPin::GetSignalStatus, + NULL + ), + DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_PRESENT( + CAntennaPin::GetSignalStatus, + NULL + ), + DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_LOCKED( + CAntennaPin::GetSignalStatus, + NULL + ), +#ifdef LOCK_CODE + DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_LOCK_CAPS( + CAntennaPin::GetSignalStatus + ), + DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_LOCK_TYPE( + CAntennaPin::GetSignalStatus + ), + DEFINE_KSPROPERTY_ITEM_BDA_SAMPLE_TIME( + CTransportPin::GetSignalStatus, + NULL + ), +#endif + + +}; + +DEFINE_KSPROPERTY_SET_TABLE(SkyWalker1TunerAutomationProperties) +{ + DEFINE_KSPROPERTY_SET + ( + &KSPROPSETID_BdaFrequencyFilter, //Property Set defined elsewhere + SIZEOF_ARRAY(SkyWalker1TunerFrequencyProperties), //Number of properties in the array + SkyWalker1TunerFrequencyProperties, //Property set array + 0, //FastIoCount + NULL //FastIoTable + ), + DEFINE_KSPROPERTY_SET + ( + &KSPROPSETID_BdaLNBInfo, //Property Set defined elsewhere + SIZEOF_ARRAY(SkyWalker1TunerLnbProperties), //Number of properties in the array + SkyWalker1TunerLnbProperties, //Property set array + 0, //FastIoCount + NULL //FastIoTable + ), + DEFINE_KSPROPERTY_SET + ( + &KSPROPSETID_BdaSignalStats, //Property Set defined elsewhere + SIZEOF_ARRAY(SkyWalker1TunerSignalProperties), //Number of properties in the array + SkyWalker1TunerSignalProperties, //Property set array + 0, //FastIoCount + NULL //FastIoTable + ), +}; + +//Tuner Automation Table.Used to get and tuner related Methods, +//Events and Properties +DEFINE_KSAUTOMATION_TABLE(SkyWalker1TunerAutomation) +{ + DEFINE_KSAUTOMATION_PROPERTIES(SkyWalker1TunerAutomationProperties), + DEFINE_KSAUTOMATION_METHODS_NULL, + DEFINE_KSAUTOMATION_EVENTS_NULL +}; + +//BDA Signal Statistics Properties for Demodulator Node + +//Defines the dispatch routines for the Signal Statistics Properties +//on the Demodulator Node. +DEFINE_KSPROPERTY_TABLE(SkyWalker1DemodulatorSignalStats) +{ + DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_QUALITY( + CTransportPin::GetSignalStatus, + NULL + ), + DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_LOCKED( + CTransportPin::GetSignalStatus, + NULL + ), + DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_PRESENT( + CTransportPin::GetSignalStatus, + NULL + ), + DEFINE_KSPROPERTY_ITEM_BDA_SIGNAL_STRENGTH( + CTransportPin::GetSignalStatus, + NULL + ), +}; + +// +//BDA Digital Demodulator Property Set for Demodulator Node +// +//Defines the dispatch routines for the Digital Demodulator Properties +//on the Demodulator Node. +// +DEFINE_KSPROPERTY_TABLE(SkyWalker1DemodulatorProps) +{ + DEFINE_KSPROPERTY_ITEM_BDA_MODULATION_TYPE( + CTransportPin::GetDigitalDemodProperty, + CTransportPin::SetDigitalDemodProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_INNER_FEC_TYPE( + CTransportPin::GetDigitalDemodProperty, + CTransportPin::SetDigitalDemodProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_INNER_FEC_RATE( + CTransportPin::GetDigitalDemodProperty, + CTransportPin::SetDigitalDemodProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_OUTER_FEC_TYPE( + CTransportPin::GetDigitalDemodProperty, + CTransportPin::SetDigitalDemodProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_OUTER_FEC_RATE( + CTransportPin::GetDigitalDemodProperty, + CTransportPin::SetDigitalDemodProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_SYMBOL_RATE( + CTransportPin::GetDigitalDemodProperty, + CTransportPin::SetDigitalDemodProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_SPECTRAL_INVERSION( + CTransportPin::GetDigitalDemodProperty, + CTransportPin::SetDigitalDemodProperty + ), + DEFINE_KSPROPERTY_ITEM_BDA_TRANSMISSION_MODE( + CTransportPin::GetDigitalDemodProperty, + CTransportPin::SetDigitalDemodProperty + ), +}; + +//BDA Extended Property Set +DEFINE_KSPROPERTY_TABLE(SkyWalker1ExtendedProperties) +{ + DEFINE_KSPROPERTY_ITEM_BDA_DISEQC( + NULL, + CTransportPin::SetExtendedProperty + ), +}; + + +/*****************************************************************************************/ + +//Demodulator Node Property Sets supported +// +//This table defines all property sets supported by the +//Demodulator Node associated with the transport output pin. +// +DEFINE_KSPROPERTY_SET_TABLE(SkyWalker1DemodulatorProperties) +{ + + DEFINE_KSPROPERTY_SET + ( + &KSPROPSETID_BdaDigitalDemodulator, //Set + SIZEOF_ARRAY(SkyWalker1DemodulatorProps), //PropertiesCount + SkyWalker1DemodulatorProps, //PropertyItems + 0, //FastIoCount + NULL //FastIoTable + ), + DEFINE_KSPROPERTY_SET + ( + &KSPROPSETID_BdaExtendedProperty, //Set + SIZEOF_ARRAY(SkyWalker1ExtendedProperties), //PropertiesCount + SkyWalker1ExtendedProperties, //PropertyItems + 0, //FastIoCount + NULL //FastIoTable + ), + DEFINE_KSPROPERTY_SET + ( + &KSPROPSETID_BdaSignalStats, //Set + SIZEOF_ARRAY(SkyWalker1DemodulatorSignalStats), //PropertiesCount + SkyWalker1DemodulatorSignalStats, //PropertyItems + 0, //FastIoCount + NULL //FastIoTable + ), + + // + //Additional property sets for the node can be added here. + // +}; + + +DEFINE_KSAUTOMATION_TABLE(SkyWalker1DemodulatorAutomation) { + DEFINE_KSAUTOMATION_PROPERTIES(SkyWalker1DemodulatorProperties), + DEFINE_KSAUTOMATION_METHODS_NULL, + DEFINE_KSAUTOMATION_EVENTS_NULL +}; + +/*****************************************************************************************/ +//Template Node Descriptors +// +//Define an array that contains all the node types that are available in the template +//topology of the filter. +//These node types must be supported by BDA and +//defined elsewhere (for example, in Bdamedia.h). +// + +const KSNODE_DESCRIPTOR TunerFilterNodeDescriptors[] = +{ + DEFINE_NODE_DESCRIPTOR( + &SkyWalker1TunerAutomation, //Point to KSAUTOMATION_TABLE structure + //for the node's automation table + &KSNODE_BDA_RF_TUNER, //Point to the guid that defines function + //of the node + NULL //Point to the guid that represents the + //name of the topology node + ), + DEFINE_NODE_DESCRIPTOR( + &SkyWalker1DemodulatorAutomation,//Point to KSAUTOMATION_TABLE structure + //for the node's automation table + &KSNODE_BDA_QPSK_DEMODULATOR,//Point to the guid that defines function + //of the node + NULL //Point to the guid that represents the + //name of the topology node + ), +}; + +/*****************************************************************************************/ +//Define BDA Template Topology Connections +// +//Lists the Connections that are possible between pin types and +//node types. This, together with the Template Filter Descriptor, and +//the Pin Pairings, describe how topologies can be created in the filter. +// +// =========== ============ +//AntennaPin ----| RF Node |--Joint--|Demod Node|----TransportPin +// =========== ============ +// +//The RF Node of this filter is controlled by the Antenna input pin. +//RF properties will be set as NODE properties (with NodeType == 0) +//on the filter's Antenna Pin +// +//The Demodulator Node of this filter is controlled by the Transport output pin. +//Demod properties will be set as NODE properties (with NodeType == 1) +//on the filter's Transport Pin + +const KSTOPOLOGY_CONNECTION TunerFilterConnections[]={ + {KSFILTER_NODE, 0, 0, KSNODEPIN_STANDARD_IN}, //Antenna pin -> Tuner pin 0 + {0, KSNODEPIN_STANDARD_OUT, 1, KSNODEPIN_STANDARD_IN}, //Tuner pin 1 -> Demodulator pin 0 + {1, KSNODEPIN_STANDARD_OUT, KSFILTER_NODE, 1}, //Demodulator pin 1 -> Transport pin +}; + +//Lists the template joints between the Antenna Input Pin Type and +//the Transport Output Pin Type. +// +//In this case the RF Node is considered to belong to the antennea input +//pin and the 8VSB Demodulator Node is considered to belong to the +//tranport stream output pin. +// +const ULONG InterNodeJoints[] = +{ + 1 //joint occurs between the two node types (second element in array) + //indicates that 1st node is controlled by input pin and 2nd node by output pin +}; + + +//Array of BDA_PIN_PAIRING structures that are used to determine +//which nodes get duplicated when more than one output pin type is +//connected to a single input pin type or when more that one input pin +//type is connected to a single output pin type. +// +const BDA_PIN_PAIRING TunerFilterPinPairings[] = +{ + //Input pin to Output pin Topology Joints + { + 0, //ulInputPin; 0 element in the TemplatePinDescriptors array. + 1, //ulOutputPin; 1 element in the TemplatePinDescriptors array. + 1, //ulcMaxInputsPerOutput + 1, //ulcMinInputsPerOutput + 1, //ulcMaxOutputsPerInput + 1, //ulcMinOutputsPerInput + SIZEOF_ARRAY(InterNodeJoints), //ulcTopologyJoints + InterNodeJoints //pTopologyJoints; array of joints + } + //If applicable, list topology of joints between other pins. +}; + +/*****************************************************************************************/ + +const KSCOMPONENTID TunerFilterComponentId={ + NULL, + NULL, + NULL, + NULL, + 1, //Version + 0 //Revision +}; +/**********************************************************************************/ + +// +//Dispatch Table for the antenna pin. +// + +const KSPIN_DISPATCH AntennaPinDispatch={ + /* Create */ CAntennaPin::PinCreate, + /* Close */ CAntennaPin::PinClose, + /* Process */ NULL, + /* Reset */ NULL, + /* SetDataFormat */ NULL, + /* SetDeviceState */ CAntennaPin::PinSetDeviceState, + /* Connect */ NULL, + /* Disconnect */ NULL, + /* Allocator */ NULL +}; + +DEFINE_KSAUTOMATION_TABLE(NullAutomation) +{ + DEFINE_KSAUTOMATION_PROPERTIES_NULL, + DEFINE_KSAUTOMATION_METHODS_NULL, + DEFINE_KSAUTOMATION_EVENTS_NULL +}; + +const KS_DATARANGE_BDA_ANTENNA AntennaPinRange = +{ + //insert the KSDATARANGE and KSDATAFORMAT here + { + sizeof( KS_DATARANGE_BDA_ANTENNA), //FormatSize + 0, //Flags - (N/A) + 0, //SampleSize - (N/A) + 0, //Reserved + { STATIC_KSDATAFORMAT_TYPE_BDA_ANTENNA }, //MajorFormat + { STATIC_KSDATAFORMAT_SUBTYPE_NONE }, //SubFormat + { STATIC_KSDATAFORMAT_SPECIFIER_NONE } //Specifier + } +}; + +const PKSDATARANGE AntennaPinRanges[]={ + (PKSDATARANGE)&AntennaPinRange, +}; + +// +//Dispatch Table for the transport Output pin. +// +//Since data on the transport is actually delivered to the +//PCI bridge in hardware, this pin does not process data. +// +//Connection of, and state transitions on, this pin help the +//driver to determine when to allocate hardware resources for +//each node. +// +const KSPIN_DISPATCH TransportPinDispatch = +{ + CTransportPin::PinCreate, //Create + CTransportPin::PinClose, //Close + NULL, //Process + NULL, //Reset + NULL, //SetDataFormat + /*AntennaPinSetDeviceState*/ NULL, //SetDeviceState + NULL, //Connect + NULL, //Disconnect + NULL, //Clock + NULL //Allocator +}; + + +const KSPIN_INTERFACE StreamInterface[]={ + { + STATICGUIDOF(KSINTERFACESETID_Standard), + KSINTERFACE_STANDARD_STREAMING, + 0 + }, +}; + +//Medium GUIDs for the Transport Output Pin. +// +//Pin Medium descriptor containing all medium accepted to be connected to +//the tuner output pin.This insures contection to the correct Capture Filter pin. +// +//{2AEB4A94-FBB7-4FB1-8D74-243B91886EAB} + +const KSPIN_MEDIUM TransportPinMediums[] = +{ + { + GUID_SKYWALKER_TUNER_OUT_MEDIUM, + 0, + 0 + } +}; + +const KS_DATARANGE_BDA_TRANSPORT TransportPinRange = +{ + //insert the KSDATARANGE and KSDATAFORMAT here + { + sizeof( KS_DATARANGE_BDA_TRANSPORT), //FormatSize + 0, //Flags - (N/A) + 0, //SampleSize - (N/A) + 0, //Reserved + { STATIC_KSDATAFORMAT_TYPE_STREAM }, //MajorFormat + { STATIC_KSDATAFORMAT_TYPE_MPEG2_TRANSPORT }, //SubFormat + { STATIC_KSDATAFORMAT_SPECIFIER_BDA_TRANSPORT } //Specifier + }, + //BDA_TRANSPORT_INFO + { + TRANSPORT_PACKET_SIZE, //Bytes in Line + TRANSPORT_PACKET_SIZE * TRANSPORT_PACKET_COUNT, //Frame Size + 0, //ulcbPhysicalFrameAlignment (no requirement) + 0 //AvgTimePerFrame, Time / Sample (not known) + } +}; + +//Format Ranges of Transport Output Pin. +// +static PKSDATAFORMAT TransportPinRanges[] = +{ + (PKSDATAFORMAT) &TransportPinRange, + + //Add more formats here if additional transport formats are supported. + // +}; + +DECLARE_SIMPLE_FRAMING_EX(TransportAllocator, + STATICGUIDOF(KSMEMORY_TYPE_KERNEL_NONPAGED), + KSALLOCATOR_REQUIREMENTF_SYSTEM_MEMORY/*|KSALLOCATOR_REQUIREMENTF_PREFERENCES_ONLY*/, + 8, + 0, + TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE, + TRANSPORT_PACKET_COUNT*TRANSPORT_PACKET_SIZE); + + +//Template Pin Descriptors + +//This data structure defines the pin types available in the filters +//template topology. These structures will be used to create a +//KDPinFactory for a pin type when BdaCreatePin or BdaMethodCreatePin +//are called. +// +//This structure defines ALL pins the filter is capable of supporting, +//including those pins which may only be created dynamically by a ring +//3 component such as a Network Provider. + +const KSPIN_DESCRIPTOR_EX TunerFilterPinDescriptors[]={ + { //Antenna input pin + &AntennaPinDispatch, //Dispatch Table + &NullAutomation, //Automation Table + { + 0, //Interfaces + NULL, + 0, //Mediums + NULL, + SIZEOF_ARRAY(AntennaPinRanges), + AntennaPinRanges, + KSPIN_DATAFLOW_IN, //Specifies that data flow is into the pin + KSPIN_COMMUNICATION_BOTH, //Specifies that the pin factory instantiates pins + //that are both IRP sinks and IRP sources + NULL, //Category + NULL, //Name + 0 + }, + KSPIN_FLAG_DO_NOT_USE_STANDARD_TRANSPORT| + KSPIN_FLAG_FRAMES_NOT_REQUIRED_FOR_PROCESSING| + KSPIN_FLAG_FIXED_FORMAT, + 1, //Maximum Possible Instances of the Pin + 1, //Mandatory Instances of this for the Filter function + NULL, + CAntennaPin::IntersectDataFormat //Data Interaction Handler + }, + //Tranport Output Pin + { + &TransportPinDispatch, //Point to the dispatch table for the output pin + &NullAutomation, //Point to the automation table for the output pin + { //Specify members of a KSPIN_DESCRIPTOR structure for the output pin + 0, //Interface Count + NULL, //Interfaces + SIZEOF_ARRAY(TransportPinMediums), //Medium Count + TransportPinMediums, //Medium + SIZEOF_ARRAY(TransportPinRanges), //Range Count + TransportPinRanges, //Ranges + KSPIN_DATAFLOW_OUT, //specifies that data flow is out of the pin + KSPIN_COMMUNICATION_BOTH, //specifies that the pin factory instantiates pins + (GUID *) &PINNAME_BDA_TRANSPORT, //Category GUID + (GUID *) &PINNAME_BDA_TRANSPORT, //GUID of the localized Unicode string //name for the pin type + 0 + }, //Specify flags + KSPIN_FLAG_DO_NOT_USE_STANDARD_TRANSPORT | + KSPIN_FLAG_FRAMES_NOT_REQUIRED_FOR_PROCESSING | + KSPIN_FLAG_FIXED_FORMAT, + 1, //Specify the maximum number of possible instances of the output pin + 1, //Specify the number of instances of this pin type that are necessary for proper functioning of this filter + NULL, //Allocator Framing + CTransportPin::IntersectDataFormat //Point to the data intersection handler function + } +}; + +/**********************************************************************************/ + +//BDA Device Topology Property Set + +//The BDA Support Library supplies a default implementation of the +//BDA Device Topology Property Set. If the driver needs to override +//this default implemenation, the definitions for the override properties +//will be defined here. + + +//BDA Device Configuration Method Set + +//The BDA Support Library provides a default implementation of +//the BDA Device Configuration Method Set. In this , the +//driver overrides the CreateTopology method. Note that the +//support libraries CreateTopology method is called before the +//driver's implementation returns. +// +DEFINE_KSMETHOD_TABLE(TunerFilterConfiguration) +{ + DEFINE_KSMETHOD_ITEM_BDA_CREATE_TOPOLOGY( + CTunerFilter::CreateTopology, //Calls BdaMethodCreateTopology + NULL + ) +}; + + +//BDA Change Sync Method Set + +//The Change Sync Method Set is required on BDA filters. Setting a +//node property should not become effective on the underlying device +//until CommitChanges is called. +//The BDA Support Library provides routines that handle committing +//changes to topology. The BDA Support Library routines should be +//called from the driver's implementation before the driver implementation +//returns. + +DEFINE_KSMETHOD_TABLE(TunerFilterChangeSync) +{ + DEFINE_KSMETHOD_ITEM_BDA_START_CHANGES( + CTunerFilter::StartChanges, //Calls BdaStartChanges + NULL + ), + DEFINE_KSMETHOD_ITEM_BDA_CHECK_CHANGES( + CTunerFilter::CheckChanges, //Calls BdaCheckChanges + NULL + ), + DEFINE_KSMETHOD_ITEM_BDA_COMMIT_CHANGES( + CTunerFilter::CommitChanges, //Calls BdaCommitChanges + NULL + ), + DEFINE_KSMETHOD_ITEM_BDA_GET_CHANGE_STATE( + CTunerFilter::GetChangeState, //Calls BdaGetChangeState + NULL + ) +}; + + +//Array of Method sets supported by filter +DEFINE_KSMETHOD_SET_TABLE(TunerFilterMethods) +{ + DEFINE_KSMETHOD_SET + ( + &KSMETHODSETID_BdaChangeSync, //Method set GUID + SIZEOF_ARRAY(TunerFilterChangeSync), //Number of methods + TunerFilterChangeSync, //Array of KSMETHOD_ITEM structures + 0, //FastIoCount + NULL //FastIoTable + ), + + DEFINE_KSMETHOD_SET + ( + &KSMETHODSETID_BdaDeviceConfiguration, //Method set GUID + SIZEOF_ARRAY(TunerFilterConfiguration), //Number of methods + TunerFilterConfiguration, //Array of KSMETHOD_ITEM structures + 0, //FastIoCount + NULL //FastIoTable + ) + +}; + +//Supporting only Filter Methods;Properties and Events are not supported +DEFINE_KSAUTOMATION_TABLE(TunerFilterAutomationTable) +{ + DEFINE_KSAUTOMATION_PROPERTIES_NULL, + DEFINE_KSAUTOMATION_METHODS(TunerFilterMethods), + DEFINE_KSAUTOMATION_EVENTS_NULL +}; +/**********************************************************************************/ +//Dispatch table for the Filter Processing +const KSFILTER_DISPATCH TunerFilterDispatchTable = +{ + /* Create */ CTunerFilter::Create, //Routine called when the Filter is created + /* Close */ CTunerFilter::FilterClose, //Routine called when the Filter is closed + /* Process */ NULL, + /* Reset */ NULL +}; + +/*****************************************************************************************/ + +//Define the Filter Factory Descriptor for the filter +//This structure brings together all of the structures that define +//the tuner filter as it appears when it is first instantiated. +//Note that not all of the template pin and node types may be exposed as +//pin and node factories when the filter is first instanciated. + +//The KSFILTER_DESCRIPTOR structure describes the characteristics of a filter created by a given filter factory. +DEFINE_KSFILTER_DESCRIPTOR(SkyWalker1TunerFilterDescriptor) +{ + &TunerFilterDispatchTable, //Dispatch (Filter Specific Driver) + &TunerFilterAutomationTable, //AutomationTable + KSFILTER_DESCRIPTOR_VERSION, //Version + 0, //Flags + &SKYWALKER_TUNER_FILTER, //ReferenceGuid + DEFINE_KSFILTER_PIN_DESCRIPTORS(TunerFilterPinDescriptors), + //PinDescriptorsCount; must expose at least one pin + //PinDescriptorSize; size of each item + //PinDescriptors; table of pin descriptors + DEFINE_KSFILTER_CATEGORY(KSCATEGORY_BDA_NETWORK_TUNER), + //CategoriesCount; number of categories in the table + //Categories; table of categories + DEFINE_KSFILTER_NODE_DESCRIPTORS(TunerFilterNodeDescriptors), + //NodeDescriptorsCount; + //NodeDescriptorSize; + //NodeDescriptors; + DEFINE_KSFILTER_CONNECTIONS(TunerFilterConnections), + //Automatically fills in the connections table for a filter which defines no explicit connections + //ConnectionsCount; number of connections in the table + //Connections; table of connections + &TunerFilterComponentId //ComponentId; +}; + +//BDA_FILTER_TEMPLATE structure describes the template topology for BDA Driver +const BDA_FILTER_TEMPLATE TunerFilterTemplate = +{ + &SkyWalker1TunerFilterDescriptor,//Pointer to KS_FILTER_DESCRIPTOR which describes the Filter for BDA Device + SIZEOF_ARRAY(TunerFilterPinPairings), //Number of PAIRS of pins in BDA_PIN_PAIRING Array + TunerFilterPinPairings //Array of Pin Pairing describes topology between a pair of Filter's Input and Output Pins +}; + +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ /* End of Function prototype definitions */ \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerPin.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerPin.cpp index d81001e..52aa943 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerPin.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1TunerPin.cpp @@ -1,212 +1,212 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1CPin.cpp - Author : - Date : - Purpose : This File Holds the General Pin related declarations - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Common For all the Definitions, - //Declarations and Library Routines - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - - -/***************************************************************************** - Function : CTunerPin::PinCreate - Description : An AVStream minidriver's AVStrMiniPinCreate routine is - called when a pin is created. Typically, this routine is - used by minidrivers that want to initialize the context - and resources associated with the pin. - IN PARAM : Pointer to the KSPIN that was just created. - Pointer to the IRP_MJ_CREATE for Pin - OUT PARAM : STATUS_SUCCESS in case of successful pin creation - Failure Code in other cases - PreCondition : None - PostCondtion : Creates the Tuner pin object and associates it - with the filter object. - Logic : NONE - Assumption : NONE - Note : None - Revision History: - *****************************************************************************/ -NTSTATUS CTunerPin::PinCreate( IN OUT PKSPIN pKSPin, - IN PIRP pIoRequestPacket - ) -{ - NTSTATUS ntCreateStatus = STATUS_SUCCESS; - CTunerPin* pPin = NULL; //Pointer to the Current Pin Instance - CTunerFilter* pFilter = NULL; //Pointer to the Filter associted with the Pin - - PrintFunctionEntry(__FUNCTION__); - SkyWalkerDebugPrint(ENTRY_LEVEL,("Sizeof DISEQC_COMMAND = %d\n",sizeof(DISEQC_COMMAND))); - //Obtain a pointer to the filter object for which the input pin is created. - - //The KsGetFilterFromIrp function returns the AVStream filter object - //associated with a given IRP. - pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); - - //Create the Tuner pin object. - pPin = new(PagedPool,TUNER_MEM_TAG) CTunerPin; // Tags the allocated memory - - if (pPin) - { - //Link the pin context to the filter context. - //That is, set the input pin's filter pointer data member to the obtained filter pointer. - pPin->SetFilter( pFilter); - - //Link the pin context to the passed in pointer to the KSPIN structure. - pKSPin->Context = pPin; - - } - else - { - ntCreateStatus = STATUS_INSUFFICIENT_RESOURCES; - } - - PrintFunctionExit(__FUNCTION__,ntCreateStatus); - return ntCreateStatus; -} - -/***************************************************************************** - Function : CTunerPin::PinClose - Description : An AVStream minidriver's AVStrMiniPinClose routine is - called when a pin is closed.It usually is provided by - minidrivers that want to free the context and resources - associated with the pin. - IN PARAM : Pointer to the KSPIN that was just closed. - Pointer to the IRP_MJ_CLOSE for Pin. - OUT PARAM : STATUS_SUCCESS in case of successful pin Close - Failure Code in other cases - PreCondition : None - PostCondtion : Deletes the previously created Tuner pin object. - Logic : NONE - Assumption : NONE - Note : This is called from the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS CTunerPin::PinClose( IN OUT PKSPIN pKSPin, - IN PIRP pIoRequestPacket - ) -{ - NTSTATUS ntCloseStatus = STATUS_SUCCESS; - CTunerPin* pPin = NULL; //Pointer to the Current Pin Instance - CTunerFilter* pFilter = NULL; //Pointer to the Filter associted with the Pin - - PrintFunctionEntry(__FUNCTION__); - - // Retrieve the Tuner pin object from the passed in - // KSPIN structure's context member. - // - pPin = reinterpret_cast(pKSPin->Context); - - if(IS_VALID(pPin)) - { - delete pPin; - pPin = NULL; - } - - PrintFunctionExit(__FUNCTION__,ntCloseStatus); - return ntCloseStatus; -} - -/***************************************************************************** - Function : CTunerPin::GetSignalStatus - Description : Retrieves the value of the signal statistics properties. - IN PARAM : IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - OUT PULONG pulProperty - OUT PARAM : Status SUCCESS in case Valid Property request - STATUS_INVALID_PARAMETER in case of Invalid property request - Else error from the lower device - PreCondition : None - PostCondtion : Signal Status read in case of successful execution - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CTunerPin::GetSignalStatus( - IN PIRP pIoRequestPacket, - IN PKSPROPERTY pKSProperty, - OUT PULONG pulProperty - ) -{ - NTSTATUS ntGetStatus = STATUS_SUCCESS; - CTunerPin* pPin = NULL; //Pointer to the Current Pin Instance - CTunerFilter* pFilter = NULL; //Pointer to the Filter associted with the Pin - BDATUNER_DEVICE_STATUS TunerStatus; - - PrintFunctionEntry(__FUNCTION__); - - - // Call the BDA support library to - // validate that the node type is associated with this pin. - ntGetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); - if (NT_SUCCESS( ntGetStatus)) - { - // Obtain a pointer to the pin object. - // - // Because the property dispatch table calls the CTunerPin::GetSignalStatus() - // method directly, the method must retrieve a pointer to the underlying pin object. - // - pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); - - // Retrieve the filter context from the pin context. - // - pFilter = pPin->GetFilter(); - - ntGetStatus = pFilter->GetStatus( &TunerStatus); - if (ntGetStatus == STATUS_SUCCESS) - { - switch (pKSProperty->Id) - { - case KSPROPERTY_BDA_SIGNAL_LOCKED: - *pulProperty = TunerStatus.fSignalLocked; - SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Lock = 0x%02X\n",*pulProperty)); - break; - case KSPROPERTY_BDA_SIGNAL_QUALITY: - *pulProperty = TunerStatus.dwSignalQuality; - SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Quality = %lu\n",*pulProperty)); - break; - case KSPROPERTY_BDA_SIGNAL_PRESENT: - *pulProperty = TunerStatus.fCarrierPresent; - SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Present = 0x%02X\n",*pulProperty)); - break; - case KSPROPERTY_BDA_SIGNAL_STRENGTH: - *pulProperty = TunerStatus.dwSignalStrength; - SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Strength = %lu\n", *pulProperty)); - break; - default: - ntGetStatus = STATUS_INVALID_PARAMETER; - } - } - } - - PrintFunctionExit(__FUNCTION__,ntGetStatus); - return ntGetStatus; +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1CPin.cpp + Author : + Date : + Purpose : This File Holds the General Pin related declarations + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Common For all the Definitions, + //Declarations and Library Routines + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + + +/***************************************************************************** + Function : CTunerPin::PinCreate + Description : An AVStream minidriver's AVStrMiniPinCreate routine is + called when a pin is created. Typically, this routine is + used by minidrivers that want to initialize the context + and resources associated with the pin. + IN PARAM : Pointer to the KSPIN that was just created. + Pointer to the IRP_MJ_CREATE for Pin + OUT PARAM : STATUS_SUCCESS in case of successful pin creation + Failure Code in other cases + PreCondition : None + PostCondtion : Creates the Tuner pin object and associates it + with the filter object. + Logic : NONE + Assumption : NONE + Note : None + Revision History: + *****************************************************************************/ +NTSTATUS CTunerPin::PinCreate( IN OUT PKSPIN pKSPin, + IN PIRP pIoRequestPacket + ) +{ + NTSTATUS ntCreateStatus = STATUS_SUCCESS; + CTunerPin* pPin = NULL; //Pointer to the Current Pin Instance + CTunerFilter* pFilter = NULL; //Pointer to the Filter associted with the Pin + + PrintFunctionEntry(__FUNCTION__); + SkyWalkerDebugPrint(ENTRY_LEVEL,("Sizeof DISEQC_COMMAND = %d\n",sizeof(DISEQC_COMMAND))); + //Obtain a pointer to the filter object for which the input pin is created. + + //The KsGetFilterFromIrp function returns the AVStream filter object + //associated with a given IRP. + pFilter = reinterpret_cast(KsGetFilterFromIrp(pIoRequestPacket)->Context); + + //Create the Tuner pin object. + pPin = new(PagedPool,TUNER_MEM_TAG) CTunerPin; // Tags the allocated memory + + if (pPin) + { + //Link the pin context to the filter context. + //That is, set the input pin's filter pointer data member to the obtained filter pointer. + pPin->SetFilter( pFilter); + + //Link the pin context to the passed in pointer to the KSPIN structure. + pKSPin->Context = pPin; + + } + else + { + ntCreateStatus = STATUS_INSUFFICIENT_RESOURCES; + } + + PrintFunctionExit(__FUNCTION__,ntCreateStatus); + return ntCreateStatus; +} + +/***************************************************************************** + Function : CTunerPin::PinClose + Description : An AVStream minidriver's AVStrMiniPinClose routine is + called when a pin is closed.It usually is provided by + minidrivers that want to free the context and resources + associated with the pin. + IN PARAM : Pointer to the KSPIN that was just closed. + Pointer to the IRP_MJ_CLOSE for Pin. + OUT PARAM : STATUS_SUCCESS in case of successful pin Close + Failure Code in other cases + PreCondition : None + PostCondtion : Deletes the previously created Tuner pin object. + Logic : NONE + Assumption : NONE + Note : This is called from the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS CTunerPin::PinClose( IN OUT PKSPIN pKSPin, + IN PIRP pIoRequestPacket + ) +{ + NTSTATUS ntCloseStatus = STATUS_SUCCESS; + CTunerPin* pPin = NULL; //Pointer to the Current Pin Instance + CTunerFilter* pFilter = NULL; //Pointer to the Filter associted with the Pin + + PrintFunctionEntry(__FUNCTION__); + + // Retrieve the Tuner pin object from the passed in + // KSPIN structure's context member. + // + pPin = reinterpret_cast(pKSPin->Context); + + if(IS_VALID(pPin)) + { + delete pPin; + pPin = NULL; + } + + PrintFunctionExit(__FUNCTION__,ntCloseStatus); + return ntCloseStatus; +} + +/***************************************************************************** + Function : CTunerPin::GetSignalStatus + Description : Retrieves the value of the signal statistics properties. + IN PARAM : IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + OUT PULONG pulProperty + OUT PARAM : Status SUCCESS in case Valid Property request + STATUS_INVALID_PARAMETER in case of Invalid property request + Else error from the lower device + PreCondition : None + PostCondtion : Signal Status read in case of successful execution + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CTunerPin::GetSignalStatus( + IN PIRP pIoRequestPacket, + IN PKSPROPERTY pKSProperty, + OUT PULONG pulProperty + ) +{ + NTSTATUS ntGetStatus = STATUS_SUCCESS; + CTunerPin* pPin = NULL; //Pointer to the Current Pin Instance + CTunerFilter* pFilter = NULL; //Pointer to the Filter associted with the Pin + BDATUNER_DEVICE_STATUS TunerStatus; + + PrintFunctionEntry(__FUNCTION__); + + + // Call the BDA support library to + // validate that the node type is associated with this pin. + ntGetStatus = BdaValidateNodeProperty( pIoRequestPacket, pKSProperty); + if (NT_SUCCESS( ntGetStatus)) + { + // Obtain a pointer to the pin object. + // + // Because the property dispatch table calls the CTunerPin::GetSignalStatus() + // method directly, the method must retrieve a pointer to the underlying pin object. + // + pPin = reinterpret_cast(KsGetPinFromIrp(pIoRequestPacket)->Context); + + // Retrieve the filter context from the pin context. + // + pFilter = pPin->GetFilter(); + + ntGetStatus = pFilter->GetStatus( &TunerStatus); + if (ntGetStatus == STATUS_SUCCESS) + { + switch (pKSProperty->Id) + { + case KSPROPERTY_BDA_SIGNAL_LOCKED: + *pulProperty = TunerStatus.fSignalLocked; + SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Lock = 0x%02X\n",*pulProperty)); + break; + case KSPROPERTY_BDA_SIGNAL_QUALITY: + *pulProperty = TunerStatus.dwSignalQuality; + SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Quality = %lu\n",*pulProperty)); + break; + case KSPROPERTY_BDA_SIGNAL_PRESENT: + *pulProperty = TunerStatus.fCarrierPresent; + SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Present = 0x%02X\n",*pulProperty)); + break; + case KSPROPERTY_BDA_SIGNAL_STRENGTH: + *pulProperty = TunerStatus.dwSignalStrength; + SkyWalkerDebugPrint(EXTREME_LEVEL,("Signal Strength = %lu\n", *pulProperty)); + break; + default: + ntGetStatus = STATUS_INVALID_PARAMETER; + } + } + } + + PrintFunctionExit(__FUNCTION__,ntGetStatus); + return ntGetStatus; } \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1USB.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1USB.cpp index e83ef37..25341fa 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1USB.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1USB.cpp @@ -1,2101 +1,2101 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1Usb.cpp - Author : - Date : - Purpose : This File Holds all the USB Device access related declarations - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ -/* Include the Library and Other header file */ -//#include -#include "SkyWalker1Main.h" //Header for the Tuner related definitions - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -#define USB_MEMORY_TAG 'MBSU' -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -NTSTATUS ReadandSelectDescriptors( IN PKSDEVICE pKSDeviceObject ); -NTSTATUS ConfigureDevice(IN PKSDEVICE pKSDeviceObject); -NTSTATUS SelectInterfaces( IN PKSDEVICE pKSDeviceObject, - IN PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor); - -NTSTATUS UsbReadWriteCompletion( - IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket, - IN PVOID pContext - ); -NTSTATUS ResetUsbPipe( IN PKSDEVICE pKSDeviceObject, - IN PUSBD_PIPE_INFORMATION pPipeInformation); -NTSTATUS ResetUsbDevice(IN PKSDEVICE pKSDeviceObject); -NTSTATUS GetUsbPortStatus( IN PKSDEVICE pKSDeviceObject, - IN OUT PULONG pulPortStatus); -NTSTATUS ResetUsbParentPort( IN PKSDEVICE pKSDeviceObject ); -NTSTATUS SendURBToBusDriver(IN PKSDEVICE pKSDeviceObject, - IN PURB pUSBRequestBlock ); -NTSTATUS AbortUsbPipes(IN PKSDEVICE pKSDeviceObject); -LONG IncrementPendingIoCount(IN OUT CSkyWalker1Device * pDevice); -LONG DecrementPendingIoCount(IN OUT CSkyWalker1Device * pDevice); - -//Debugging related Functions -VOID PrintDeviceDescriptor( PUSB_DEVICE_DESCRIPTOR pDeviceDescriptor); -VOID PrintConfigurationDescriptor(IN PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor); -VOID PrintInterfaceDescriptor(IN PUSB_INTERFACE_DESCRIPTOR pInterfaceDescriptor); -VOID PrintPipeInformation(PUSBD_PIPE_INFORMATION pPipeInformation); -/* End of Function prototype definitions */ - -/***************************************************************************** - Function : InitializeUsbDevice - Description : Function to used to Initialize the USB Interface of the Tuner - IN PARAM : Pointer to Device Object which needs to be Initialized - Irp to start the device IRP_MN_START - OUT PARAM : Status of the Device Initialization - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Device initialized - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS InitializeUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - NTSTATUS ntInitStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = NULL; - KIRQL kOldIrql; - - PrintFunctionEntry(__FUNCTION__); - - pDevice = reinterpret_cast(pKSDeviceObject->Context); - - //We cannot touch the device (send it any non pnp irps) until a - //start device has been passed down to the lower drivers. - //first pass the Irp down - - ntInitStatus = PassDownIRPAndWaitForCompletion( pKSDeviceObject->NextDeviceObject, - pIoRequestPacket, - TRUE); - if(!NT_SUCCESS(ntInitStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Lower Driver did not start !!!,Stopping Device Start\n")); - goto FinishStartDevice; - } - - //Lower Device Initialized , Start the Device Now - // - //Read the device descriptor, configuration descriptor - //and select the interface descriptors - // - - ntInitStatus = ReadandSelectDescriptors(pKSDeviceObject); - - if(!NT_SUCCESS(ntInitStatus)) - { - - SkyWalkerDebugPrint(ENTRY_LEVEL,("ReadandSelectDescriptors failed\n")); - goto FinishStartDevice; - } - - KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); - - SET_NEW_PNP_STATE(pDevice, Working); - pDevice->QueueState = AllowRequests; - - KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); - -/* - //initialize wait wake outstanding flag to false. - //and issue a wait wake. - - deviceExtension->FlagWWOutstanding = 0; - deviceExtension->FlagWWCancel = 0; - deviceExtension->WaitWakeIrp = NULL; - - if(deviceExtension->WaitWakeEnable) - { - - IssueWaitWake(deviceExtension); - } - - ProcessQueuedRequests(deviceExtension); - -*/ -FinishStartDevice: - PrintFunctionExit(__FUNCTION__,ntInitStatus); - - return ntInitStatus; -} - -/***************************************************************************** - Function : ReadandSelectDescriptors - Description : Function to used to Read Device Descriptor - IN PARAM : Pointer to KS Device Object - OUT PARAM : Status of the Device Descriptor Read - STATUS_SUCCESS on Successful execution - else Error - PreCondition : NONE - PostCondtion : Device Descriptor Read - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS ReadandSelectDescriptors( IN PKSDEVICE pKSDeviceObject ) -{ - URB USBRequestBlock; - NTSTATUS ntStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; - - PrintFunctionEntry(__FUNCTION__); - - //1. Read the device descriptor - - UsbBuildGetDescriptorRequest( - &USBRequestBlock, - (USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST), - USB_DEVICE_DESCRIPTOR_TYPE, - 0, - 0, - &pDevice->USBDeviceDescriptor, - NULL, - sizeof(pDevice->USBDeviceDescriptor), - NULL); - - ntStatus = SendURBToBusDriver( pKSDeviceObject, - &USBRequestBlock); - if (!NT_SUCCESS(ntStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Error Trying to Read Device Descriptor\n")); - goto CompleteReadAndSetup; - } - - PrintDeviceDescriptor(&pDevice->USBDeviceDescriptor); - - //Device Descriptor read successfully thus, Read and select configuration - ntStatus = ConfigureDevice(pKSDeviceObject); - -CompleteReadAndSetup: - - PrintFunctionExit(__FUNCTION__,ntStatus); - return ntStatus; -} - -/***************************************************************************** - Function : ConfigureDevice - Description : This helper routine reads the configuration descriptor - for the device in couple of steps. - IN PARAM : Pointer to KS Device Object - OUT PARAM : Status of the Configuration Descriptor Read - STATUS_SUCCESS on Successful execution - else Error - PreCondition : NONE - PostCondtion : Configuration Descriptor Read - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS ConfigureDevice(IN PKSDEVICE pKSDeviceObject) -{ - URB USBRequestBlock; - NTSTATUS ntConfigureStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; - USB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor; - PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor = NULL; - - PrintFunctionEntry(__FUNCTION__); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Total Number of Configurations = %d\n", pDevice->USBDeviceDescriptor.bNumConfigurations)); - - //Read the first configuration descriptor - //This requires two steps: - //1. Read the fixed sized configuration desciptor (CD) - //2. Read the CD with all embedded interface and endpoint descriptors - - UsbBuildGetDescriptorRequest( &USBRequestBlock, - sizeof(_URB_CONTROL_DESCRIPTOR_REQUEST), - USB_CONFIGURATION_DESCRIPTOR_TYPE, - 0, - 0, - &ConfigurationDescriptor, - NULL, - sizeof(ConfigurationDescriptor), - NULL); - - ntConfigureStatus = SendURBToBusDriver( pKSDeviceObject, - &USBRequestBlock); - if (!NT_SUCCESS(ntConfigureStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Error Trying to Read Fixed Size Configuration Descriptor\n")); - goto CompleteDeviceConfigure; - } - - ULONG ulConfigurationDesciptorSize = ConfigurationDescriptor.wTotalLength; - SkyWalkerDebugPrint(EXTREME_LEVEL, ("Configuration Descriptor Size = %lu \n",ulConfigurationDesciptorSize)); - - pConfigurationDescriptor = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag( NonPagedPool, - ulConfigurationDesciptorSize, - USB_MEMORY_TAG); - if (!pConfigurationDescriptor) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to allocate %lu bytes for Configuration Descriptor\n", ulConfigurationDesciptorSize)); - ntConfigureStatus = STATUS_INSUFFICIENT_RESOURCES; - goto CompleteDeviceConfigure; - } - - UsbBuildGetDescriptorRequest( &USBRequestBlock, - sizeof(_URB_CONTROL_DESCRIPTOR_REQUEST), - USB_CONFIGURATION_DESCRIPTOR_TYPE, - 0, - 0, - pConfigurationDescriptor, - NULL, - ulConfigurationDesciptorSize, - NULL); - - ntConfigureStatus = SendURBToBusDriver( pKSDeviceObject, - &USBRequestBlock); - if (!NT_SUCCESS(ntConfigureStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Error Trying to Read Actual Configuration Descriptor\n")); - goto CompleteDeviceConfigure; - } - - PrintConfigurationDescriptor(&ConfigurationDescriptor); - - ntConfigureStatus = SelectInterfaces(pKSDeviceObject, pConfigurationDescriptor); - -CompleteDeviceConfigure: - if(pConfigurationDescriptor) - { - ExFreePoolWithTag(pConfigurationDescriptor,USB_MEMORY_TAG); - } - PrintFunctionExit(__FUNCTION__,ntConfigureStatus); - return ntConfigureStatus; -} - -/***************************************************************************** - Function : SelectInterfaces - Description : This helper routine selects the configuration - IN PARAM : Pointer to KS Device Object - Configuration Descriptor - OUT PARAM : Status of the Configuration Descriptor Selection - STATUS_SUCCESS on Successful execution - else Error - PreCondition : NONE - PostCondtion : Configuration Descriptor Selection - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS SelectInterfaces( IN PKSDEVICE pKSDeviceObject, - IN PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor) -{ - NTSTATUS ntSelectStatus = STATUS_SUCCESS; - URB USBRequestBlock; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; - PUSB_INTERFACE_DESCRIPTOR pInterfaceDescriptor = NULL; - PUSBD_INTERFACE_INFORMATION pInterfaceInformation = NULL; - LONG lNumberOfInterfaces = pConfigurationDescriptor->bNumInterfaces; - LONG lInterfaceNumber = 0L; - LONG lInterfaceIndex = 0L; - ULONG ulPipeIndex = 0L; - - //The Device needs to be configured by sending a URB that specifies the configuration to use - //if device driver fails to configure the device then the I/O manager subsequently unloads - //the Driver from memory - //Search for the Configuration descriptor in the list of all configuration and obtain a pointer - //to the interface - pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx( - pConfigurationDescriptor, //Address of the Configuration Descriptor Structure - pConfigurationDescriptor, //Adress within the First Structure from where search should begin - -1, - -1, - -1, - -1, - -1); - if (!pInterfaceDescriptor) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("No Interface available for the Device\n")); - ntSelectStatus = STATUS_DEVICE_CONFIGURATION_ERROR; - goto FinishSelectInterface; - } - - //Create a URB that can be sent to the host controller driver (HCD) to set the Device - //in the configured state - USBD_INTERFACE_LIST_ENTRY Interfaces[2] = - { - {pInterfaceDescriptor, NULL}, - {NULL, NULL}, - }; - - PURB pConfigSelectURB = USBD_CreateConfigurationRequestEx(pConfigurationDescriptor, Interfaces); - if (!pConfigSelectURB) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Create Configuration Request\n")); - ntSelectStatus = STATUS_INSUFFICIENT_RESOURCES; - goto FinishSelectInterface; - } - - //Get the Interface supported by selected configuration - - pInterfaceInformation = &pConfigSelectURB->UrbSelectConfiguration.Interface; - - for(ulPipeIndex=0; ulPipeIndexNumberOfPipes; ulPipeIndex++) - { - //Perform pipe initialization here set the transfer size and any pipe flags we use - //USBD sets the rest of the Interface struct members - - //pInterfaceInformation->Pipes[ulPipeIndex].MaximumTransferSize = USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE; - } - - ntSelectStatus = SendURBToBusDriver(pKSDeviceObject,pConfigSelectURB); - - if (!NT_SUCCESS(ntSelectStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Error Trying to Select Configuration\n")); - goto FinishSelectInterface; - } - - PrintInterfaceDescriptor(pInterfaceDescriptor); - - for(ulPipeIndex=0; ulPipeIndexNumberOfPipes; ulPipeIndex++) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("--------------------------\n")); - PrintPipeInformation(&pInterfaceInformation->Pipes[ulPipeIndex]); - SkyWalkerDebugPrint(EXTREME_LEVEL,("--------------------------\n")); - - //Setting the Pipes - if(pInterfaceInformation->Pipes[ulPipeIndex].EndpointAddress == 0x82) - { - RtlCopyMemory( &pDevice->ReadPipe, - &pInterfaceInformation->Pipes[ulPipeIndex], - sizeof(pDevice->ReadPipe)); - } - else - { - RtlCopyMemory( &pDevice->WritePipe, - &pInterfaceInformation->Pipes[ulPipeIndex], - sizeof(pDevice->WritePipe)); - } - - } -FinishSelectInterface: - - if(pConfigSelectURB) - { - ExFreePool(pConfigSelectURB); - } - - PrintFunctionExit(__FUNCTION__,ntSelectStatus); - return ntSelectStatus; -} - -/***************************************************************************** - Function : QueryStopUsbDevice - Description : Function to used to Service PnP IRPs of Minor Type - IRP_MN_QUERY_STOP_DEVICE - IN PARAM : Pointer to Device Object whose stop query has come - Device Stop Query Irp with Minor Code IRP_MN_QUERY_STOP_DEVICE - OUT PARAM : Status of the Stop Query Processing - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Device can be stopped or not is returned - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS QueryStopUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - KIRQL kOldIrql; - NTSTATUS ntQueryStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; - - PrintFunctionEntry(__FUNCTION__); - - //If we can stop the device, we need to set the QueueState to - //HoldRequests so further requests will be queued. - KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); - - SET_NEW_PNP_STATE(pDevice, PendingStop); - pDevice->QueueState = HoldRequests; - - KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); - - //wait for the existing ones to be finished. - //first, decrement this operation - DecrementPendingIoCount(pDevice); - - KeWaitForSingleObject(&pDevice->EvDeviceStopOk, - Executive, - KernelMode, - FALSE, - NULL); - - PrintFunctionExit(__FUNCTION__,ntQueryStatus); - - return ntQueryStatus; -} - -/***************************************************************************** - Function : DecrementPendingIoCount - Description : This routine decrements the outstanding I/O count - This is typically invoked after the dispatch routine - has finished processing the irp. - IN PARAM : Device Pointer - OUT PARAM : Io Pending Count - PreCondition : NONE - PostCondtion : Pending IO Count is Decremented and same is returned to - the caller - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -LONG DecrementPendingIoCount(IN OUT CSkyWalker1Device * pDevice) -{ - LONG ulResult = 0; - KIRQL kOldIrql; - - PrintFunctionEntry(__FUNCTION__); - - KeAcquireSpinLock(&pDevice->kIoCountLock, &kOldIrql); - - ulResult = InterlockedDecrement((PLONG)&pDevice->ulOutStandingIoCount); - - if(ulResult == 1) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("Device can be Stopped\n")); - KeSetEvent(&pDevice->EvDeviceStopOk, IO_NO_INCREMENT, FALSE); - } - - if(ulResult == 0) - { - SkyWalkerDebugPrint(EXTREME_LEVEL,("Device can be Removed\n")); - KeSetEvent(&pDevice->EvDeviceRemoveOk,IO_NO_INCREMENT, FALSE); - } - - KeReleaseSpinLock(&pDevice->kIoCountLock, kOldIrql); - - SkyWalkerDebugPrint(EXTREME_LEVEL, ("%s::%d\n",__FUNCTION__,ulResult)); - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); - - return ulResult; -} -/***************************************************************************** - Function : IncrementPendingIoCount - Description : This routine increments the outstanding I/O count - This is typically invoked before the dispatch routine - to process new Irp is called - IN PARAM : Device Pointer - OUT PARAM : Io Pending Count - PreCondition : NONE - PostCondtion : Pending IO Count is Incremented and same is returned to - the caller - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -LONG IncrementPendingIoCount(IN OUT CSkyWalker1Device * pDevice) -{ - LONG ulResult = 0; - KIRQL kOldIrql; - - PrintFunctionEntry(__FUNCTION__); - KeAcquireSpinLock(&pDevice->kIoCountLock, &kOldIrql); - - ulResult = InterlockedIncrement((PLONG)&pDevice->ulOutStandingIoCount); - - //when OutStandingIO bumps from 1 to 2, clear the StopEvent - if(ulResult == 2) - { - - KeClearEvent(&pDevice->EvDeviceStopOk); - } - - KeReleaseSpinLock(&pDevice->kIoCountLock, kOldIrql); - - SkyWalkerDebugPrint(EXTREME_LEVEL, ("%s::%d\n",__FUNCTION__,ulResult)); - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); - - return ulResult; -} - -/***************************************************************************** - Function : CancelStopUsbDevice - Description : Function to used to Service PnP IRPs of Minor Type - IRP_MN_CANCEL_STOP_DEVICE - IN PARAM : Pointer to Device Object whose Cancel stop request has come - Device Stop Cancel Irp with Minor Code IRP_MN_CANCEL_STOP_DEVICE - OUT PARAM : Status of the Device Stop Cancel Processing - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Device Stop is cancelled - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CancelStopUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - KIRQL kOldIrql; - KEVENT Evevent; - NTSTATUS ntStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; - - PrintFunctionEntry(__FUNCTION__); - - //First check to see whether you have received cancel-stop - //without first receiving a query-stop. This could happen if someone - //above us fails a query-stop and passes down the subsequent - //cancel-stop. - if(pDevice->UsbDeviceState == PendingStop ) - { - if(NT_SUCCESS(ntStatus)) - { - - KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); - - RESTORE_PREVIOUS_PNP_STATE(pDevice); - pDevice->QueueState = AllowRequests; - - KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); - - //ProcessQueuedRequests(deviceExtension); - } - - } - else - { - - //spurious Irp - // - //If the device is already in an active state when the driver - //receives this IRP, a function driver simply sets status to - //success and passes the IRP to the next driver. For such a - //cancel-stop IRP, a function driver need not set a completion - //routine. - - } - - PrintFunctionExit(__FUNCTION__,ntStatus); - - return ntStatus; -} - -/***************************************************************************** - Function : DeconfigureUsbDevice - Description : This routine is invoked when the device is removed or stopped. - This routine de-configures the usb device. - IN PARAM : Pointer to KS Device Object - OUT PARAM : Status of the Device Deinitialization - STATUS_SUCCESS on Successful execution - else Error - PreCondition : NONE - PostCondtion : USB Device Uninitialized - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS DeconfigureUsbDevice(IN PKSDEVICE pKSDeviceObject) -{ - NTSTATUS ntUnInitStatus = STATUS_SUCCESS; - URB USBRequestBlock; - - PrintFunctionEntry(__FUNCTION__); - - UsbBuildSelectConfigurationRequest(&USBRequestBlock, sizeof(_URB_SELECT_CONFIGURATION), NULL); - - ntUnInitStatus = SendURBToBusDriver(pKSDeviceObject,&USBRequestBlock); - - if (!NT_SUCCESS(ntUnInitStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Error Trying to Deconfigure the Device\n")); - } - - PrintFunctionExit(__FUNCTION__,ntUnInitStatus); - return ntUnInitStatus; - -} - -/***************************************************************************** - Function : StopUsbDevice - Description : This routine is invoked when the device is stopped. - This routine services Irp of minor type IRP_MN_STOP_DEVICE - IN PARAM : Pointer to KS Device Object - STOP DEVICE Irp - OUT PARAM : Status of the Device Stop - STATUS_SUCCESS on Successful execution - else Error - PreCondition : NONE - PostCondtion : USB Device Stopped - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS StopUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - KIRQL kOldIrql; - NTSTATUS ntDeviceStopStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = NULL; - - PrintFunctionEntry(__FUNCTION__); - - //initialize variables - pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; - - - //if(WinXpOrBetter == deviceExtension->WdmVersion) { - - // if(deviceExtension->SSEnable) { - - // // - // //Cancel the timer so that the DPCs are no longer fired. - // //Thus, we are making judicious usage of our resources. - // //we do not need DPCs because the device is stopping. - // //The timers are re-initialized while handling the start - // //device irp. - // // - - // KeCancelTimer(&deviceExtension->Timer); - - // // - // //after the device is stopped, it can be surprise removed. - // //we set this to 0, so that we do not attempt to cancel - // //the timer while handling surprise remove or remove irps. - // //when we get the start device request, this flag will be - // //reinitialized. - // // - // deviceExtension->SSEnable = 0; - - // // - // //make sure that if a DPC was fired before we called cancel timer, - // //then the DPC and work-time have run to their completion - // // - // KeWaitForSingleObject(&deviceExtension->NoDpcWorkItemPendingEvent, - // Executive, - // KernelMode, - // FALSE, - // NULL); - - // // - // //make sure that the selective suspend request has been completed. - // // - // KeWaitForSingleObject(&deviceExtension->NoIdleReqPendEvent, - // Executive, - // KernelMode, - // FALSE, - // NULL); - // } - //} - - // - //after the stop Irp is sent to the lower driver object, - //the driver must not send any more Irps down that touch - //the device until another Start has occurred. - // - - /* if(deviceExtension->WaitWakeEnable) { - - CancelWaitWake(deviceExtension); - }*/ - - KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); - - SET_NEW_PNP_STATE(pDevice, Stopped); - - KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); - - ntDeviceStopStatus = DeconfigureUsbDevice(pKSDeviceObject); - - PrintFunctionExit(__FUNCTION__,ntDeviceStopStatus); - - return ntDeviceStopStatus; -} - -/***************************************************************************** - Function : ReadWriteUsbDevice - Description : Dispatch routine for read and write. - This routine creates a BULKUSB_RW_CONTEXT for a read/write. - This read/write is performed in stages of MAX_BULK_PACKET_SIZE. - once a stage of transfer is complete, then the irp is circulated again, - until the requested length of tranfer is performed. - IN PARAM : Pointer to Device Object - Device Register to/From which Write/ Read is to be performed - Transfer Buffer - Length of the Transfer Buffer - True if Read from else False for Write to device - OUT PARAM : Status of Read / Write request sending to Device - STATUS_PENDING on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Read/ Write Request submitted to Device - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS ReadWriteUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN ULONG ulStreamIndex, - IN ULONG ulPacketIndex, - IN PUCHAR pucTransferBuffer, - IN ULONG ulTransferLength, - IN BOOLEAN bRead) -{ - NTSTATUS ntReadWriteStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; - ULONG ulURBFlags = 0; - ULONG ulStageTransferLength = 0; - PURB pUSBRequestBlock = NULL; - PUSBD_PIPE_INFORMATION pPipeInformation = NULL; - PIO_STACK_LOCATION pNextStackLocation = NULL; - PBULKUSB_RW_CONTEXT pReadWriteContext = NULL; - PIRP pUsbIoRequestPacket = NULL; - - PrintFunctionEntry(__FUNCTION__); - - //Acquire device Remove lock here - if(pDevice->UsbDeviceState != Working) - { - - SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid device state\n")); - ntReadWriteStatus = STATUS_INVALID_DEVICE_STATE; - goto FinishDeviceReadWrite; - } - - if(!IS_VALID(pucTransferBuffer)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Invalid Transfer Buffer Passed\n")); - ntReadWriteStatus = STATUS_INVALID_PARAMETER; - goto FinishDeviceReadWrite; - } - - if(ulTransferLength > MAX_BULK_TRANSFER_SIZE) - { - SkyWalkerDebugPrint(ENTRY_LEVEL, - ("Tansfer Length (%lu) > MAX_BULK_TRANSFER_SIZE (%lu)\n", - ulTransferLength, - MAX_BULK_TRANSFER_SIZE)); - ntReadWriteStatus = STATUS_INVALID_PARAMETER; - goto FinishDeviceReadWrite; - } - - if(ulTransferLength == 0) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Transfer data length = 0\n")); - ntReadWriteStatus = STATUS_SUCCESS; - goto FinishDeviceReadWrite; - } - - ulURBFlags = USBD_SHORT_TRANSFER_OK; - if(bRead) - { - ulURBFlags |= USBD_TRANSFER_DIRECTION_IN; - SkyWalkerDebugPrint(EXTREME_LEVEL,("Read Operation\n")); - pPipeInformation = &pDevice->ReadPipe; - } - else - { - - ulURBFlags |= USBD_TRANSFER_DIRECTION_OUT; - SkyWalkerDebugPrint(EXTREME_LEVEL,("Write Operation\n")); - pPipeInformation = &pDevice->WritePipe; - } - - DbgPrint("Pipe Information = %p\n",pPipeInformation); - - //the transfer request is for TransferLength. - //we can perform a max of Packet Size - //in each stage. - if(ulTransferLength > MAX_BULK_PACKET_SIZE) - { - ulStageTransferLength = MAX_BULK_PACKET_SIZE; - } - else - { - ulStageTransferLength = ulTransferLength; - } - - //Allocate IRP for the USB Transfer - pUsbIoRequestPacket = IoAllocateIrp(pKSDeviceObject->NextDeviceObject->StackSize, - FALSE); - if(!IS_VALID(pUsbIoRequestPacket)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Allocate Memory for the USB Irp\n")); - ntReadWriteStatus = STATUS_INSUFFICIENT_RESOURCES; - goto FinishDeviceReadWrite; - } - - ULONG ulStreamOffset = PACKET_PER_FRAME * ulStreamIndex; - pDevice->pUsbStreamIrp[ulPacketIndex + ulStreamOffset] = pUsbIoRequestPacket; - - - pUSBRequestBlock = (PURB)ExAllocatePoolWithTag( NonPagedPool, - sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER), - USB_MEMORY_TAG); - - if(!IS_VALID(pUSBRequestBlock)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Allocate Memory for the Bulk / Interrupt URB\n")); - ntReadWriteStatus = STATUS_INSUFFICIENT_RESOURCES; - goto FinishDeviceReadWrite; - } - - RtlZeroMemory(pUSBRequestBlock, sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER)); - - UsbBuildInterruptOrBulkTransferRequest( - pUSBRequestBlock, - sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER), - pPipeInformation->PipeHandle, - pucTransferBuffer, - NULL, - ulStageTransferLength, - ulURBFlags, - NULL); - - pReadWriteContext = (PBULKUSB_RW_CONTEXT)ExAllocatePoolWithTag( NonPagedPool, - sizeof(BULKUSB_RW_CONTEXT), - USB_MEMORY_TAG); - - if(!IS_VALID(pReadWriteContext)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Allocate Memory for the Read Write Context\n")); - ntReadWriteStatus = STATUS_INSUFFICIENT_RESOURCES; - goto FinishDeviceReadWrite; - } - - //set BULKUSB_RW_CONTEXT parameters. - pReadWriteContext->pUSBRequestBlock = pUSBRequestBlock; - pReadWriteContext->pTransferBuffer = pucTransferBuffer + ulStageTransferLength; - pReadWriteContext->ulRemainingByteTransfer = ulTransferLength - ulStageTransferLength ; - pReadWriteContext->ulCompletedByteTransfer = 0L; - pReadWriteContext->pDevice = pDevice; - pReadWriteContext->ulStreamIndex = ulStreamIndex; - - //use the original read/write irp as an internal device control irp - pNextStackLocation = IoGetNextIrpStackLocation(pUsbIoRequestPacket); - pNextStackLocation->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; - pNextStackLocation->Parameters.Others.Argument1 = (PVOID)pUSBRequestBlock ; - pNextStackLocation->Parameters.DeviceIoControl.IoControlCode = - IOCTL_INTERNAL_USB_SUBMIT_URB; - - IoSetCompletionRoutine(pUsbIoRequestPacket, - (PIO_COMPLETION_ROUTINE)UsbReadWriteCompletion, - pReadWriteContext, - TRUE, - TRUE, - TRUE); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("USB Transfer Details for Stream %lu Packet %lu\n", - ulStreamIndex, - ulPacketIndex)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("ulStageTransferLength = %lu\n",ulStageTransferLength)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulCompletedByteTransfer = %lu\n",pReadWriteContext->ulCompletedByteTransfer)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulRemainingByteTransfer = %lu\n",pReadWriteContext->ulRemainingByteTransfer)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("bRead = %02d\n",bRead)); - - //since we return STATUS_PENDING call IoMarkIrpPending. - //This is the boiler plate code. - //This may cause extra overhead of an APC for the Irp completion - //but this is the correct thing to do. - - IoMarkIrpPending(pUsbIoRequestPacket); - IncrementPendingIoCount(pDevice); - - ntReadWriteStatus = IoCallDriver( pKSDeviceObject->NextDeviceObject, - pUsbIoRequestPacket); - - if(!NT_SUCCESS(ntReadWriteStatus)) - { - - SkyWalkerDebugPrint(ENTRY_LEVEL, ("IoCallDriver Failed with status %X\n", ntReadWriteStatus)); - - //if the device was yanked out, then the pipeInformation - //field is invalid. - //similarly if the request was cancelled, then we need not - //invoked reset pipe/device. - KIRQL CurrentIrql = KeGetCurrentIrql(); - - if((ntReadWriteStatus != STATUS_CANCELLED) && - (ntReadWriteStatus != STATUS_DEVICE_NOT_CONNECTED)) - { - - if(CurrentIrql < DISPATCH_LEVEL) - { - ntReadWriteStatus = ResetUsbPipe(pKSDeviceObject,pPipeInformation); - if(!NT_SUCCESS(ntReadWriteStatus)) - { - - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Reset USB Pipe Failed\n")); - - ntReadWriteStatus = ResetUsbDevice(pKSDeviceObject); - } - } - } - else - { - - SkyWalkerDebugPrint(ENTRY_LEVEL, ("ntReadWriteStatus is STATUS_CANCELLED or " - "STATUS_DEVICE_NOT_CONNECTED\n")); - } - //Freeing up the resources allocated in this routine - goto FinishDeviceReadWrite; - } - - PrintFunctionExit(__FUNCTION__,STATUS_PENDING); - - return STATUS_PENDING; - -FinishDeviceReadWrite: - - if(IS_VALID(pUSBRequestBlock)) - { - ExFreePoolWithTag(pUSBRequestBlock,USB_MEMORY_TAG); - pUSBRequestBlock = NULL; - } - if(IS_VALID(pReadWriteContext)) - { - ExFreePoolWithTag(pReadWriteContext,USB_MEMORY_TAG); - pReadWriteContext = NULL; - } - if(IS_VALID(pUsbIoRequestPacket)) - { - IoFreeIrp(pUsbIoRequestPacket); - pUsbIoRequestPacket = NULL; - } - - PrintFunctionExit(__FUNCTION__,ntReadWriteStatus); - - return ntReadWriteStatus; -} - -/***************************************************************************** - Function : UsbReadWriteCompletion - Description : This is the completion routine for reads/writes - If the irp completes with success, we check if we - need to recirculate this irp for another stage of - transfer. In this case return STATUS_MORE_PROCESSING_REQUIRED. - if the irp completes in error, free all memory allocs and - return the status. - IN PARAM : Pointer to Device Object - Io Request Packet - Context - OUT PARAM : Status of Read / Write request Completion - STATUS_MORE_PROCESSING_REQUIRED always - PreCondition : NONE - PostCondtion : On Success Read/ Write Request Completed - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS UsbReadWriteCompletion( - IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket, - IN PVOID pContext - ) -{ - ULONG ulStageTransferLength = 0; - NTSTATUS ntReadWriteCompleteStatus = pIoRequestPacket->IoStatus.Status; - PIO_STACK_LOCATION pNextStackLocation; - PBULKUSB_RW_CONTEXT pReadWriteContext = (PBULKUSB_RW_CONTEXT)pContext; - CSkyWalker1Device * pDevice = pReadWriteContext->pDevice; - ULONG ulStreamIndex = 0L; - PrintFunctionEntry(__FUNCTION__); - - //successfully performed a stageLength of transfer. - //check if we need to recirculate the irp. - if(NT_SUCCESS(ntReadWriteCompleteStatus)) - { - if(pReadWriteContext) - { - pReadWriteContext->ulCompletedByteTransfer += - pReadWriteContext->pUSBRequestBlock->UrbBulkOrInterruptTransfer.TransferBufferLength; - - if((pReadWriteContext->ulRemainingByteTransfer)&& (!pIoRequestPacket->Cancel)) - { - //another stage transfer - SkyWalkerDebugPrint(EXTREME_LEVEL, ("Another stage transfer...\n")); - - //the transfer request is for TransferLength. - //we can perform a max of MAX_BULK_PACKET_SIZE - //in each stage. - if(pReadWriteContext->ulRemainingByteTransfer > MAX_BULK_PACKET_SIZE) - { - ulStageTransferLength = MAX_BULK_PACKET_SIZE; - } - else - { - ulStageTransferLength = pReadWriteContext->ulRemainingByteTransfer; - } - - //Reinitialize the urb - pReadWriteContext->pUSBRequestBlock->UrbBulkOrInterruptTransfer.TransferBufferLength - = ulStageTransferLength; - - pReadWriteContext->pUSBRequestBlock->UrbBulkOrInterruptTransfer.TransferBuffer - = pReadWriteContext->pTransferBuffer; - - pReadWriteContext->pTransferBuffer += ulStageTransferLength; - pReadWriteContext->ulRemainingByteTransfer -= ulStageTransferLength ; - - //use the original read/write irp as an internal device control irp - pNextStackLocation = IoGetNextIrpStackLocation(pIoRequestPacket); - pNextStackLocation->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; - pNextStackLocation->Parameters.Others.Argument1 = (PVOID)pReadWriteContext->pUSBRequestBlock ; - pNextStackLocation->Parameters.DeviceIoControl.IoControlCode = - IOCTL_INTERNAL_USB_SUBMIT_URB; - - IoSetCompletionRoutine(pIoRequestPacket, - (PIO_COMPLETION_ROUTINE)UsbReadWriteCompletion, - pReadWriteContext, - TRUE, - TRUE, - TRUE); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("USB Transfer Details for Stream %lu\n", - pReadWriteContext->ulStreamIndex)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("ulStageTransferLength = %lu\n",ulStageTransferLength)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulCompletedByteTransfer = %lu\n",pReadWriteContext->ulCompletedByteTransfer)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulRemainingByteTransfer = %lu\n",pReadWriteContext->ulRemainingByteTransfer)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pIoRequestPacket = 0x%p\n",pIoRequestPacket)); - ntReadWriteCompleteStatus = IoCallDriver( pDevice->m_pKSDevice->NextDeviceObject, - pIoRequestPacket); - - return STATUS_MORE_PROCESSING_REQUIRED; - } - else - { - - InterlockedExchangeAdd((LONG*)&pDevice->m_NumberOfBytesRead[pReadWriteContext->ulStreamIndex], - pReadWriteContext->ulCompletedByteTransfer); - - ulStreamIndex = pReadWriteContext->ulStreamIndex; - - //This is the last transfer - //SkyWalkerDebugPrint(ENTRY_LEVEL,("Valid Synthesis Buffer\n")); - // //This is not needed as anyways the IRP is going to Free soon - //pIoRequestPacket->IoStatus.Information = pReadWriteContext->ulCompletedByteTransfer; - ////Set the Frame Read Event here - // - //if(pReadWriteContext->ulCompletedByteTransfer == pDevice->m_SampleSize) - //{ - // pDevice->m_SynthesisDataValid = 1; - //} - - } - } - } - else - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("ReadWriteCompletion Failed \n")); - } - - if(pReadWriteContext) - { - //Dump pReadWriteContext - SkyWalkerDebugPrint(EXTREME_LEVEL,("Completed Stream = %lu",pReadWriteContext->ulStreamIndex)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->pUSBRequestBlock = 0x%p\n", - pReadWriteContext->pUSBRequestBlock)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulRemainingByteTransfer = %lu\n", - pReadWriteContext->ulRemainingByteTransfer)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulCompletedByteTransfer = %lu\n", - pReadWriteContext->ulCompletedByteTransfer)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->pDevice = 0x%p\n", - pReadWriteContext->pDevice)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("Actual Byte Transfer = %lu", - pReadWriteContext->pUSBRequestBlock-> - UrbBulkOrInterruptTransfer.TransferBufferLength)); - - - DecrementPendingIoCount(pDevice); - - if(IS_VALID(pReadWriteContext->pUSBRequestBlock)) - { - ExFreePoolWithTag(pReadWriteContext->pUSBRequestBlock,USB_MEMORY_TAG); - pReadWriteContext->pUSBRequestBlock = NULL; - } - - if(IS_VALID(pReadWriteContext)) - { - ExFreePoolWithTag(pReadWriteContext,USB_MEMORY_TAG); - pReadWriteContext = NULL; - } - - if(IS_VALID(pIoRequestPacket)) - { - IoFreeIrp(pIoRequestPacket); - pIoRequestPacket = NULL; - } - - } - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Calling ProcessStream \n")); - //TS is read, Process the stream - pDevice->ProcessStream(ulStreamIndex); - - PrintFunctionExit(__FUNCTION__,STATUS_MORE_PROCESSING_REQUIRED); - - //This is the only status that can be returned from the Asynchronous - //IRP created by Driver - return STATUS_MORE_PROCESSING_REQUIRED; -} -/***************************************************************************** - Function : ControlUsbDevice - Description : This Function is used to send the Vendor requests to the Device - IN PARAM : Pointer to Device Object - Vendor Request - Value corresponding to Request - Index for the Request (Used with Request) - Transfer Buffer for Read/Write - size of the Transfer Buffer - True if Read from else False for Write to device - OUT PARAM : Status of Vendor Request Execution - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Vendor Request Command Executed - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS ControlUsbDevice( IN PKSDEVICE pKSDeviceObject, - IN UCHAR ucRequest, - IN USHORT usValue, - IN USHORT usIndex, - IN PUCHAR pucTransferBuffer, - IN ULONG ulTransferLength, - IN BOOLEAN bRead) -{ - NTSTATUS ntControlStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; - PURB pUSBRequestBlock = NULL; - ULONG ulURBFlags = 0L; - PrintFunctionEntry(__FUNCTION__); - - if(pDevice->UsbDeviceState != Working) - { - - SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid device state\n")); - ntControlStatus = STATUS_INVALID_DEVICE_STATE; - goto ExitControlDevice; - } - ulURBFlags = USBD_SHORT_TRANSFER_OK; - if(bRead) - { - ulURBFlags |= USBD_TRANSFER_DIRECTION_IN; //Requst data from Device - SkyWalkerDebugPrint(EXTREME_LEVEL,("Read Operation\n")); - } - else - { - ulURBFlags |= USBD_TRANSFER_DIRECTION_OUT; - SkyWalkerDebugPrint(EXTREME_LEVEL,("Write Operation\n")); - } - - pUSBRequestBlock = (PURB)ExAllocatePoolWithTag( NonPagedPool, - sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST), - USB_MEMORY_TAG); - - if(!IS_VALID(pUSBRequestBlock)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Allocate Memory for the Vendor URB\n")); - ntControlStatus = STATUS_INSUFFICIENT_RESOURCES; - goto ExitControlDevice; - } - - UsbBuildVendorRequest( - pUSBRequestBlock,//Pointer to an URB that - //is to be formatted as a vendor or class - //request. - URB_FUNCTION_VENDOR_DEVICE, //Function - sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST), //Length of URB in Bytes - ulURBFlags, //Zero, One or Combination of - //USBD_TRANSFER_DIRECTION_IN & USBD_SHORT_TRANSFER_OK - 0, //Reserved - ucRequest, //USB/Vendor Specific Request Code - usValue, //Value Specific to Request - usIndex, //Device Defined identifier else Zero - pucTransferBuffer,//Pointer to Resident Buffer for Transfer or NULL - NULL, //Pointer to MDL for Transfer or NULL - ulTransferLength,//Length in Bytes of Buffer specified - NULL //NULL Always - ); - - - ntControlStatus = SendURBToBusDriver(pKSDeviceObject,pUSBRequestBlock); - - if(!NT_SUCCESS(ntControlStatus)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Sending Vendor Request Failed\n")); - } - - ExFreePool(pUSBRequestBlock); - -ExitControlDevice: - - PrintFunctionExit(__FUNCTION__,ntControlStatus); - return ntControlStatus; -} - -/***************************************************************************** - Function : SendURBToBusDriver - Description : Function to used to Send the URB to the USB Bus Driver (USBD.sys) - IN PARAM : Pointer to Device Object to which URB is to be sent - Pointer to the URB to be sent - OUT PARAM : Status of the URB Send operation - STATUS_SUCCESS on Successful execution - STATUS_INVALID_PARAMETER in case of Error - STATUS_INSUFFICIENT_RESOURCES in case IRP could not be created - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success URB sent to the Bus Driver - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS SendURBToBusDriver(IN PKSDEVICE pKSDeviceObject, - IN PURB pUSBRequestBlock ) -{ - NTSTATUS ntIrpProcessingStatus = STATUS_SUCCESS; - USBD_STATUS UsbStatus; - PIRP pIoRequestPacket = NULL; - KEVENT kIrpCompleted; - IO_STATUS_BLOCK IoStatusBlock; - PIO_STACK_LOCATION pNextIoStackLocation = NULL; - PDEVICE_OBJECT pRootDeviceObject = pKSDeviceObject->NextDeviceObject; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; - - PrintFunctionEntry(__FUNCTION__); - - if((!IS_VALID(pRootDeviceObject)) || (!(IS_VALID(pUSBRequestBlock)))) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("pRootDeviceObject = 0x%p, pUSBRequestBlock = 0x%p\n",pRootDeviceObject,pUSBRequestBlock)); - ntIrpProcessingStatus = STATUS_INVALID_PARAMETER; - goto CompleteURBSend; - } - //Initialize Kernel Event which should be trigerred after completion - //of the Device IO Control request - KeInitializeEvent(&kIrpCompleted, //PKEVENT - NotificationEvent, //Type - FALSE); //State - - //Create Internal Device Io Control Request - pIoRequestPacket = IoBuildDeviceIoControlRequest( IOCTL_INTERNAL_USB_SUBMIT_URB , - pRootDeviceObject, //Device to which the request to be sent - NULL, - 0, - NULL, - 0, - TRUE, //TRUE for IRM_MJ_INTERNAL_DEVICE_CONTROL, IRP_MJ_SCSI - &kIrpCompleted, //Event should be trigerred when the IRP completes - &IoStatusBlock); //The Status Block should be set when the Io request Completes - - - //If could not create the IRP return with the INSUFFICIENT RESOURCES - if(pIoRequestPacket == NULL) - { - ntIrpProcessingStatus = STATUS_INSUFFICIENT_RESOURCES; - goto CompleteURBSend; - } - - pNextIoStackLocation = IoGetNextIrpStackLocation(pIoRequestPacket); - pNextIoStackLocation->Parameters.Others.Argument1 = pUSBRequestBlock; - - IncrementPendingIoCount(pDevice); - - //Call the Next Driver - ntIrpProcessingStatus = IoCallDriver(pRootDeviceObject,pIoRequestPacket); - if(ntIrpProcessingStatus == STATUS_PENDING) - { - LARGE_INTEGER Timeout; - Timeout.QuadPart = (LONGLONG) 2 /*sec*/* 1000 /*msec*/ * 1000 /*usec*/ * (-10)/*Conv. Factor*/; - //for(int nRetry = 0; ((nRetry < 3) && (ntIrpProcessingStatus != STATUS_SUCCESS)) ; nRetry++) - { - //IRP is yet to be processed thus STATUS_PENDING is returned from the Lower Device Driver (USBD.sys) - ntIrpProcessingStatus = KeWaitForSingleObject(&kIrpCompleted, //PKEVENT - Executive, //Wait Reason has to be Executive - KernelMode, //Must be kernel mode so - //that Stack will not Paged out - FALSE, //No Alert - NULL//&Timeout //Wait for 2 sec max - ); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("KeWaitForSingleObject returned with status = %s(0x%X)\n", - NTStatusToString(ntIrpProcessingStatus),ntIrpProcessingStatus)); - //SkyWalkerDebugPrint(EXTREME_LEVEL,("nRetry = %d\n",nRetry)); - } - - ntIrpProcessingStatus = IoStatusBlock.Status; - UsbStatus = URB_STATUS(pUSBRequestBlock); - SkyWalkerDebugPrint(EXTREME_LEVEL,("URB STATUS = 0x%X\n",UsbStatus)); - } - - DecrementPendingIoCount(pDevice); - -CompleteURBSend: - - PrintFunctionExit(__FUNCTION__,ntIrpProcessingStatus); - - return ntIrpProcessingStatus; - -} - -/***************************************************************************** - Function : QueryRemoveUsbDevice - Description : Function to used to Service PnP IRPs of Minor Type - IRP_MN_QUERY_REMOVE_DEVICE - IN PARAM : Pointer to Device Object whose Remove Query has come - Device Remove Query Irp with Minor Code IRP_MN_QUERY_REMOVE_DEVICE - OUT PARAM : Status of the Device Remove Query Processing - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Device can be removed is conveyed to the caller - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS QueryRemoveUsbDevice( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - KIRQL kOldIrql; - NTSTATUS ntQueryStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; - - PrintFunctionEntry(__FUNCTION__); - - // - //If we can allow removal of the device, we should set the QueueState - //to HoldRequests so further requests will be queued. This is required - //so that we can process queued up requests in cancel-remove just in - //case somebody else in the stack fails the query-remove. - KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); - - SET_NEW_PNP_STATE(pDevice, PendingRemove); - pDevice->QueueState = HoldRequests; - - KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); - - //wait for the existing ones to be finished. - //first, decrement this operation - DecrementPendingIoCount(pDevice); - - KeWaitForSingleObject(&pDevice->EvDeviceStopOk, - Executive, - KernelMode, - FALSE, - NULL); - - PrintFunctionExit(__FUNCTION__,ntQueryStatus); - - return ntQueryStatus; - -} - -/***************************************************************************** - Function : CancelRemoveUsbDevice - Description : Function to used to Service PnP IRPs of Minor Type - IRP_MN_CANCEL_REMOVE_DEVICE - IN PARAM : Pointer to Device Object whose Remove request has - been cancelled - Device Remove Cancel Irp with Minor Code - IRP_MN_CANCEL_REMOVE_DEVICE - OUT PARAM : Status of the Device Remove Cancel Irp Processing - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Device Remove cancel request is processed - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS CancelRemoveUsbDevice(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - KIRQL kOldIrql; - KEVENT Evevent; - NTSTATUS ntStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; - - PrintFunctionEntry(__FUNCTION__); - - //We need to reset the QueueState flag to ProcessRequest, - //since the device resume its normal activities. - // - //First check to see whether you have received cancel-stop - //without first receiving a query-stop. This could happen if someone - //above us fails a query-stop and passes down the subsequent - //cancel-stop. - if(pDevice->UsbDeviceState == PendingRemove ) - { - ntStatus = PassDownIRPAndWaitForCompletion(pKSDeviceObject->NextDeviceObject, - pIoRequestPacket, - true); - if(NT_SUCCESS(ntStatus)) - { - - KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); - - RESTORE_PREVIOUS_PNP_STATE(pDevice); - pDevice->QueueState = AllowRequests; - - KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); - - //ProcessQueuedRequests(deviceExtension); - } - - } - else - { - - /* spurious cancel Remove - - If the device is already started when the driver receives - this IRP, the driver simply sets status to success and passes - the IRP to the next driver. For such a cancel-remove IRP, a - function driver need not set a completion routine. The device - may not be in the remove-pending state, because, for example, - the driver failed the previous IRP_MN_QUERY_REMOVE_DEVICE.*/ - - } - - PrintFunctionExit(__FUNCTION__,ntStatus); - - return ntStatus; - -} - -/***************************************************************************** - Function : SurpriseUsbDeviceRemoval - Description : Function to used to Service PnP IRPs of Minor Type - IRP_MN_SURPRISE_REMOVAL - IN PARAM : Pointer to Device Object which is removed - surprisingly - Device Remove Cancel Irp with Minor Code - IRP_MN_SURPRISE_REMOVAL - OUT PARAM : Status of the Spurious Device Removal Processing - STATUS_SUCCESS Always - PreCondition : NONE - PostCondtion : On Success Surprised Device Remove is processed - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS SurpriseUsbDeviceRemoval(IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - KIRQL kOldIrql; - KEVENT Evevent; - NTSTATUS ntStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; - - PrintFunctionEntry(__FUNCTION__); - - // - //1. fail pending requests - //2. return device and memory resources - //3. disable interfaces - // - - //if(deviceExtension->WaitWakeEnable) { - // - // CancelWaitWake(deviceExtension); - //} - - - //if(WinXpOrBetter == deviceExtension->WdmVersion) { - - // if(deviceExtension->SSEnable) { - - // // - // //Cancel the timer so that the DPCs are no longer fired. - // //we do not need DPCs because the device has been surprise - // //removed - // // - // - // KeCancelTimer(&deviceExtension->Timer); - - // deviceExtension->SSEnable = 0; - - // // - // //make sure that if a DPC was fired before we called cancel timer, - // //then the DPC and work-time have run to their completion - // // - // KeWaitForSingleObject(&deviceExtension->NoDpcWorkItemPendingEvent, - // Executive, - // KernelMode, - // FALSE, - // NULL); - - // // - // //make sure that the selective suspend request has been completed. - // // - // KeWaitForSingleObject(&deviceExtension->NoIdleReqPendEvent, - // Executive, - // KernelMode, - // FALSE, - // NULL); - // } - //} - - KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); - - SET_NEW_PNP_STATE(pDevice, SurpriseRemoved); - pDevice->QueueState = FailRequests; - - KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); - - //ProcessQueuedRequests(deviceExtension); - - AbortUsbPipes(pKSDeviceObject); - - PrintFunctionExit(__FUNCTION__,ntStatus); - - return ntStatus; -} - -/***************************************************************************** - Function : AbortUsbPipes - Description : This function sends an abort pipe request for pipes. - IN PARAM : Pointer to Device Object - OUT PARAM : Status of the Pipe Abort - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Pipe are aborted - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS AbortUsbPipes(IN PKSDEVICE pKSDeviceObject) -{ - PURB pUsbRequestBlock = NULL; - ULONG ulPipeIndex = 0; - NTSTATUS ntStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; - PUSBD_PIPE_INFORMATION pPipeInformation = &pDevice->ReadPipe; - - PrintFunctionEntry(__FUNCTION__); - - for(ulPipeIndex = 0; ulPipeIndex < 2; ulPipeIndex++) - { - //if(pPipeInformation->PipeOpen) - { - - SkyWalkerDebugPrint(EXTREME_LEVEL, ("Aborting Pipe 0x%X\n",pPipeInformation->EndpointAddress)); - - pUsbRequestBlock = (PURB) ExAllocatePoolWithTag( NonPagedPool, - sizeof(struct _URB_PIPE_REQUEST), - USB_MEMORY_TAG); - - if(pUsbRequestBlock) - { - - pUsbRequestBlock->UrbHeader.Length = sizeof(struct _URB_PIPE_REQUEST); - pUsbRequestBlock->UrbHeader.Function = URB_FUNCTION_ABORT_PIPE; - pUsbRequestBlock->UrbPipeRequest.PipeHandle = pPipeInformation->PipeHandle; - - ntStatus = SendURBToBusDriver(pKSDeviceObject,pUsbRequestBlock); - - ExFreePool(pUsbRequestBlock); - } - else - { - - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Allocate memory for URB during Pipe Abort\n")); - ntStatus = STATUS_INSUFFICIENT_RESOURCES; - goto FinishAbortPipe; - } - - if(NT_SUCCESS(ntStatus)) - { - //pPipeInformation->PipeOpen = FALSE; - } - pPipeInformation = &pDevice->WritePipe; - } - } - -FinishAbortPipe: - - PrintFunctionExit(__FUNCTION__,ntStatus); - - return ntStatus; -} - -/***************************************************************************** - Function : RemoveUsbDevice - Description : Function to used to Service PnP IRPs of Minor Type - IRP_MN_REMOVE_DEVICE - IN PARAM : Pointer to Device Object whose remove - request has come - Device Remove Cancel Irp with Minor Code - IRP_MN_REMOVE_DEVICE - OUT PARAM : Status of the Device Removal - STATUS_SUCCESS Always - PreCondition : NONE - PostCondtion : On Success Device Remove request is processed - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS RemoveUsbDevice( IN PKSDEVICE pKSDeviceObject, - IN PIRP pIoRequestPacket) -{ - KIRQL kOldIrql; - KEVENT Evevent; - NTSTATUS ntStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; - ULONG ulRequestCount = 0L; - PrintFunctionEntry(__FUNCTION__); - - // - //The Plug & Play system has dictated the removal of this device. We - //have no choice but to detach and delete the device object. - //(If we wanted to express an interest in preventing this removal, - //we should have failed the query remove IRP). - // - - if(pDevice->UsbDeviceState != SurpriseRemoved ) - { - - // - //we are here after QUERY_REMOVE - // - - KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); - - pDevice->QueueState = FailRequests; - - KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); - - //if(deviceExtension->WaitWakeEnable) { - // - // CancelWaitWake(deviceExtension); - //} - - //if(WinXpOrBetter == deviceExtension->WdmVersion) { - - // if(deviceExtension->SSEnable) { - - // // - // //Cancel the timer so that the DPCs are no longer fired. - // //we do not need DPCs because the device has been removed - // // - // KeCancelTimer(&deviceExtension->Timer); - - // deviceExtension->SSEnable = 0; - - // // - // //make sure that if a DPC was fired before we called cancel timer, - // //then the DPC and work-time have run to their completion - // // - // KeWaitForSingleObject(&deviceExtension->NoDpcWorkItemPendingEvent, - // Executive, - // KernelMode, - // FALSE, - // NULL); - - // // - // //make sure that the selective suspend request has been completed. - // // - // KeWaitForSingleObject(&deviceExtension->NoIdleReqPendEvent, - // Executive, - // KernelMode, - // FALSE, - // NULL); - // } - //} - - //ProcessQueuedRequests(deviceExtension); - - AbortUsbPipes(pKSDeviceObject); - } - - KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); - - SET_NEW_PNP_STATE(pDevice, Removed); - - KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); - - // - //need 2 decrements - // - - ulRequestCount = DecrementPendingIoCount(pDevice); - ulRequestCount = DecrementPendingIoCount(pDevice); - - KeWaitForSingleObject(&pDevice->EvDeviceRemoveOk, - Executive, - KernelMode, - FALSE, - NULL); - - PrintFunctionExit(__FUNCTION__,ntStatus); - - return ntStatus; -} - -/***************************************************************************** - Function : ResetUsbPipe - Description : This routine synchronously submits a URB_FUNCTION_RESET_PIPE - request down the stack. - IN PARAM : Pointer to Device Object - Pipe to be reseted - OUT PARAM : Status of the Reset Usb Pipe Request - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Usb Pipe is reseted - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS ResetUsbPipe( IN PKSDEVICE pKSDeviceObject, - IN PUSBD_PIPE_INFORMATION pPipeInformation) -{ - PURB pUsbRequestBlock = NULL; - NTSTATUS ntResetStatus = STATUS_SUCCESS; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; - - PrintFunctionEntry(__FUNCTION__); - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Pipe to Reset = 0x%X",pPipeInformation->EndpointAddress)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("Pipe Handle = 0x%p",pPipeInformation->PipeHandle)); - - pUsbRequestBlock = (PURB) ExAllocatePoolWithTag( NonPagedPool, - sizeof(struct _URB_PIPE_REQUEST), - USB_MEMORY_TAG); - - if(pUsbRequestBlock) - { - - pUsbRequestBlock->UrbHeader.Length = (USHORT) sizeof(struct _URB_PIPE_REQUEST); - pUsbRequestBlock->UrbHeader.Function = URB_FUNCTION_RESET_PIPE; - pUsbRequestBlock->UrbPipeRequest.PipeHandle = pPipeInformation->PipeHandle; - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Sending the Pipe Reset Command\n")); - ntResetStatus = SendURBToBusDriver(pKSDeviceObject, pUsbRequestBlock); - - ExFreePool(pUsbRequestBlock); - } - else - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Failed to allocate URB Memory during Pipe Reset\n")); - ntResetStatus = STATUS_INSUFFICIENT_RESOURCES; - } - - if(NT_SUCCESS(ntResetStatus)) - { - SkyWalkerDebugPrint(EXTREME_LEVEL, ("Successfully Reseted the Usb Pipe\n")); - ntResetStatus = STATUS_SUCCESS; - } - else - { - SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to reset the Usb Pipe\n")); - } - - PrintFunctionExit(__FUNCTION__,ntResetStatus); - - return ntResetStatus; -} - -/***************************************************************************** - Function : ResetUsbDevice - Description : This routine checks the current status of the Usb Port - If Device is connected but not enabled then it resets - the Usb Port - IN PARAM : Pointer to Device Object - OUT PARAM : Status of the Reset Usb Device Request - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Usb Device is reseted - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS ResetUsbDevice(IN PKSDEVICE pKSDeviceObject) -{ - NTSTATUS ntResetStatus = STATUS_SUCCESS; - ULONG ulPortStatus = 0; - - PrintFunctionEntry(__FUNCTION__); - - ntResetStatus = GetUsbPortStatus(pKSDeviceObject, &ulPortStatus); - - if((NT_SUCCESS(ntResetStatus)) - && (!(ulPortStatus & USBD_PORT_ENABLED)) - && (ulPortStatus & USBD_PORT_CONNECTED)) - { - - SkyWalkerDebugPrint(ENTRY_LEVEL,("Resetting the Parent Port\n")); - ntResetStatus = ResetUsbParentPort(pKSDeviceObject); - } - - PrintFunctionExit(__FUNCTION__,ntResetStatus); - - return ntResetStatus; -} - -/***************************************************************************** - Function : GetUsbPortStatus - Description : This routine retrives the Usb Port Status as Enabled / Disabled - and Connected / Not Connected - IN PARAM : Pointer to Device Object - Port Status - OUT PARAM : Status of the USB Port Status Request - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Usb Port status is retrived - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS GetUsbPortStatus( IN PKSDEVICE pKSDeviceObject, - IN OUT PULONG pulPortStatus) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - KEVENT EvRequestComplete; - PIRP pIoRequestPacket = NULL; - IO_STATUS_BLOCK IoStatus; - PIO_STACK_LOCATION pNextStackLocation = NULL; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; - - PrintFunctionEntry(__FUNCTION__); - - *pulPortStatus = 0; - - //Initialize the Event to be triggerred after completion of Device Io Control request - KeInitializeEvent(&EvRequestComplete, NotificationEvent, FALSE); - - pIoRequestPacket = IoBuildDeviceIoControlRequest( - IOCTL_INTERNAL_USB_GET_PORT_STATUS, - pKSDeviceObject->NextDeviceObject, - NULL, - 0, - NULL, - 0, - TRUE, - &EvRequestComplete, - &IoStatus); - - if(!IS_VALID(pIoRequestPacket)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Memory Allocation during Get Port Status Failed\n")); - goto FinishGetPortStatus; - return STATUS_INSUFFICIENT_RESOURCES; - } - - pNextStackLocation = IoGetNextIrpStackLocation(pIoRequestPacket); - - pNextStackLocation->Parameters.Others.Argument1 = pulPortStatus; - - ntStatus = IoCallDriver(pKSDeviceObject->NextDeviceObject, pIoRequestPacket); - - if(ntStatus == STATUS_PENDING) - { - KeWaitForSingleObject(&EvRequestComplete, Executive, KernelMode, FALSE, NULL); - } - else - { - IoStatus.Status = ntStatus; - } - - ntStatus = IoStatus.Status; - - SkyWalkerDebugPrint(EXTREME_LEVEL,("Port Status = %lu (",*pulPortStatus)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("%s, ",(((*pulPortStatus) & USBD_PORT_ENABLED)? "Enabled" : "Disabled"))); - SkyWalkerDebugPrint(EXTREME_LEVEL,("%s)\n",(((*pulPortStatus) & USBD_PORT_CONNECTED)? "Connected" : "Not Connected"))); - -FinishGetPortStatus: - - PrintFunctionExit(__FUNCTION__,ntStatus); - - return ntStatus; -} - -/***************************************************************************** - Function : ResetUsbParentPort - Description : This routine sends an IOCTL_INTERNAL_USB_RESET_PORT - synchronously down the stack. - IN PARAM : Pointer to Device Object - OUT PARAM : Status of the Reset USB Port Request - STATUS_SUCCESS on Successful execution - else Error from the Bus Driver - PreCondition : NONE - PostCondtion : On Success Usb Port is reseted - Logic : NONE - Assumption : NONE - Revision History: - *****************************************************************************/ -NTSTATUS ResetUsbParentPort( IN PKSDEVICE pKSDeviceObject ) -{ - NTSTATUS ntStatus = STATUS_SUCCESS; - KEVENT EvRequestComplete; - PIRP pIoRequestPacket = NULL; - IO_STATUS_BLOCK IoStatus; - PIO_STACK_LOCATION pNextStackLocation = NULL; - CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; - - PrintFunctionEntry(__FUNCTION__); - - //Initialize the Event to be triggerred after completion of Device Io Control request - KeInitializeEvent(&EvRequestComplete, NotificationEvent, FALSE); - - pIoRequestPacket = IoBuildDeviceIoControlRequest( - IOCTL_INTERNAL_USB_RESET_PORT, - pKSDeviceObject->NextDeviceObject, - NULL, - 0, - NULL, - 0, - TRUE, - &EvRequestComplete, - &IoStatus); - - if(!IS_VALID(pIoRequestPacket)) - { - SkyWalkerDebugPrint(ENTRY_LEVEL,("Memory Allocation during Reset Parent Device Failed\n")); - ntStatus = STATUS_INSUFFICIENT_RESOURCES; - goto FinishResetDevice; - - } - - pNextStackLocation = IoGetNextIrpStackLocation(pIoRequestPacket); - - ntStatus = IoCallDriver(pKSDeviceObject->NextDeviceObject, pIoRequestPacket); - - if(ntStatus == STATUS_PENDING) - { - KeWaitForSingleObject(&EvRequestComplete, Executive, KernelMode, FALSE, NULL); - } - else - { - IoStatus.Status = ntStatus; - } - - ntStatus = IoStatus.Status; - -FinishResetDevice: - - PrintFunctionExit(__FUNCTION__,ntStatus); - - return ntStatus; -} - -//Print Device Descriptor -VOID PrintDeviceDescriptor(IN PUSB_DEVICE_DESCRIPTOR pDeviceDescriptor) -{ - SkyWalkerDebugPrint(INTERMEDIATE_LEVEL, (__FUNCTION__"\n")); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bLength= %02d\n", - pDeviceDescriptor->bLength)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bDescriptorType= %02d\n", - pDeviceDescriptor->bDescriptorType)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bcdUSB= 0x%X\n", - pDeviceDescriptor->bcdUSB)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bDeviceClass= 0x%02X\n", - pDeviceDescriptor->bDeviceClass)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bDeviceSubClass= 0x%02X\n", - pDeviceDescriptor->bDeviceSubClass)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bDeviceProtocol= 0x%02X\n", - pDeviceDescriptor->bDeviceProtocol)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bMaxPacketSize0= %02d\n", - pDeviceDescriptor->bMaxPacketSize0)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->idVendor= 0x%X\n", - pDeviceDescriptor->idVendor)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->idProduct= 0x%X\n", - pDeviceDescriptor->idProduct)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bcdDevice= 0x%X\n", - pDeviceDescriptor->bcdDevice)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->iManufacturer= %02d\n", - pDeviceDescriptor->iManufacturer)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->iProduct= %02d\n", - pDeviceDescriptor->iProduct)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->iSerialNumber= %02d\n", - pDeviceDescriptor->iSerialNumber)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bNumConfigurations= %02d\n", - pDeviceDescriptor->bNumConfigurations)); -} - -//Print Configuration Descriptor -VOID PrintConfigurationDescriptor(IN PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor) -{ - SkyWalkerDebugPrint(INTERMEDIATE_LEVEL, (__FUNCTION__"\n")); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->bLength= %02d\n", - pConfigurationDescriptor->bLength)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->bDescriptorType= %02d\n", - pConfigurationDescriptor->bDescriptorType)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->wTotalLength= %d\n", - pConfigurationDescriptor->wTotalLength)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->bNumInterfaces= %02d\n", - pConfigurationDescriptor->bNumInterfaces)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->bConfigurationValue= %02d\n", - pConfigurationDescriptor->bConfigurationValue)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->iConfiguration= %02d\n", - pConfigurationDescriptor->iConfiguration)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->bmAttributes= 0x%02X\n", - pConfigurationDescriptor->bmAttributes)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->MaxPower= %02d\n", - pConfigurationDescriptor->MaxPower)); - -} -//Print Interface Descriptor -VOID PrintInterfaceDescriptor(IN PUSB_INTERFACE_DESCRIPTOR pInterfaceDescriptor) -{ - SkyWalkerDebugPrint(INTERMEDIATE_LEVEL, (__FUNCTION__"\n")); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bLength= %02d\n", - pInterfaceDescriptor->bLength)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bDescriptorType= %02d\n", - pInterfaceDescriptor->bDescriptorType)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bInterfaceNumber= %02d\n", - pInterfaceDescriptor->bInterfaceNumber)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bAlternateSetting= %02d\n", - pInterfaceDescriptor->bAlternateSetting)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bNumEndpoints= %02d\n", - pInterfaceDescriptor->bNumEndpoints)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bInterfaceClass= 0x%02X\n", - pInterfaceDescriptor->bInterfaceClass)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bInterfaceSubClass= 0x%02X\n", - pInterfaceDescriptor->bInterfaceSubClass)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bInterfaceProtocol= 0x%02X\n", - pInterfaceDescriptor->bInterfaceProtocol)); - SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->iInterface= %02d\n", - pInterfaceDescriptor->iInterface)); - -} - -//Print Pipe Information -VOID PrintPipeInformation(PUSBD_PIPE_INFORMATION pPipeInformation) -{ - SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->PipeType = 0x%X\n", - pPipeInformation->PipeType)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->EndpointAddress = 0x%X\n", - pPipeInformation->EndpointAddress)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->MaximumPacketSize = 0x%X\n", - pPipeInformation->MaximumPacketSize)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->Interval = 0x%X\n", - pPipeInformation->Interval)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->PipeHandle = 0x%p\n", - pPipeInformation->PipeHandle)); - SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->MaximumTransferSize = 0x%X\n", - pPipeInformation->MaximumTransferSize)); - +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1Usb.cpp + Author : + Date : + Purpose : This File Holds all the USB Device access related declarations + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ +/* Include the Library and Other header file */ +//#include +#include "SkyWalker1Main.h" //Header for the Tuner related definitions + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +#define USB_MEMORY_TAG 'MBSU' +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +NTSTATUS ReadandSelectDescriptors( IN PKSDEVICE pKSDeviceObject ); +NTSTATUS ConfigureDevice(IN PKSDEVICE pKSDeviceObject); +NTSTATUS SelectInterfaces( IN PKSDEVICE pKSDeviceObject, + IN PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor); + +NTSTATUS UsbReadWriteCompletion( + IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket, + IN PVOID pContext + ); +NTSTATUS ResetUsbPipe( IN PKSDEVICE pKSDeviceObject, + IN PUSBD_PIPE_INFORMATION pPipeInformation); +NTSTATUS ResetUsbDevice(IN PKSDEVICE pKSDeviceObject); +NTSTATUS GetUsbPortStatus( IN PKSDEVICE pKSDeviceObject, + IN OUT PULONG pulPortStatus); +NTSTATUS ResetUsbParentPort( IN PKSDEVICE pKSDeviceObject ); +NTSTATUS SendURBToBusDriver(IN PKSDEVICE pKSDeviceObject, + IN PURB pUSBRequestBlock ); +NTSTATUS AbortUsbPipes(IN PKSDEVICE pKSDeviceObject); +LONG IncrementPendingIoCount(IN OUT CSkyWalker1Device * pDevice); +LONG DecrementPendingIoCount(IN OUT CSkyWalker1Device * pDevice); + +//Debugging related Functions +VOID PrintDeviceDescriptor( PUSB_DEVICE_DESCRIPTOR pDeviceDescriptor); +VOID PrintConfigurationDescriptor(IN PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor); +VOID PrintInterfaceDescriptor(IN PUSB_INTERFACE_DESCRIPTOR pInterfaceDescriptor); +VOID PrintPipeInformation(PUSBD_PIPE_INFORMATION pPipeInformation); +/* End of Function prototype definitions */ + +/***************************************************************************** + Function : InitializeUsbDevice + Description : Function to used to Initialize the USB Interface of the Tuner + IN PARAM : Pointer to Device Object which needs to be Initialized + Irp to start the device IRP_MN_START + OUT PARAM : Status of the Device Initialization + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Device initialized + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS InitializeUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + NTSTATUS ntInitStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = NULL; + KIRQL kOldIrql; + + PrintFunctionEntry(__FUNCTION__); + + pDevice = reinterpret_cast(pKSDeviceObject->Context); + + //We cannot touch the device (send it any non pnp irps) until a + //start device has been passed down to the lower drivers. + //first pass the Irp down + + ntInitStatus = PassDownIRPAndWaitForCompletion( pKSDeviceObject->NextDeviceObject, + pIoRequestPacket, + TRUE); + if(!NT_SUCCESS(ntInitStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Lower Driver did not start !!!,Stopping Device Start\n")); + goto FinishStartDevice; + } + + //Lower Device Initialized , Start the Device Now + // + //Read the device descriptor, configuration descriptor + //and select the interface descriptors + // + + ntInitStatus = ReadandSelectDescriptors(pKSDeviceObject); + + if(!NT_SUCCESS(ntInitStatus)) + { + + SkyWalkerDebugPrint(ENTRY_LEVEL,("ReadandSelectDescriptors failed\n")); + goto FinishStartDevice; + } + + KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); + + SET_NEW_PNP_STATE(pDevice, Working); + pDevice->QueueState = AllowRequests; + + KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); + +/* + //initialize wait wake outstanding flag to false. + //and issue a wait wake. + + deviceExtension->FlagWWOutstanding = 0; + deviceExtension->FlagWWCancel = 0; + deviceExtension->WaitWakeIrp = NULL; + + if(deviceExtension->WaitWakeEnable) + { + + IssueWaitWake(deviceExtension); + } + + ProcessQueuedRequests(deviceExtension); + +*/ +FinishStartDevice: + PrintFunctionExit(__FUNCTION__,ntInitStatus); + + return ntInitStatus; +} + +/***************************************************************************** + Function : ReadandSelectDescriptors + Description : Function to used to Read Device Descriptor + IN PARAM : Pointer to KS Device Object + OUT PARAM : Status of the Device Descriptor Read + STATUS_SUCCESS on Successful execution + else Error + PreCondition : NONE + PostCondtion : Device Descriptor Read + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS ReadandSelectDescriptors( IN PKSDEVICE pKSDeviceObject ) +{ + URB USBRequestBlock; + NTSTATUS ntStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; + + PrintFunctionEntry(__FUNCTION__); + + //1. Read the device descriptor + + UsbBuildGetDescriptorRequest( + &USBRequestBlock, + (USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST), + USB_DEVICE_DESCRIPTOR_TYPE, + 0, + 0, + &pDevice->USBDeviceDescriptor, + NULL, + sizeof(pDevice->USBDeviceDescriptor), + NULL); + + ntStatus = SendURBToBusDriver( pKSDeviceObject, + &USBRequestBlock); + if (!NT_SUCCESS(ntStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Error Trying to Read Device Descriptor\n")); + goto CompleteReadAndSetup; + } + + PrintDeviceDescriptor(&pDevice->USBDeviceDescriptor); + + //Device Descriptor read successfully thus, Read and select configuration + ntStatus = ConfigureDevice(pKSDeviceObject); + +CompleteReadAndSetup: + + PrintFunctionExit(__FUNCTION__,ntStatus); + return ntStatus; +} + +/***************************************************************************** + Function : ConfigureDevice + Description : This helper routine reads the configuration descriptor + for the device in couple of steps. + IN PARAM : Pointer to KS Device Object + OUT PARAM : Status of the Configuration Descriptor Read + STATUS_SUCCESS on Successful execution + else Error + PreCondition : NONE + PostCondtion : Configuration Descriptor Read + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS ConfigureDevice(IN PKSDEVICE pKSDeviceObject) +{ + URB USBRequestBlock; + NTSTATUS ntConfigureStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; + USB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor; + PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor = NULL; + + PrintFunctionEntry(__FUNCTION__); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Total Number of Configurations = %d\n", pDevice->USBDeviceDescriptor.bNumConfigurations)); + + //Read the first configuration descriptor + //This requires two steps: + //1. Read the fixed sized configuration desciptor (CD) + //2. Read the CD with all embedded interface and endpoint descriptors + + UsbBuildGetDescriptorRequest( &USBRequestBlock, + sizeof(_URB_CONTROL_DESCRIPTOR_REQUEST), + USB_CONFIGURATION_DESCRIPTOR_TYPE, + 0, + 0, + &ConfigurationDescriptor, + NULL, + sizeof(ConfigurationDescriptor), + NULL); + + ntConfigureStatus = SendURBToBusDriver( pKSDeviceObject, + &USBRequestBlock); + if (!NT_SUCCESS(ntConfigureStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Error Trying to Read Fixed Size Configuration Descriptor\n")); + goto CompleteDeviceConfigure; + } + + ULONG ulConfigurationDesciptorSize = ConfigurationDescriptor.wTotalLength; + SkyWalkerDebugPrint(EXTREME_LEVEL, ("Configuration Descriptor Size = %lu \n",ulConfigurationDesciptorSize)); + + pConfigurationDescriptor = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag( NonPagedPool, + ulConfigurationDesciptorSize, + USB_MEMORY_TAG); + if (!pConfigurationDescriptor) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to allocate %lu bytes for Configuration Descriptor\n", ulConfigurationDesciptorSize)); + ntConfigureStatus = STATUS_INSUFFICIENT_RESOURCES; + goto CompleteDeviceConfigure; + } + + UsbBuildGetDescriptorRequest( &USBRequestBlock, + sizeof(_URB_CONTROL_DESCRIPTOR_REQUEST), + USB_CONFIGURATION_DESCRIPTOR_TYPE, + 0, + 0, + pConfigurationDescriptor, + NULL, + ulConfigurationDesciptorSize, + NULL); + + ntConfigureStatus = SendURBToBusDriver( pKSDeviceObject, + &USBRequestBlock); + if (!NT_SUCCESS(ntConfigureStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Error Trying to Read Actual Configuration Descriptor\n")); + goto CompleteDeviceConfigure; + } + + PrintConfigurationDescriptor(&ConfigurationDescriptor); + + ntConfigureStatus = SelectInterfaces(pKSDeviceObject, pConfigurationDescriptor); + +CompleteDeviceConfigure: + if(pConfigurationDescriptor) + { + ExFreePoolWithTag(pConfigurationDescriptor,USB_MEMORY_TAG); + } + PrintFunctionExit(__FUNCTION__,ntConfigureStatus); + return ntConfigureStatus; +} + +/***************************************************************************** + Function : SelectInterfaces + Description : This helper routine selects the configuration + IN PARAM : Pointer to KS Device Object + Configuration Descriptor + OUT PARAM : Status of the Configuration Descriptor Selection + STATUS_SUCCESS on Successful execution + else Error + PreCondition : NONE + PostCondtion : Configuration Descriptor Selection + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS SelectInterfaces( IN PKSDEVICE pKSDeviceObject, + IN PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor) +{ + NTSTATUS ntSelectStatus = STATUS_SUCCESS; + URB USBRequestBlock; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; + PUSB_INTERFACE_DESCRIPTOR pInterfaceDescriptor = NULL; + PUSBD_INTERFACE_INFORMATION pInterfaceInformation = NULL; + LONG lNumberOfInterfaces = pConfigurationDescriptor->bNumInterfaces; + LONG lInterfaceNumber = 0L; + LONG lInterfaceIndex = 0L; + ULONG ulPipeIndex = 0L; + + //The Device needs to be configured by sending a URB that specifies the configuration to use + //if device driver fails to configure the device then the I/O manager subsequently unloads + //the Driver from memory + //Search for the Configuration descriptor in the list of all configuration and obtain a pointer + //to the interface + pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx( + pConfigurationDescriptor, //Address of the Configuration Descriptor Structure + pConfigurationDescriptor, //Adress within the First Structure from where search should begin + -1, + -1, + -1, + -1, + -1); + if (!pInterfaceDescriptor) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("No Interface available for the Device\n")); + ntSelectStatus = STATUS_DEVICE_CONFIGURATION_ERROR; + goto FinishSelectInterface; + } + + //Create a URB that can be sent to the host controller driver (HCD) to set the Device + //in the configured state + USBD_INTERFACE_LIST_ENTRY Interfaces[2] = + { + {pInterfaceDescriptor, NULL}, + {NULL, NULL}, + }; + + PURB pConfigSelectURB = USBD_CreateConfigurationRequestEx(pConfigurationDescriptor, Interfaces); + if (!pConfigSelectURB) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Unable to Create Configuration Request\n")); + ntSelectStatus = STATUS_INSUFFICIENT_RESOURCES; + goto FinishSelectInterface; + } + + //Get the Interface supported by selected configuration + + pInterfaceInformation = &pConfigSelectURB->UrbSelectConfiguration.Interface; + + for(ulPipeIndex=0; ulPipeIndexNumberOfPipes; ulPipeIndex++) + { + //Perform pipe initialization here set the transfer size and any pipe flags we use + //USBD sets the rest of the Interface struct members + + //pInterfaceInformation->Pipes[ulPipeIndex].MaximumTransferSize = USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE; + } + + ntSelectStatus = SendURBToBusDriver(pKSDeviceObject,pConfigSelectURB); + + if (!NT_SUCCESS(ntSelectStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Error Trying to Select Configuration\n")); + goto FinishSelectInterface; + } + + PrintInterfaceDescriptor(pInterfaceDescriptor); + + for(ulPipeIndex=0; ulPipeIndexNumberOfPipes; ulPipeIndex++) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("--------------------------\n")); + PrintPipeInformation(&pInterfaceInformation->Pipes[ulPipeIndex]); + SkyWalkerDebugPrint(EXTREME_LEVEL,("--------------------------\n")); + + //Setting the Pipes + if(pInterfaceInformation->Pipes[ulPipeIndex].EndpointAddress == 0x82) + { + RtlCopyMemory( &pDevice->ReadPipe, + &pInterfaceInformation->Pipes[ulPipeIndex], + sizeof(pDevice->ReadPipe)); + } + else + { + RtlCopyMemory( &pDevice->WritePipe, + &pInterfaceInformation->Pipes[ulPipeIndex], + sizeof(pDevice->WritePipe)); + } + + } +FinishSelectInterface: + + if(pConfigSelectURB) + { + ExFreePool(pConfigSelectURB); + } + + PrintFunctionExit(__FUNCTION__,ntSelectStatus); + return ntSelectStatus; +} + +/***************************************************************************** + Function : QueryStopUsbDevice + Description : Function to used to Service PnP IRPs of Minor Type + IRP_MN_QUERY_STOP_DEVICE + IN PARAM : Pointer to Device Object whose stop query has come + Device Stop Query Irp with Minor Code IRP_MN_QUERY_STOP_DEVICE + OUT PARAM : Status of the Stop Query Processing + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Device can be stopped or not is returned + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS QueryStopUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + KIRQL kOldIrql; + NTSTATUS ntQueryStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; + + PrintFunctionEntry(__FUNCTION__); + + //If we can stop the device, we need to set the QueueState to + //HoldRequests so further requests will be queued. + KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); + + SET_NEW_PNP_STATE(pDevice, PendingStop); + pDevice->QueueState = HoldRequests; + + KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); + + //wait for the existing ones to be finished. + //first, decrement this operation + DecrementPendingIoCount(pDevice); + + KeWaitForSingleObject(&pDevice->EvDeviceStopOk, + Executive, + KernelMode, + FALSE, + NULL); + + PrintFunctionExit(__FUNCTION__,ntQueryStatus); + + return ntQueryStatus; +} + +/***************************************************************************** + Function : DecrementPendingIoCount + Description : This routine decrements the outstanding I/O count + This is typically invoked after the dispatch routine + has finished processing the irp. + IN PARAM : Device Pointer + OUT PARAM : Io Pending Count + PreCondition : NONE + PostCondtion : Pending IO Count is Decremented and same is returned to + the caller + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +LONG DecrementPendingIoCount(IN OUT CSkyWalker1Device * pDevice) +{ + LONG ulResult = 0; + KIRQL kOldIrql; + + PrintFunctionEntry(__FUNCTION__); + + KeAcquireSpinLock(&pDevice->kIoCountLock, &kOldIrql); + + ulResult = InterlockedDecrement((PLONG)&pDevice->ulOutStandingIoCount); + + if(ulResult == 1) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("Device can be Stopped\n")); + KeSetEvent(&pDevice->EvDeviceStopOk, IO_NO_INCREMENT, FALSE); + } + + if(ulResult == 0) + { + SkyWalkerDebugPrint(EXTREME_LEVEL,("Device can be Removed\n")); + KeSetEvent(&pDevice->EvDeviceRemoveOk,IO_NO_INCREMENT, FALSE); + } + + KeReleaseSpinLock(&pDevice->kIoCountLock, kOldIrql); + + SkyWalkerDebugPrint(EXTREME_LEVEL, ("%s::%d\n",__FUNCTION__,ulResult)); + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); + + return ulResult; +} +/***************************************************************************** + Function : IncrementPendingIoCount + Description : This routine increments the outstanding I/O count + This is typically invoked before the dispatch routine + to process new Irp is called + IN PARAM : Device Pointer + OUT PARAM : Io Pending Count + PreCondition : NONE + PostCondtion : Pending IO Count is Incremented and same is returned to + the caller + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +LONG IncrementPendingIoCount(IN OUT CSkyWalker1Device * pDevice) +{ + LONG ulResult = 0; + KIRQL kOldIrql; + + PrintFunctionEntry(__FUNCTION__); + KeAcquireSpinLock(&pDevice->kIoCountLock, &kOldIrql); + + ulResult = InterlockedIncrement((PLONG)&pDevice->ulOutStandingIoCount); + + //when OutStandingIO bumps from 1 to 2, clear the StopEvent + if(ulResult == 2) + { + + KeClearEvent(&pDevice->EvDeviceStopOk); + } + + KeReleaseSpinLock(&pDevice->kIoCountLock, kOldIrql); + + SkyWalkerDebugPrint(EXTREME_LEVEL, ("%s::%d\n",__FUNCTION__,ulResult)); + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); + + return ulResult; +} + +/***************************************************************************** + Function : CancelStopUsbDevice + Description : Function to used to Service PnP IRPs of Minor Type + IRP_MN_CANCEL_STOP_DEVICE + IN PARAM : Pointer to Device Object whose Cancel stop request has come + Device Stop Cancel Irp with Minor Code IRP_MN_CANCEL_STOP_DEVICE + OUT PARAM : Status of the Device Stop Cancel Processing + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Device Stop is cancelled + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CancelStopUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + KIRQL kOldIrql; + KEVENT Evevent; + NTSTATUS ntStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; + + PrintFunctionEntry(__FUNCTION__); + + //First check to see whether you have received cancel-stop + //without first receiving a query-stop. This could happen if someone + //above us fails a query-stop and passes down the subsequent + //cancel-stop. + if(pDevice->UsbDeviceState == PendingStop ) + { + if(NT_SUCCESS(ntStatus)) + { + + KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); + + RESTORE_PREVIOUS_PNP_STATE(pDevice); + pDevice->QueueState = AllowRequests; + + KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); + + //ProcessQueuedRequests(deviceExtension); + } + + } + else + { + + //spurious Irp + // + //If the device is already in an active state when the driver + //receives this IRP, a function driver simply sets status to + //success and passes the IRP to the next driver. For such a + //cancel-stop IRP, a function driver need not set a completion + //routine. + + } + + PrintFunctionExit(__FUNCTION__,ntStatus); + + return ntStatus; +} + +/***************************************************************************** + Function : DeconfigureUsbDevice + Description : This routine is invoked when the device is removed or stopped. + This routine de-configures the usb device. + IN PARAM : Pointer to KS Device Object + OUT PARAM : Status of the Device Deinitialization + STATUS_SUCCESS on Successful execution + else Error + PreCondition : NONE + PostCondtion : USB Device Uninitialized + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS DeconfigureUsbDevice(IN PKSDEVICE pKSDeviceObject) +{ + NTSTATUS ntUnInitStatus = STATUS_SUCCESS; + URB USBRequestBlock; + + PrintFunctionEntry(__FUNCTION__); + + UsbBuildSelectConfigurationRequest(&USBRequestBlock, sizeof(_URB_SELECT_CONFIGURATION), NULL); + + ntUnInitStatus = SendURBToBusDriver(pKSDeviceObject,&USBRequestBlock); + + if (!NT_SUCCESS(ntUnInitStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Error Trying to Deconfigure the Device\n")); + } + + PrintFunctionExit(__FUNCTION__,ntUnInitStatus); + return ntUnInitStatus; + +} + +/***************************************************************************** + Function : StopUsbDevice + Description : This routine is invoked when the device is stopped. + This routine services Irp of minor type IRP_MN_STOP_DEVICE + IN PARAM : Pointer to KS Device Object + STOP DEVICE Irp + OUT PARAM : Status of the Device Stop + STATUS_SUCCESS on Successful execution + else Error + PreCondition : NONE + PostCondtion : USB Device Stopped + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS StopUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + KIRQL kOldIrql; + NTSTATUS ntDeviceStopStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = NULL; + + PrintFunctionEntry(__FUNCTION__); + + //initialize variables + pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; + + + //if(WinXpOrBetter == deviceExtension->WdmVersion) { + + // if(deviceExtension->SSEnable) { + + // // + // //Cancel the timer so that the DPCs are no longer fired. + // //Thus, we are making judicious usage of our resources. + // //we do not need DPCs because the device is stopping. + // //The timers are re-initialized while handling the start + // //device irp. + // // + + // KeCancelTimer(&deviceExtension->Timer); + + // // + // //after the device is stopped, it can be surprise removed. + // //we set this to 0, so that we do not attempt to cancel + // //the timer while handling surprise remove or remove irps. + // //when we get the start device request, this flag will be + // //reinitialized. + // // + // deviceExtension->SSEnable = 0; + + // // + // //make sure that if a DPC was fired before we called cancel timer, + // //then the DPC and work-time have run to their completion + // // + // KeWaitForSingleObject(&deviceExtension->NoDpcWorkItemPendingEvent, + // Executive, + // KernelMode, + // FALSE, + // NULL); + + // // + // //make sure that the selective suspend request has been completed. + // // + // KeWaitForSingleObject(&deviceExtension->NoIdleReqPendEvent, + // Executive, + // KernelMode, + // FALSE, + // NULL); + // } + //} + + // + //after the stop Irp is sent to the lower driver object, + //the driver must not send any more Irps down that touch + //the device until another Start has occurred. + // + + /* if(deviceExtension->WaitWakeEnable) { + + CancelWaitWake(deviceExtension); + }*/ + + KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); + + SET_NEW_PNP_STATE(pDevice, Stopped); + + KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); + + ntDeviceStopStatus = DeconfigureUsbDevice(pKSDeviceObject); + + PrintFunctionExit(__FUNCTION__,ntDeviceStopStatus); + + return ntDeviceStopStatus; +} + +/***************************************************************************** + Function : ReadWriteUsbDevice + Description : Dispatch routine for read and write. + This routine creates a BULKUSB_RW_CONTEXT for a read/write. + This read/write is performed in stages of MAX_BULK_PACKET_SIZE. + once a stage of transfer is complete, then the irp is circulated again, + until the requested length of tranfer is performed. + IN PARAM : Pointer to Device Object + Device Register to/From which Write/ Read is to be performed + Transfer Buffer + Length of the Transfer Buffer + True if Read from else False for Write to device + OUT PARAM : Status of Read / Write request sending to Device + STATUS_PENDING on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Read/ Write Request submitted to Device + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS ReadWriteUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN ULONG ulStreamIndex, + IN ULONG ulPacketIndex, + IN PUCHAR pucTransferBuffer, + IN ULONG ulTransferLength, + IN BOOLEAN bRead) +{ + NTSTATUS ntReadWriteStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; + ULONG ulURBFlags = 0; + ULONG ulStageTransferLength = 0; + PURB pUSBRequestBlock = NULL; + PUSBD_PIPE_INFORMATION pPipeInformation = NULL; + PIO_STACK_LOCATION pNextStackLocation = NULL; + PBULKUSB_RW_CONTEXT pReadWriteContext = NULL; + PIRP pUsbIoRequestPacket = NULL; + + PrintFunctionEntry(__FUNCTION__); + + //Acquire device Remove lock here + if(pDevice->UsbDeviceState != Working) + { + + SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid device state\n")); + ntReadWriteStatus = STATUS_INVALID_DEVICE_STATE; + goto FinishDeviceReadWrite; + } + + if(!IS_VALID(pucTransferBuffer)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Invalid Transfer Buffer Passed\n")); + ntReadWriteStatus = STATUS_INVALID_PARAMETER; + goto FinishDeviceReadWrite; + } + + if(ulTransferLength > MAX_BULK_TRANSFER_SIZE) + { + SkyWalkerDebugPrint(ENTRY_LEVEL, + ("Tansfer Length (%lu) > MAX_BULK_TRANSFER_SIZE (%lu)\n", + ulTransferLength, + MAX_BULK_TRANSFER_SIZE)); + ntReadWriteStatus = STATUS_INVALID_PARAMETER; + goto FinishDeviceReadWrite; + } + + if(ulTransferLength == 0) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Transfer data length = 0\n")); + ntReadWriteStatus = STATUS_SUCCESS; + goto FinishDeviceReadWrite; + } + + ulURBFlags = USBD_SHORT_TRANSFER_OK; + if(bRead) + { + ulURBFlags |= USBD_TRANSFER_DIRECTION_IN; + SkyWalkerDebugPrint(EXTREME_LEVEL,("Read Operation\n")); + pPipeInformation = &pDevice->ReadPipe; + } + else + { + + ulURBFlags |= USBD_TRANSFER_DIRECTION_OUT; + SkyWalkerDebugPrint(EXTREME_LEVEL,("Write Operation\n")); + pPipeInformation = &pDevice->WritePipe; + } + + DbgPrint("Pipe Information = %p\n",pPipeInformation); + + //the transfer request is for TransferLength. + //we can perform a max of Packet Size + //in each stage. + if(ulTransferLength > MAX_BULK_PACKET_SIZE) + { + ulStageTransferLength = MAX_BULK_PACKET_SIZE; + } + else + { + ulStageTransferLength = ulTransferLength; + } + + //Allocate IRP for the USB Transfer + pUsbIoRequestPacket = IoAllocateIrp(pKSDeviceObject->NextDeviceObject->StackSize, + FALSE); + if(!IS_VALID(pUsbIoRequestPacket)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Allocate Memory for the USB Irp\n")); + ntReadWriteStatus = STATUS_INSUFFICIENT_RESOURCES; + goto FinishDeviceReadWrite; + } + + ULONG ulStreamOffset = PACKET_PER_FRAME * ulStreamIndex; + pDevice->pUsbStreamIrp[ulPacketIndex + ulStreamOffset] = pUsbIoRequestPacket; + + + pUSBRequestBlock = (PURB)ExAllocatePoolWithTag( NonPagedPool, + sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER), + USB_MEMORY_TAG); + + if(!IS_VALID(pUSBRequestBlock)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Allocate Memory for the Bulk / Interrupt URB\n")); + ntReadWriteStatus = STATUS_INSUFFICIENT_RESOURCES; + goto FinishDeviceReadWrite; + } + + RtlZeroMemory(pUSBRequestBlock, sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER)); + + UsbBuildInterruptOrBulkTransferRequest( + pUSBRequestBlock, + sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER), + pPipeInformation->PipeHandle, + pucTransferBuffer, + NULL, + ulStageTransferLength, + ulURBFlags, + NULL); + + pReadWriteContext = (PBULKUSB_RW_CONTEXT)ExAllocatePoolWithTag( NonPagedPool, + sizeof(BULKUSB_RW_CONTEXT), + USB_MEMORY_TAG); + + if(!IS_VALID(pReadWriteContext)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Allocate Memory for the Read Write Context\n")); + ntReadWriteStatus = STATUS_INSUFFICIENT_RESOURCES; + goto FinishDeviceReadWrite; + } + + //set BULKUSB_RW_CONTEXT parameters. + pReadWriteContext->pUSBRequestBlock = pUSBRequestBlock; + pReadWriteContext->pTransferBuffer = pucTransferBuffer + ulStageTransferLength; + pReadWriteContext->ulRemainingByteTransfer = ulTransferLength - ulStageTransferLength ; + pReadWriteContext->ulCompletedByteTransfer = 0L; + pReadWriteContext->pDevice = pDevice; + pReadWriteContext->ulStreamIndex = ulStreamIndex; + + //use the original read/write irp as an internal device control irp + pNextStackLocation = IoGetNextIrpStackLocation(pUsbIoRequestPacket); + pNextStackLocation->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + pNextStackLocation->Parameters.Others.Argument1 = (PVOID)pUSBRequestBlock ; + pNextStackLocation->Parameters.DeviceIoControl.IoControlCode = + IOCTL_INTERNAL_USB_SUBMIT_URB; + + IoSetCompletionRoutine(pUsbIoRequestPacket, + (PIO_COMPLETION_ROUTINE)UsbReadWriteCompletion, + pReadWriteContext, + TRUE, + TRUE, + TRUE); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("USB Transfer Details for Stream %lu Packet %lu\n", + ulStreamIndex, + ulPacketIndex)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("ulStageTransferLength = %lu\n",ulStageTransferLength)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulCompletedByteTransfer = %lu\n",pReadWriteContext->ulCompletedByteTransfer)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulRemainingByteTransfer = %lu\n",pReadWriteContext->ulRemainingByteTransfer)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("bRead = %02d\n",bRead)); + + //since we return STATUS_PENDING call IoMarkIrpPending. + //This is the boiler plate code. + //This may cause extra overhead of an APC for the Irp completion + //but this is the correct thing to do. + + IoMarkIrpPending(pUsbIoRequestPacket); + IncrementPendingIoCount(pDevice); + + ntReadWriteStatus = IoCallDriver( pKSDeviceObject->NextDeviceObject, + pUsbIoRequestPacket); + + if(!NT_SUCCESS(ntReadWriteStatus)) + { + + SkyWalkerDebugPrint(ENTRY_LEVEL, ("IoCallDriver Failed with status %X\n", ntReadWriteStatus)); + + //if the device was yanked out, then the pipeInformation + //field is invalid. + //similarly if the request was cancelled, then we need not + //invoked reset pipe/device. + KIRQL CurrentIrql = KeGetCurrentIrql(); + + if((ntReadWriteStatus != STATUS_CANCELLED) && + (ntReadWriteStatus != STATUS_DEVICE_NOT_CONNECTED)) + { + + if(CurrentIrql < DISPATCH_LEVEL) + { + ntReadWriteStatus = ResetUsbPipe(pKSDeviceObject,pPipeInformation); + if(!NT_SUCCESS(ntReadWriteStatus)) + { + + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Reset USB Pipe Failed\n")); + + ntReadWriteStatus = ResetUsbDevice(pKSDeviceObject); + } + } + } + else + { + + SkyWalkerDebugPrint(ENTRY_LEVEL, ("ntReadWriteStatus is STATUS_CANCELLED or " + "STATUS_DEVICE_NOT_CONNECTED\n")); + } + //Freeing up the resources allocated in this routine + goto FinishDeviceReadWrite; + } + + PrintFunctionExit(__FUNCTION__,STATUS_PENDING); + + return STATUS_PENDING; + +FinishDeviceReadWrite: + + if(IS_VALID(pUSBRequestBlock)) + { + ExFreePoolWithTag(pUSBRequestBlock,USB_MEMORY_TAG); + pUSBRequestBlock = NULL; + } + if(IS_VALID(pReadWriteContext)) + { + ExFreePoolWithTag(pReadWriteContext,USB_MEMORY_TAG); + pReadWriteContext = NULL; + } + if(IS_VALID(pUsbIoRequestPacket)) + { + IoFreeIrp(pUsbIoRequestPacket); + pUsbIoRequestPacket = NULL; + } + + PrintFunctionExit(__FUNCTION__,ntReadWriteStatus); + + return ntReadWriteStatus; +} + +/***************************************************************************** + Function : UsbReadWriteCompletion + Description : This is the completion routine for reads/writes + If the irp completes with success, we check if we + need to recirculate this irp for another stage of + transfer. In this case return STATUS_MORE_PROCESSING_REQUIRED. + if the irp completes in error, free all memory allocs and + return the status. + IN PARAM : Pointer to Device Object + Io Request Packet + Context + OUT PARAM : Status of Read / Write request Completion + STATUS_MORE_PROCESSING_REQUIRED always + PreCondition : NONE + PostCondtion : On Success Read/ Write Request Completed + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS UsbReadWriteCompletion( + IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket, + IN PVOID pContext + ) +{ + ULONG ulStageTransferLength = 0; + NTSTATUS ntReadWriteCompleteStatus = pIoRequestPacket->IoStatus.Status; + PIO_STACK_LOCATION pNextStackLocation; + PBULKUSB_RW_CONTEXT pReadWriteContext = (PBULKUSB_RW_CONTEXT)pContext; + CSkyWalker1Device * pDevice = pReadWriteContext->pDevice; + ULONG ulStreamIndex = 0L; + PrintFunctionEntry(__FUNCTION__); + + //successfully performed a stageLength of transfer. + //check if we need to recirculate the irp. + if(NT_SUCCESS(ntReadWriteCompleteStatus)) + { + if(pReadWriteContext) + { + pReadWriteContext->ulCompletedByteTransfer += + pReadWriteContext->pUSBRequestBlock->UrbBulkOrInterruptTransfer.TransferBufferLength; + + if((pReadWriteContext->ulRemainingByteTransfer)&& (!pIoRequestPacket->Cancel)) + { + //another stage transfer + SkyWalkerDebugPrint(EXTREME_LEVEL, ("Another stage transfer...\n")); + + //the transfer request is for TransferLength. + //we can perform a max of MAX_BULK_PACKET_SIZE + //in each stage. + if(pReadWriteContext->ulRemainingByteTransfer > MAX_BULK_PACKET_SIZE) + { + ulStageTransferLength = MAX_BULK_PACKET_SIZE; + } + else + { + ulStageTransferLength = pReadWriteContext->ulRemainingByteTransfer; + } + + //Reinitialize the urb + pReadWriteContext->pUSBRequestBlock->UrbBulkOrInterruptTransfer.TransferBufferLength + = ulStageTransferLength; + + pReadWriteContext->pUSBRequestBlock->UrbBulkOrInterruptTransfer.TransferBuffer + = pReadWriteContext->pTransferBuffer; + + pReadWriteContext->pTransferBuffer += ulStageTransferLength; + pReadWriteContext->ulRemainingByteTransfer -= ulStageTransferLength ; + + //use the original read/write irp as an internal device control irp + pNextStackLocation = IoGetNextIrpStackLocation(pIoRequestPacket); + pNextStackLocation->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + pNextStackLocation->Parameters.Others.Argument1 = (PVOID)pReadWriteContext->pUSBRequestBlock ; + pNextStackLocation->Parameters.DeviceIoControl.IoControlCode = + IOCTL_INTERNAL_USB_SUBMIT_URB; + + IoSetCompletionRoutine(pIoRequestPacket, + (PIO_COMPLETION_ROUTINE)UsbReadWriteCompletion, + pReadWriteContext, + TRUE, + TRUE, + TRUE); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("USB Transfer Details for Stream %lu\n", + pReadWriteContext->ulStreamIndex)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("ulStageTransferLength = %lu\n",ulStageTransferLength)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulCompletedByteTransfer = %lu\n",pReadWriteContext->ulCompletedByteTransfer)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulRemainingByteTransfer = %lu\n",pReadWriteContext->ulRemainingByteTransfer)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pIoRequestPacket = 0x%p\n",pIoRequestPacket)); + ntReadWriteCompleteStatus = IoCallDriver( pDevice->m_pKSDevice->NextDeviceObject, + pIoRequestPacket); + + return STATUS_MORE_PROCESSING_REQUIRED; + } + else + { + + InterlockedExchangeAdd((LONG*)&pDevice->m_NumberOfBytesRead[pReadWriteContext->ulStreamIndex], + pReadWriteContext->ulCompletedByteTransfer); + + ulStreamIndex = pReadWriteContext->ulStreamIndex; + + //This is the last transfer + //SkyWalkerDebugPrint(ENTRY_LEVEL,("Valid Synthesis Buffer\n")); + // //This is not needed as anyways the IRP is going to Free soon + //pIoRequestPacket->IoStatus.Information = pReadWriteContext->ulCompletedByteTransfer; + ////Set the Frame Read Event here + // + //if(pReadWriteContext->ulCompletedByteTransfer == pDevice->m_SampleSize) + //{ + // pDevice->m_SynthesisDataValid = 1; + //} + + } + } + } + else + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("ReadWriteCompletion Failed \n")); + } + + if(pReadWriteContext) + { + //Dump pReadWriteContext + SkyWalkerDebugPrint(EXTREME_LEVEL,("Completed Stream = %lu",pReadWriteContext->ulStreamIndex)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->pUSBRequestBlock = 0x%p\n", + pReadWriteContext->pUSBRequestBlock)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulRemainingByteTransfer = %lu\n", + pReadWriteContext->ulRemainingByteTransfer)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->ulCompletedByteTransfer = %lu\n", + pReadWriteContext->ulCompletedByteTransfer)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pReadWriteContext->pDevice = 0x%p\n", + pReadWriteContext->pDevice)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("Actual Byte Transfer = %lu", + pReadWriteContext->pUSBRequestBlock-> + UrbBulkOrInterruptTransfer.TransferBufferLength)); + + + DecrementPendingIoCount(pDevice); + + if(IS_VALID(pReadWriteContext->pUSBRequestBlock)) + { + ExFreePoolWithTag(pReadWriteContext->pUSBRequestBlock,USB_MEMORY_TAG); + pReadWriteContext->pUSBRequestBlock = NULL; + } + + if(IS_VALID(pReadWriteContext)) + { + ExFreePoolWithTag(pReadWriteContext,USB_MEMORY_TAG); + pReadWriteContext = NULL; + } + + if(IS_VALID(pIoRequestPacket)) + { + IoFreeIrp(pIoRequestPacket); + pIoRequestPacket = NULL; + } + + } + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Calling ProcessStream \n")); + //TS is read, Process the stream + pDevice->ProcessStream(ulStreamIndex); + + PrintFunctionExit(__FUNCTION__,STATUS_MORE_PROCESSING_REQUIRED); + + //This is the only status that can be returned from the Asynchronous + //IRP created by Driver + return STATUS_MORE_PROCESSING_REQUIRED; +} +/***************************************************************************** + Function : ControlUsbDevice + Description : This Function is used to send the Vendor requests to the Device + IN PARAM : Pointer to Device Object + Vendor Request + Value corresponding to Request + Index for the Request (Used with Request) + Transfer Buffer for Read/Write + size of the Transfer Buffer + True if Read from else False for Write to device + OUT PARAM : Status of Vendor Request Execution + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Vendor Request Command Executed + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS ControlUsbDevice( IN PKSDEVICE pKSDeviceObject, + IN UCHAR ucRequest, + IN USHORT usValue, + IN USHORT usIndex, + IN PUCHAR pucTransferBuffer, + IN ULONG ulTransferLength, + IN BOOLEAN bRead) +{ + NTSTATUS ntControlStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; + PURB pUSBRequestBlock = NULL; + ULONG ulURBFlags = 0L; + PrintFunctionEntry(__FUNCTION__); + + if(pDevice->UsbDeviceState != Working) + { + + SkyWalkerDebugPrint(ENTRY_LEVEL,("Invalid device state\n")); + ntControlStatus = STATUS_INVALID_DEVICE_STATE; + goto ExitControlDevice; + } + ulURBFlags = USBD_SHORT_TRANSFER_OK; + if(bRead) + { + ulURBFlags |= USBD_TRANSFER_DIRECTION_IN; //Requst data from Device + SkyWalkerDebugPrint(EXTREME_LEVEL,("Read Operation\n")); + } + else + { + ulURBFlags |= USBD_TRANSFER_DIRECTION_OUT; + SkyWalkerDebugPrint(EXTREME_LEVEL,("Write Operation\n")); + } + + pUSBRequestBlock = (PURB)ExAllocatePoolWithTag( NonPagedPool, + sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST), + USB_MEMORY_TAG); + + if(!IS_VALID(pUSBRequestBlock)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Allocate Memory for the Vendor URB\n")); + ntControlStatus = STATUS_INSUFFICIENT_RESOURCES; + goto ExitControlDevice; + } + + UsbBuildVendorRequest( + pUSBRequestBlock,//Pointer to an URB that + //is to be formatted as a vendor or class + //request. + URB_FUNCTION_VENDOR_DEVICE, //Function + sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST), //Length of URB in Bytes + ulURBFlags, //Zero, One or Combination of + //USBD_TRANSFER_DIRECTION_IN & USBD_SHORT_TRANSFER_OK + 0, //Reserved + ucRequest, //USB/Vendor Specific Request Code + usValue, //Value Specific to Request + usIndex, //Device Defined identifier else Zero + pucTransferBuffer,//Pointer to Resident Buffer for Transfer or NULL + NULL, //Pointer to MDL for Transfer or NULL + ulTransferLength,//Length in Bytes of Buffer specified + NULL //NULL Always + ); + + + ntControlStatus = SendURBToBusDriver(pKSDeviceObject,pUSBRequestBlock); + + if(!NT_SUCCESS(ntControlStatus)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Sending Vendor Request Failed\n")); + } + + ExFreePool(pUSBRequestBlock); + +ExitControlDevice: + + PrintFunctionExit(__FUNCTION__,ntControlStatus); + return ntControlStatus; +} + +/***************************************************************************** + Function : SendURBToBusDriver + Description : Function to used to Send the URB to the USB Bus Driver (USBD.sys) + IN PARAM : Pointer to Device Object to which URB is to be sent + Pointer to the URB to be sent + OUT PARAM : Status of the URB Send operation + STATUS_SUCCESS on Successful execution + STATUS_INVALID_PARAMETER in case of Error + STATUS_INSUFFICIENT_RESOURCES in case IRP could not be created + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success URB sent to the Bus Driver + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS SendURBToBusDriver(IN PKSDEVICE pKSDeviceObject, + IN PURB pUSBRequestBlock ) +{ + NTSTATUS ntIrpProcessingStatus = STATUS_SUCCESS; + USBD_STATUS UsbStatus; + PIRP pIoRequestPacket = NULL; + KEVENT kIrpCompleted; + IO_STATUS_BLOCK IoStatusBlock; + PIO_STACK_LOCATION pNextIoStackLocation = NULL; + PDEVICE_OBJECT pRootDeviceObject = pKSDeviceObject->NextDeviceObject; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; + + PrintFunctionEntry(__FUNCTION__); + + if((!IS_VALID(pRootDeviceObject)) || (!(IS_VALID(pUSBRequestBlock)))) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("pRootDeviceObject = 0x%p, pUSBRequestBlock = 0x%p\n",pRootDeviceObject,pUSBRequestBlock)); + ntIrpProcessingStatus = STATUS_INVALID_PARAMETER; + goto CompleteURBSend; + } + //Initialize Kernel Event which should be trigerred after completion + //of the Device IO Control request + KeInitializeEvent(&kIrpCompleted, //PKEVENT + NotificationEvent, //Type + FALSE); //State + + //Create Internal Device Io Control Request + pIoRequestPacket = IoBuildDeviceIoControlRequest( IOCTL_INTERNAL_USB_SUBMIT_URB , + pRootDeviceObject, //Device to which the request to be sent + NULL, + 0, + NULL, + 0, + TRUE, //TRUE for IRM_MJ_INTERNAL_DEVICE_CONTROL, IRP_MJ_SCSI + &kIrpCompleted, //Event should be trigerred when the IRP completes + &IoStatusBlock); //The Status Block should be set when the Io request Completes + + + //If could not create the IRP return with the INSUFFICIENT RESOURCES + if(pIoRequestPacket == NULL) + { + ntIrpProcessingStatus = STATUS_INSUFFICIENT_RESOURCES; + goto CompleteURBSend; + } + + pNextIoStackLocation = IoGetNextIrpStackLocation(pIoRequestPacket); + pNextIoStackLocation->Parameters.Others.Argument1 = pUSBRequestBlock; + + IncrementPendingIoCount(pDevice); + + //Call the Next Driver + ntIrpProcessingStatus = IoCallDriver(pRootDeviceObject,pIoRequestPacket); + if(ntIrpProcessingStatus == STATUS_PENDING) + { + LARGE_INTEGER Timeout; + Timeout.QuadPart = (LONGLONG) 2 /*sec*/* 1000 /*msec*/ * 1000 /*usec*/ * (-10)/*Conv. Factor*/; + //for(int nRetry = 0; ((nRetry < 3) && (ntIrpProcessingStatus != STATUS_SUCCESS)) ; nRetry++) + { + //IRP is yet to be processed thus STATUS_PENDING is returned from the Lower Device Driver (USBD.sys) + ntIrpProcessingStatus = KeWaitForSingleObject(&kIrpCompleted, //PKEVENT + Executive, //Wait Reason has to be Executive + KernelMode, //Must be kernel mode so + //that Stack will not Paged out + FALSE, //No Alert + NULL//&Timeout //Wait for 2 sec max + ); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("KeWaitForSingleObject returned with status = %s(0x%X)\n", + NTStatusToString(ntIrpProcessingStatus),ntIrpProcessingStatus)); + //SkyWalkerDebugPrint(EXTREME_LEVEL,("nRetry = %d\n",nRetry)); + } + + ntIrpProcessingStatus = IoStatusBlock.Status; + UsbStatus = URB_STATUS(pUSBRequestBlock); + SkyWalkerDebugPrint(EXTREME_LEVEL,("URB STATUS = 0x%X\n",UsbStatus)); + } + + DecrementPendingIoCount(pDevice); + +CompleteURBSend: + + PrintFunctionExit(__FUNCTION__,ntIrpProcessingStatus); + + return ntIrpProcessingStatus; + +} + +/***************************************************************************** + Function : QueryRemoveUsbDevice + Description : Function to used to Service PnP IRPs of Minor Type + IRP_MN_QUERY_REMOVE_DEVICE + IN PARAM : Pointer to Device Object whose Remove Query has come + Device Remove Query Irp with Minor Code IRP_MN_QUERY_REMOVE_DEVICE + OUT PARAM : Status of the Device Remove Query Processing + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Device can be removed is conveyed to the caller + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS QueryRemoveUsbDevice( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + KIRQL kOldIrql; + NTSTATUS ntQueryStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; + + PrintFunctionEntry(__FUNCTION__); + + // + //If we can allow removal of the device, we should set the QueueState + //to HoldRequests so further requests will be queued. This is required + //so that we can process queued up requests in cancel-remove just in + //case somebody else in the stack fails the query-remove. + KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); + + SET_NEW_PNP_STATE(pDevice, PendingRemove); + pDevice->QueueState = HoldRequests; + + KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); + + //wait for the existing ones to be finished. + //first, decrement this operation + DecrementPendingIoCount(pDevice); + + KeWaitForSingleObject(&pDevice->EvDeviceStopOk, + Executive, + KernelMode, + FALSE, + NULL); + + PrintFunctionExit(__FUNCTION__,ntQueryStatus); + + return ntQueryStatus; + +} + +/***************************************************************************** + Function : CancelRemoveUsbDevice + Description : Function to used to Service PnP IRPs of Minor Type + IRP_MN_CANCEL_REMOVE_DEVICE + IN PARAM : Pointer to Device Object whose Remove request has + been cancelled + Device Remove Cancel Irp with Minor Code + IRP_MN_CANCEL_REMOVE_DEVICE + OUT PARAM : Status of the Device Remove Cancel Irp Processing + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Device Remove cancel request is processed + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS CancelRemoveUsbDevice(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + KIRQL kOldIrql; + KEVENT Evevent; + NTSTATUS ntStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; + + PrintFunctionEntry(__FUNCTION__); + + //We need to reset the QueueState flag to ProcessRequest, + //since the device resume its normal activities. + // + //First check to see whether you have received cancel-stop + //without first receiving a query-stop. This could happen if someone + //above us fails a query-stop and passes down the subsequent + //cancel-stop. + if(pDevice->UsbDeviceState == PendingRemove ) + { + ntStatus = PassDownIRPAndWaitForCompletion(pKSDeviceObject->NextDeviceObject, + pIoRequestPacket, + true); + if(NT_SUCCESS(ntStatus)) + { + + KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); + + RESTORE_PREVIOUS_PNP_STATE(pDevice); + pDevice->QueueState = AllowRequests; + + KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); + + //ProcessQueuedRequests(deviceExtension); + } + + } + else + { + + /* spurious cancel Remove + + If the device is already started when the driver receives + this IRP, the driver simply sets status to success and passes + the IRP to the next driver. For such a cancel-remove IRP, a + function driver need not set a completion routine. The device + may not be in the remove-pending state, because, for example, + the driver failed the previous IRP_MN_QUERY_REMOVE_DEVICE.*/ + + } + + PrintFunctionExit(__FUNCTION__,ntStatus); + + return ntStatus; + +} + +/***************************************************************************** + Function : SurpriseUsbDeviceRemoval + Description : Function to used to Service PnP IRPs of Minor Type + IRP_MN_SURPRISE_REMOVAL + IN PARAM : Pointer to Device Object which is removed + surprisingly + Device Remove Cancel Irp with Minor Code + IRP_MN_SURPRISE_REMOVAL + OUT PARAM : Status of the Spurious Device Removal Processing + STATUS_SUCCESS Always + PreCondition : NONE + PostCondtion : On Success Surprised Device Remove is processed + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS SurpriseUsbDeviceRemoval(IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + KIRQL kOldIrql; + KEVENT Evevent; + NTSTATUS ntStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; + + PrintFunctionEntry(__FUNCTION__); + + // + //1. fail pending requests + //2. return device and memory resources + //3. disable interfaces + // + + //if(deviceExtension->WaitWakeEnable) { + // + // CancelWaitWake(deviceExtension); + //} + + + //if(WinXpOrBetter == deviceExtension->WdmVersion) { + + // if(deviceExtension->SSEnable) { + + // // + // //Cancel the timer so that the DPCs are no longer fired. + // //we do not need DPCs because the device has been surprise + // //removed + // // + // + // KeCancelTimer(&deviceExtension->Timer); + + // deviceExtension->SSEnable = 0; + + // // + // //make sure that if a DPC was fired before we called cancel timer, + // //then the DPC and work-time have run to their completion + // // + // KeWaitForSingleObject(&deviceExtension->NoDpcWorkItemPendingEvent, + // Executive, + // KernelMode, + // FALSE, + // NULL); + + // // + // //make sure that the selective suspend request has been completed. + // // + // KeWaitForSingleObject(&deviceExtension->NoIdleReqPendEvent, + // Executive, + // KernelMode, + // FALSE, + // NULL); + // } + //} + + KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); + + SET_NEW_PNP_STATE(pDevice, SurpriseRemoved); + pDevice->QueueState = FailRequests; + + KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); + + //ProcessQueuedRequests(deviceExtension); + + AbortUsbPipes(pKSDeviceObject); + + PrintFunctionExit(__FUNCTION__,ntStatus); + + return ntStatus; +} + +/***************************************************************************** + Function : AbortUsbPipes + Description : This function sends an abort pipe request for pipes. + IN PARAM : Pointer to Device Object + OUT PARAM : Status of the Pipe Abort + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Pipe are aborted + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS AbortUsbPipes(IN PKSDEVICE pKSDeviceObject) +{ + PURB pUsbRequestBlock = NULL; + ULONG ulPipeIndex = 0; + NTSTATUS ntStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; + PUSBD_PIPE_INFORMATION pPipeInformation = &pDevice->ReadPipe; + + PrintFunctionEntry(__FUNCTION__); + + for(ulPipeIndex = 0; ulPipeIndex < 2; ulPipeIndex++) + { + //if(pPipeInformation->PipeOpen) + { + + SkyWalkerDebugPrint(EXTREME_LEVEL, ("Aborting Pipe 0x%X\n",pPipeInformation->EndpointAddress)); + + pUsbRequestBlock = (PURB) ExAllocatePoolWithTag( NonPagedPool, + sizeof(struct _URB_PIPE_REQUEST), + USB_MEMORY_TAG); + + if(pUsbRequestBlock) + { + + pUsbRequestBlock->UrbHeader.Length = sizeof(struct _URB_PIPE_REQUEST); + pUsbRequestBlock->UrbHeader.Function = URB_FUNCTION_ABORT_PIPE; + pUsbRequestBlock->UrbPipeRequest.PipeHandle = pPipeInformation->PipeHandle; + + ntStatus = SendURBToBusDriver(pKSDeviceObject,pUsbRequestBlock); + + ExFreePool(pUsbRequestBlock); + } + else + { + + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to Allocate memory for URB during Pipe Abort\n")); + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + goto FinishAbortPipe; + } + + if(NT_SUCCESS(ntStatus)) + { + //pPipeInformation->PipeOpen = FALSE; + } + pPipeInformation = &pDevice->WritePipe; + } + } + +FinishAbortPipe: + + PrintFunctionExit(__FUNCTION__,ntStatus); + + return ntStatus; +} + +/***************************************************************************** + Function : RemoveUsbDevice + Description : Function to used to Service PnP IRPs of Minor Type + IRP_MN_REMOVE_DEVICE + IN PARAM : Pointer to Device Object whose remove + request has come + Device Remove Cancel Irp with Minor Code + IRP_MN_REMOVE_DEVICE + OUT PARAM : Status of the Device Removal + STATUS_SUCCESS Always + PreCondition : NONE + PostCondtion : On Success Device Remove request is processed + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS RemoveUsbDevice( IN PKSDEVICE pKSDeviceObject, + IN PIRP pIoRequestPacket) +{ + KIRQL kOldIrql; + KEVENT Evevent; + NTSTATUS ntStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; + ULONG ulRequestCount = 0L; + PrintFunctionEntry(__FUNCTION__); + + // + //The Plug & Play system has dictated the removal of this device. We + //have no choice but to detach and delete the device object. + //(If we wanted to express an interest in preventing this removal, + //we should have failed the query remove IRP). + // + + if(pDevice->UsbDeviceState != SurpriseRemoved ) + { + + // + //we are here after QUERY_REMOVE + // + + KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); + + pDevice->QueueState = FailRequests; + + KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); + + //if(deviceExtension->WaitWakeEnable) { + // + // CancelWaitWake(deviceExtension); + //} + + //if(WinXpOrBetter == deviceExtension->WdmVersion) { + + // if(deviceExtension->SSEnable) { + + // // + // //Cancel the timer so that the DPCs are no longer fired. + // //we do not need DPCs because the device has been removed + // // + // KeCancelTimer(&deviceExtension->Timer); + + // deviceExtension->SSEnable = 0; + + // // + // //make sure that if a DPC was fired before we called cancel timer, + // //then the DPC and work-time have run to their completion + // // + // KeWaitForSingleObject(&deviceExtension->NoDpcWorkItemPendingEvent, + // Executive, + // KernelMode, + // FALSE, + // NULL); + + // // + // //make sure that the selective suspend request has been completed. + // // + // KeWaitForSingleObject(&deviceExtension->NoIdleReqPendEvent, + // Executive, + // KernelMode, + // FALSE, + // NULL); + // } + //} + + //ProcessQueuedRequests(deviceExtension); + + AbortUsbPipes(pKSDeviceObject); + } + + KeAcquireSpinLock(&pDevice->DeviceStateLock, &kOldIrql); + + SET_NEW_PNP_STATE(pDevice, Removed); + + KeReleaseSpinLock(&pDevice->DeviceStateLock, kOldIrql); + + // + //need 2 decrements + // + + ulRequestCount = DecrementPendingIoCount(pDevice); + ulRequestCount = DecrementPendingIoCount(pDevice); + + KeWaitForSingleObject(&pDevice->EvDeviceRemoveOk, + Executive, + KernelMode, + FALSE, + NULL); + + PrintFunctionExit(__FUNCTION__,ntStatus); + + return ntStatus; +} + +/***************************************************************************** + Function : ResetUsbPipe + Description : This routine synchronously submits a URB_FUNCTION_RESET_PIPE + request down the stack. + IN PARAM : Pointer to Device Object + Pipe to be reseted + OUT PARAM : Status of the Reset Usb Pipe Request + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Usb Pipe is reseted + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS ResetUsbPipe( IN PKSDEVICE pKSDeviceObject, + IN PUSBD_PIPE_INFORMATION pPipeInformation) +{ + PURB pUsbRequestBlock = NULL; + NTSTATUS ntResetStatus = STATUS_SUCCESS; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *)pKSDeviceObject->Context; + + PrintFunctionEntry(__FUNCTION__); + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Pipe to Reset = 0x%X",pPipeInformation->EndpointAddress)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("Pipe Handle = 0x%p",pPipeInformation->PipeHandle)); + + pUsbRequestBlock = (PURB) ExAllocatePoolWithTag( NonPagedPool, + sizeof(struct _URB_PIPE_REQUEST), + USB_MEMORY_TAG); + + if(pUsbRequestBlock) + { + + pUsbRequestBlock->UrbHeader.Length = (USHORT) sizeof(struct _URB_PIPE_REQUEST); + pUsbRequestBlock->UrbHeader.Function = URB_FUNCTION_RESET_PIPE; + pUsbRequestBlock->UrbPipeRequest.PipeHandle = pPipeInformation->PipeHandle; + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Sending the Pipe Reset Command\n")); + ntResetStatus = SendURBToBusDriver(pKSDeviceObject, pUsbRequestBlock); + + ExFreePool(pUsbRequestBlock); + } + else + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Failed to allocate URB Memory during Pipe Reset\n")); + ntResetStatus = STATUS_INSUFFICIENT_RESOURCES; + } + + if(NT_SUCCESS(ntResetStatus)) + { + SkyWalkerDebugPrint(EXTREME_LEVEL, ("Successfully Reseted the Usb Pipe\n")); + ntResetStatus = STATUS_SUCCESS; + } + else + { + SkyWalkerDebugPrint(ENTRY_LEVEL, ("Failed to reset the Usb Pipe\n")); + } + + PrintFunctionExit(__FUNCTION__,ntResetStatus); + + return ntResetStatus; +} + +/***************************************************************************** + Function : ResetUsbDevice + Description : This routine checks the current status of the Usb Port + If Device is connected but not enabled then it resets + the Usb Port + IN PARAM : Pointer to Device Object + OUT PARAM : Status of the Reset Usb Device Request + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Usb Device is reseted + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS ResetUsbDevice(IN PKSDEVICE pKSDeviceObject) +{ + NTSTATUS ntResetStatus = STATUS_SUCCESS; + ULONG ulPortStatus = 0; + + PrintFunctionEntry(__FUNCTION__); + + ntResetStatus = GetUsbPortStatus(pKSDeviceObject, &ulPortStatus); + + if((NT_SUCCESS(ntResetStatus)) + && (!(ulPortStatus & USBD_PORT_ENABLED)) + && (ulPortStatus & USBD_PORT_CONNECTED)) + { + + SkyWalkerDebugPrint(ENTRY_LEVEL,("Resetting the Parent Port\n")); + ntResetStatus = ResetUsbParentPort(pKSDeviceObject); + } + + PrintFunctionExit(__FUNCTION__,ntResetStatus); + + return ntResetStatus; +} + +/***************************************************************************** + Function : GetUsbPortStatus + Description : This routine retrives the Usb Port Status as Enabled / Disabled + and Connected / Not Connected + IN PARAM : Pointer to Device Object + Port Status + OUT PARAM : Status of the USB Port Status Request + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Usb Port status is retrived + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS GetUsbPortStatus( IN PKSDEVICE pKSDeviceObject, + IN OUT PULONG pulPortStatus) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + KEVENT EvRequestComplete; + PIRP pIoRequestPacket = NULL; + IO_STATUS_BLOCK IoStatus; + PIO_STACK_LOCATION pNextStackLocation = NULL; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; + + PrintFunctionEntry(__FUNCTION__); + + *pulPortStatus = 0; + + //Initialize the Event to be triggerred after completion of Device Io Control request + KeInitializeEvent(&EvRequestComplete, NotificationEvent, FALSE); + + pIoRequestPacket = IoBuildDeviceIoControlRequest( + IOCTL_INTERNAL_USB_GET_PORT_STATUS, + pKSDeviceObject->NextDeviceObject, + NULL, + 0, + NULL, + 0, + TRUE, + &EvRequestComplete, + &IoStatus); + + if(!IS_VALID(pIoRequestPacket)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Memory Allocation during Get Port Status Failed\n")); + goto FinishGetPortStatus; + return STATUS_INSUFFICIENT_RESOURCES; + } + + pNextStackLocation = IoGetNextIrpStackLocation(pIoRequestPacket); + + pNextStackLocation->Parameters.Others.Argument1 = pulPortStatus; + + ntStatus = IoCallDriver(pKSDeviceObject->NextDeviceObject, pIoRequestPacket); + + if(ntStatus == STATUS_PENDING) + { + KeWaitForSingleObject(&EvRequestComplete, Executive, KernelMode, FALSE, NULL); + } + else + { + IoStatus.Status = ntStatus; + } + + ntStatus = IoStatus.Status; + + SkyWalkerDebugPrint(EXTREME_LEVEL,("Port Status = %lu (",*pulPortStatus)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("%s, ",(((*pulPortStatus) & USBD_PORT_ENABLED)? "Enabled" : "Disabled"))); + SkyWalkerDebugPrint(EXTREME_LEVEL,("%s)\n",(((*pulPortStatus) & USBD_PORT_CONNECTED)? "Connected" : "Not Connected"))); + +FinishGetPortStatus: + + PrintFunctionExit(__FUNCTION__,ntStatus); + + return ntStatus; +} + +/***************************************************************************** + Function : ResetUsbParentPort + Description : This routine sends an IOCTL_INTERNAL_USB_RESET_PORT + synchronously down the stack. + IN PARAM : Pointer to Device Object + OUT PARAM : Status of the Reset USB Port Request + STATUS_SUCCESS on Successful execution + else Error from the Bus Driver + PreCondition : NONE + PostCondtion : On Success Usb Port is reseted + Logic : NONE + Assumption : NONE + Revision History: + *****************************************************************************/ +NTSTATUS ResetUsbParentPort( IN PKSDEVICE pKSDeviceObject ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + KEVENT EvRequestComplete; + PIRP pIoRequestPacket = NULL; + IO_STATUS_BLOCK IoStatus; + PIO_STACK_LOCATION pNextStackLocation = NULL; + CSkyWalker1Device * pDevice = (CSkyWalker1Device *) pKSDeviceObject->Context; + + PrintFunctionEntry(__FUNCTION__); + + //Initialize the Event to be triggerred after completion of Device Io Control request + KeInitializeEvent(&EvRequestComplete, NotificationEvent, FALSE); + + pIoRequestPacket = IoBuildDeviceIoControlRequest( + IOCTL_INTERNAL_USB_RESET_PORT, + pKSDeviceObject->NextDeviceObject, + NULL, + 0, + NULL, + 0, + TRUE, + &EvRequestComplete, + &IoStatus); + + if(!IS_VALID(pIoRequestPacket)) + { + SkyWalkerDebugPrint(ENTRY_LEVEL,("Memory Allocation during Reset Parent Device Failed\n")); + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + goto FinishResetDevice; + + } + + pNextStackLocation = IoGetNextIrpStackLocation(pIoRequestPacket); + + ntStatus = IoCallDriver(pKSDeviceObject->NextDeviceObject, pIoRequestPacket); + + if(ntStatus == STATUS_PENDING) + { + KeWaitForSingleObject(&EvRequestComplete, Executive, KernelMode, FALSE, NULL); + } + else + { + IoStatus.Status = ntStatus; + } + + ntStatus = IoStatus.Status; + +FinishResetDevice: + + PrintFunctionExit(__FUNCTION__,ntStatus); + + return ntStatus; +} + +//Print Device Descriptor +VOID PrintDeviceDescriptor(IN PUSB_DEVICE_DESCRIPTOR pDeviceDescriptor) +{ + SkyWalkerDebugPrint(INTERMEDIATE_LEVEL, (__FUNCTION__"\n")); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bLength= %02d\n", + pDeviceDescriptor->bLength)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bDescriptorType= %02d\n", + pDeviceDescriptor->bDescriptorType)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bcdUSB= 0x%X\n", + pDeviceDescriptor->bcdUSB)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bDeviceClass= 0x%02X\n", + pDeviceDescriptor->bDeviceClass)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bDeviceSubClass= 0x%02X\n", + pDeviceDescriptor->bDeviceSubClass)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bDeviceProtocol= 0x%02X\n", + pDeviceDescriptor->bDeviceProtocol)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bMaxPacketSize0= %02d\n", + pDeviceDescriptor->bMaxPacketSize0)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->idVendor= 0x%X\n", + pDeviceDescriptor->idVendor)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->idProduct= 0x%X\n", + pDeviceDescriptor->idProduct)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bcdDevice= 0x%X\n", + pDeviceDescriptor->bcdDevice)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->iManufacturer= %02d\n", + pDeviceDescriptor->iManufacturer)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->iProduct= %02d\n", + pDeviceDescriptor->iProduct)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->iSerialNumber= %02d\n", + pDeviceDescriptor->iSerialNumber)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pDeviceDescriptor->bNumConfigurations= %02d\n", + pDeviceDescriptor->bNumConfigurations)); +} + +//Print Configuration Descriptor +VOID PrintConfigurationDescriptor(IN PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor) +{ + SkyWalkerDebugPrint(INTERMEDIATE_LEVEL, (__FUNCTION__"\n")); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->bLength= %02d\n", + pConfigurationDescriptor->bLength)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->bDescriptorType= %02d\n", + pConfigurationDescriptor->bDescriptorType)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->wTotalLength= %d\n", + pConfigurationDescriptor->wTotalLength)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->bNumInterfaces= %02d\n", + pConfigurationDescriptor->bNumInterfaces)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->bConfigurationValue= %02d\n", + pConfigurationDescriptor->bConfigurationValue)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->iConfiguration= %02d\n", + pConfigurationDescriptor->iConfiguration)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->bmAttributes= 0x%02X\n", + pConfigurationDescriptor->bmAttributes)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pConfigurationDescriptor->MaxPower= %02d\n", + pConfigurationDescriptor->MaxPower)); + +} +//Print Interface Descriptor +VOID PrintInterfaceDescriptor(IN PUSB_INTERFACE_DESCRIPTOR pInterfaceDescriptor) +{ + SkyWalkerDebugPrint(INTERMEDIATE_LEVEL, (__FUNCTION__"\n")); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bLength= %02d\n", + pInterfaceDescriptor->bLength)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bDescriptorType= %02d\n", + pInterfaceDescriptor->bDescriptorType)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bInterfaceNumber= %02d\n", + pInterfaceDescriptor->bInterfaceNumber)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bAlternateSetting= %02d\n", + pInterfaceDescriptor->bAlternateSetting)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bNumEndpoints= %02d\n", + pInterfaceDescriptor->bNumEndpoints)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bInterfaceClass= 0x%02X\n", + pInterfaceDescriptor->bInterfaceClass)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bInterfaceSubClass= 0x%02X\n", + pInterfaceDescriptor->bInterfaceSubClass)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->bInterfaceProtocol= 0x%02X\n", + pInterfaceDescriptor->bInterfaceProtocol)); + SkyWalkerDebugPrint(EXTREME_LEVEL, ("pInterfaceDescriptor->iInterface= %02d\n", + pInterfaceDescriptor->iInterface)); + +} + +//Print Pipe Information +VOID PrintPipeInformation(PUSBD_PIPE_INFORMATION pPipeInformation) +{ + SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->PipeType = 0x%X\n", + pPipeInformation->PipeType)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->EndpointAddress = 0x%X\n", + pPipeInformation->EndpointAddress)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->MaximumPacketSize = 0x%X\n", + pPipeInformation->MaximumPacketSize)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->Interval = 0x%X\n", + pPipeInformation->Interval)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->PipeHandle = 0x%p\n", + pPipeInformation->PipeHandle)); + SkyWalkerDebugPrint(EXTREME_LEVEL,("pPipeInformation->MaximumTransferSize = 0x%X\n", + pPipeInformation->MaximumTransferSize)); + } \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Utility.cpp b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Utility.cpp index bba4deb..f0abaea 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Utility.cpp +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker1Utility.cpp @@ -1,2620 +1,2620 @@ -/***************************************************************************** - Company : Shree Ganesha Inc. - File Name : SkyWalker1Utility.cpp - Author : - Date : - Purpose : This file contains the Utility / Commanly Used Functions for the - SkyWalker1 Tuner Device Driver - - Revision History: -=============================================================================== - DATE VERSION AUTHOR REMARK -=============================================================================== - - XXth April,2009 01 Initial Version - -*****************************************************************************/ - -/* Include the Library and Other header file */ - -#include "SkyWalker1Main.h" //Common For all the Definitions, - //Declarations and Library Routines - -/* End of Inclusion the Library and Other header file */ - -/* Macro Definitions */ -/* End of Macro Definitions */ - -/* Global & Static variables Declaration */ -int nCurrentDebugLevel = EXTREME_LEVEL; -/* End of Global & Static variables Declaration */ - -/* External Variable Declaration */ -/* End of External Variable Declaration */ - -/* Declare Enumerations here */ -/* End of Enumeration declaration */ - -/* Function Prototypes */ -/* End of Function prototype definitions */ - -/***************************************************************************** - Function : PrintFunctionEntry - Description : Function to print Message while Entering the Function - IN PARAM : Function Name - OUT PARAM : NONE - PreCondition : NONE - PostCondtion : Function Entry Printed on Screen - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -void PrintFunctionEntry(IN char * pcFunctionName) -{ - - SkyWalkerDebugPrint(INTERMEDIATE_LEVEL, ("Entered into Function : %s() @ IRQ Level = %s\n", - pcFunctionName, - GetCurrentIrqlString())); - -} - -/***************************************************************************** - Function : PrintFunctionExit - Description : Function to print message while exiting the Function - IN PARAM : Function Name - Function Response - OUT PARAM : NONE - PreCondition : NONE - PostCondtion : Function Exit Printed on Screen - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -void PrintFunctionExit(IN char * pcFunctionName, IN NTSTATUS ntReturnCode) -{ - SkyWalkerDebugPrint(INTERMEDIATE_LEVEL, ("Exiting from Function : %s() @ IRQ Level = %s, with Status = %s(0x%08X)\n", - pcFunctionName, - GetCurrentIrqlString(), - NTStatusToString(ntReturnCode), - ntReturnCode)); -} - -/***************************************************************************** - Function : LowerDeviceCompletedIrp - Description : This function is a Completion Routine set when the IRP - is sent to the Lower Driver.It is called when the Lower Device - completes the IRP using the IoCompleteRequest() - IN PARAM : Reference of the Device Object whose lower device - was called - IRP Reference which sent Down - Context (In our case it is PKEVENT always) - OUT PARAM : STATUS_MORE_PROCESSING_REQUIRED always as Caller - will complete the IRP - PreCondition : IRP Passed Down to the Lower Device - PostCondtion : Event Set when the Response Received from the Lower Device - Logic : 1) Set the event is Pending Return from Lower Device - 2) return STATUS_MORE_PROCESSING_REQUIRED - Assumption : NONE - Note : This is usually called at the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS LowerDeviceCompletedIrp(IN PDEVICE_OBJECT pDeviceObject, - IN PIRP pIoRequestPacket, - IN PVOID pContext) -{ - - PrintFunctionEntry(__FUNCTION__); - - if (pIoRequestPacket->PendingReturned == TRUE) - { - // - // Set the event only if the lower driver has returned - // STATUS_PENDING earlier. This optimization removes the need to - // call KeSetEvent unnecessarily and improves performance because the - // system does not have to acquire an internal lock. - // (REFERENCE : Microsoft MSDN)http://support.microsoft.com/kb/320275 - - KeSetEvent ((PKEVENT) pContext, IO_NO_INCREMENT, FALSE); - } - - PrintFunctionExit(__FUNCTION__,STATUS_MORE_PROCESSING_REQUIRED); - // This is the only status that can be returned. - return STATUS_MORE_PROCESSING_REQUIRED; -} - -/***************************************************************************** - Function : PassDownIRPAndWaitForCompletion - Description : This function Passes the IRP to the Lower Device attached - and after passing waits for and Event which is triggerred after - the completion of the IRP (i.e. When IoCompleteRequest()is called by the - Lower Device) - IN PARAM : Reference of the Lower Device Object to be called - IRP Reference which needs to be pass Down - OUT PARAM : the Passing Down Status,Based on Status returned from - Lower Device - PreCondition : NONE - PostCondtion : IRP Passed Down to the Lower Device on successful Execution - Logic : 1) Initialize the Completion routine - 2) Copy Current IRP Stack location to Next (IoCopyCurrentIrpStackLocationToNext()) - 3) Set the IRP Completion Routine (IoSetCompeltionRoutine()) - 4) Call the Lower Device (IoCallDriver) - 5) Wait for the Irp Completion (KeWaitForSingleObject()) - Assumption : Device Extension has a Valid Lower Device Reference - Note : This is usually called at the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS PassDownIRPAndWaitForCompletion(IN PDEVICE_OBJECT pLowerDeviceObject, - IN PIRP pIoRequestPacket, - IN BOOLEAN bCopyStackLocation) -{ - NTSTATUS ntIrpProcessingStatus = STATUS_SUCCESS; - KEVENT kIrpCompleted; - PrintFunctionEntry(__FUNCTION__); - - //Initialize Kernel Event - KeInitializeEvent(&kIrpCompleted, //PKEVENT - NotificationEvent, //Type - FALSE); //State - - if(bCopyStackLocation) - { - //Copy Current IRP Stack to Next - IoCopyCurrentIrpStackLocationToNext(pIoRequestPacket); - } - - //Set Completion Routine - IoSetCompletionRoutine(pIoRequestPacket,//PIRP - LowerDeviceCompletedIrp,//Completion Routine - &kIrpCompleted, //PKEVENT (Context) - TRUE, //Flag on Success - TRUE, //Flag on Error - TRUE //Falg on Cancel - ); - //Call the Next Driver - ntIrpProcessingStatus = IoCallDriver(pLowerDeviceObject,pIoRequestPacket); - if(ntIrpProcessingStatus == STATUS_PENDING) - { - //IRP is to be processed - ntIrpProcessingStatus = KeWaitForSingleObject(&kIrpCompleted, //PKEVENT - Executive, //Wait Reason has to be Executive - KernelMode, //Must be kernel mode so - //that Stack will not Paged out - FALSE, //No Alert - NULL //Infinite Wait - ); - if(NT_SUCCESS(ntIrpProcessingStatus)) - { - //Lower Driver Completed the IRP (IoCompleteIrp()) - KeClearEvent(&kIrpCompleted); - ntIrpProcessingStatus = pIoRequestPacket->IoStatus.Status; - } - } - - PrintFunctionExit(__FUNCTION__,ntIrpProcessingStatus); - - return ntIrpProcessingStatus; - -} - - -/***************************************************************************** - Function : PassDownIRPAndForget - Description : This function Skips the IRP to the Lower Device attached - IN PARAM : Reference of the Lower Device Object to be called - IRP Reference which needs to be pass Down - OUT PARAM : the Passing Down Status,Based on Status returned from - IoCallDriver() Function - PreCondition : NONE - PostCondtion : IRP Passed Down to the Lower Device on successful Execution - Logic : 1) Skip the Current IRP - 2) Call the Lower Device Driver - Assumption : Device Extension has a Valid Lower Device Reference - Note : This is usually called at the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -NTSTATUS PassDownIRPAndForget(IN PDEVICE_OBJECT pLowerDeviceObject, - IN PIRP pIoRequestPacket) -{ - NTSTATUS ntIrpProcessingStatus = STATUS_SUCCESS; - - PrintFunctionEntry(__FUNCTION__); - - // - // As not setting a completion routine, skipping the stack - // location because it provides better performance. - // (REFERENCE : Microsoft MSDN) http://support.microsoft.com/kb/320275 - - //Skip Current IRP Stack - IoSkipCurrentIrpStackLocation(pIoRequestPacket); - - //Call the Lower Device - ntIrpProcessingStatus = IoCallDriver(pLowerDeviceObject,pIoRequestPacket); - - PrintFunctionExit(__FUNCTION__,ntIrpProcessingStatus); - - return ntIrpProcessingStatus; -} - -/***************************************************************************** - Function : CompleteIrpInDispatch - Description : This function Completes the IRP - IN PARAM : Reference of the Device Object - IRP Reference which needs to be Completed - OUT PARAM : NONE - PreCondition : NONE - PostCondtion : IRP Completed - Logic : NONE - Assumption : NONE - Note : This is usually called at the PASSIVE_LEVEL_IRQL - Revision History: - *****************************************************************************/ -VOID CompleteIrpInDispatch(IN PDEVICE_OBJECT pDeviceObject, - IN PIRP pIoRequestPacket) -{ - PrintFunctionEntry(__FUNCTION__); - - IoCompleteRequest(pIoRequestPacket,IO_NO_INCREMENT); - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); -} - -/***************************************************************************** - Function : Delay - Description : This function Delays Execution thread by the Microseconds passed - IN PARAM : Delay in MicroSeconds - OUT PARAM : NONE - PreCondition : NONE - PostCondtion : Delayed Execution Thread - Logic : NONE - Assumption : NONE - NOTE : This Function should always be called the the passive level - Revision History: - *****************************************************************************/ -VOID Delay(IN ULONG ulDelayInMicroSeconds) -{ - LARGE_INTEGER DelayIn100NanoSeconds; - PrintFunctionEntry(__FUNCTION__); - - //we need to Convert MicroSeconds to 100 nano Second Unit - //for the KeDelayExecutionThread() Call - //1000 nano Second = 1 MicroSecond - //100 nano Second = 0.1 MicroSecond - //Thus to Convert X Microseconds to Y 100 Nano Second Unit - // 1 (100 nano Second Unit) = 0.1 Micro Second Unit - // Y (100 nano Second Unit) = X Micro Second Unit - //Thus X (100 Nano Second Unit) = 0.1 Y MicroSecond Unit - //Thus Y (100 Nano Second Unit) = 10 * X (Micro Second) - //Thus Conversion Factor is 10 - - //Converting the Micro Seconds to 100 Nano Second Unit - //Negative Timeout Value is to Indicate the Relative Time from Current Time - DelayIn100NanoSeconds.QuadPart = (LONGLONG)ulDelayInMicroSeconds * (-10L); - - KeDelayExecutionThread(KernelMode, //Processor Wait Mode - Kernel - FALSE, //Lower Level Drivers are Not Alertable - &DelayIn100NanoSeconds); //Interval - - PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); -} -/***************************************************************************** - Function : GetCurrentIrqlString - Description : This function converts the IRQ Level to the String - IN PARAM : Status to be converted - OUT PARAM : NONE - PreCondition : NONE - PostCondtion : String value of the IRQ Level returned - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -char * GetCurrentIrqlString(void) -{ - ULONG ulCurrentIrql = KeGetCurrentIrql(); - - switch(ulCurrentIrql) - { - case PASSIVE_LEVEL: - return "PASSIVE_LEVEL"; - case APC_LEVEL: - return "APC_LEVEL"; - case DISPATCH_LEVEL: - return "DISPATCH_LEVEL"; - default: - return "DEVICE IRQL"; - } -} -/***************************************************************************** - Function : PrintDeviceChangeState - Description : This function Prints the Tuner State change - IN PARAM : State to which transition occurred - State From which transition occurred - OUT PARAM : NONE - PreCondition : NONE - PostCondtion : State Transition Printed - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -VOID PrintDeviceChangeState(IN KSSTATE ToState,IN KSSTATE FromState) -{ - PCHAR pFromState = NULL; - PCHAR pToState = NULL; - KSSTATE State = ToState; - PCHAR pState = NULL; - - for(INT nStateIndex = 0;nStateIndex < 2;nStateIndex++) - { - switch(State) - { - case KSSTATE_STOP: - pState = "Stop"; - break; - case KSSTATE_ACQUIRE: - pState = "Acquire"; - break; - case KSSTATE_PAUSE: - pState = "Pause"; - break; - case KSSTATE_RUN: - pState = "Run"; - break; - default : - pState = "Unknown"; - break; - - } - if(nStateIndex == 0) - { - pToState = pState; - State = FromState; - } - else - { - pFromState = pState; - } - } - - SkyWalkerDebugPrint(EXTREME_LEVEL, ("Change State From %s to %s\n",pFromState,pToState)); - -} - -/***************************************************************************** - Function : NTStatusToString - Description : This function converts the NTSTATUS value to String - IN PARAM : Status to be converted - OUT PARAM : NONE - PreCondition : NONE - PostCondtion : String value of the Status returned - Logic : NONE - Assumption : NONE - Note : NONE - Revision History: - *****************************************************************************/ -PUCHAR NTStatusToString(NTSTATUS Status) -{ - - switch (Status) { - case STATUS_SUCCESS: - return (PUCHAR)"STATUS_SUCCESS"; - case STATUS_WAIT_1: - return (PUCHAR)"STATUS_WAIT_1"; - case STATUS_WAIT_2: - return (PUCHAR)"STATUS_WAIT_2"; - case STATUS_WAIT_3: - return (PUCHAR)"STATUS_WAIT_3"; - case STATUS_WAIT_63: - return (PUCHAR)"STATUS_WAIT_63"; - case STATUS_ABANDONED: - return (PUCHAR)"STATUS_ABANDONED"; - case STATUS_ABANDONED_WAIT_63: - return (PUCHAR)"STATUS_ABANDONED_WAIT_63"; - case STATUS_USER_APC: - return (PUCHAR)"STATUS_USER_APC"; - case STATUS_KERNEL_APC: - return (PUCHAR)"STATUS_KERNEL_APC"; - case STATUS_ALERTED: - return (PUCHAR)"STATUS_ALERTED"; - case STATUS_TIMEOUT: - return (PUCHAR)"STATUS_TIMEOUT"; - case STATUS_PENDING: - return (PUCHAR)"STATUS_PENDING"; - case STATUS_REPARSE: - return (PUCHAR)"STATUS_REPARSE"; - case STATUS_MORE_ENTRIES: - return (PUCHAR)"STATUS_MORE_ENTRIES"; - case STATUS_NOT_ALL_ASSIGNED: - return (PUCHAR)"STATUS_NOT_ALL_ASSIGNED"; - case STATUS_SOME_NOT_MAPPED: - return (PUCHAR)"STATUS_SOME_NOT_MAPPED"; - case STATUS_OPLOCK_BREAK_IN_PROGRESS: - return (PUCHAR)"STATUS_OPLOCK_BREAK_IN_PROGRESS"; - case STATUS_VOLUME_MOUNTED: - return (PUCHAR)"STATUS_VOLUME_MOUNTED"; - case STATUS_RXACT_COMMITTED: - return (PUCHAR)"STATUS_RXACT_COMMITTED"; - case STATUS_NOTIFY_CLEANUP: - return (PUCHAR)"STATUS_NOTIFY_CLEANUP"; - case STATUS_NOTIFY_ENUM_DIR: - return (PUCHAR)"STATUS_NOTIFY_ENUM_DIR"; - case STATUS_NO_QUOTAS_FOR_ACCOUNT: - return (PUCHAR)"STATUS_NO_QUOTAS_FOR_ACCOUNT"; - case STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED: - return (PUCHAR)"STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED"; - case STATUS_PAGE_FAULT_TRANSITION: - return (PUCHAR)"STATUS_PAGE_FAULT_TRANSITION"; - case STATUS_PAGE_FAULT_DEMAND_ZERO: - return (PUCHAR)"STATUS_PAGE_FAULT_DEMAND_ZERO"; - case STATUS_PAGE_FAULT_COPY_ON_WRITE: - return (PUCHAR)"STATUS_PAGE_FAULT_COPY_ON_WRITE"; - case STATUS_PAGE_FAULT_GUARD_PAGE: - return (PUCHAR)"STATUS_PAGE_FAULT_GUARD_PAGE"; - case STATUS_PAGE_FAULT_PAGING_FILE: - return (PUCHAR)"STATUS_PAGE_FAULT_PAGING_FILE"; - case STATUS_CACHE_PAGE_LOCKED: - return (PUCHAR)"STATUS_CACHE_PAGE_LOCKED"; - case STATUS_CRASH_DUMP: - return (PUCHAR)"STATUS_CRASH_DUMP"; - case STATUS_BUFFER_ALL_ZEROS: - return (PUCHAR)"STATUS_BUFFER_ALL_ZEROS"; - case STATUS_REPARSE_OBJECT: - return (PUCHAR)"STATUS_REPARSE_OBJECT"; - case STATUS_RESOURCE_REQUIREMENTS_CHANGED: - return (PUCHAR)"STATUS_RESOURCE_REQUIREMENTS_CHANGED"; - case STATUS_TRANSLATION_COMPLETE: - return (PUCHAR)"STATUS_TRANSLATION_COMPLETE"; - case STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY: - return (PUCHAR)"STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY"; -#if (VER_PRODUCT_BUILD >= 2600) - case STATUS_NOTHING_TO_TERMINATE: - return (PUCHAR)"STATUS_NOTHING_TO_TERMINATE"; - case STATUS_PROCESS_NOT_IN_JOB: - return (PUCHAR)"STATUS_PROCESS_NOT_IN_JOB"; - case STATUS_PROCESS_IN_JOB: - return (PUCHAR)"STATUS_PROCESS_IN_JOB"; -#endif -#if (VER_PRODUCT_BUILD > 2600) - case STATUS_VOLSNAP_HIBERNATE_READY: - return (PUCHAR)"STATUS_VOLSNAP_HIBERNATE_READY"; - case STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY: - return (PUCHAR)"STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY"; -#endif -#if (VER_PRODUCT_BUILD >= 2600) - case STATUS_WAIT_FOR_OPLOCK: - return (PUCHAR)"STATUS_WAIT_FOR_OPLOCK"; -#endif - case DBG_EXCEPTION_HANDLED: - return (PUCHAR)"DBG_EXCEPTION_HANDLED"; - case DBG_CONTINUE: - return (PUCHAR)"DBG_CONTINUE"; - case STATUS_OBJECT_NAME_EXISTS: - return (PUCHAR)"STATUS_OBJECT_NAME_EXISTS"; - case STATUS_THREAD_WAS_SUSPENDED: - return (PUCHAR)"STATUS_THREAD_WAS_SUSPENDED"; - case STATUS_WORKING_SET_LIMIT_RANGE: - return (PUCHAR)"STATUS_WORKING_SET_LIMIT_RANGE"; - case STATUS_IMAGE_NOT_AT_BASE: - return (PUCHAR)"STATUS_IMAGE_NOT_AT_BASE"; - case STATUS_RXACT_STATE_CREATED: - return (PUCHAR)"STATUS_RXACT_STATE_CREATED"; - case STATUS_SEGMENT_NOTIFICATION: - return (PUCHAR)"STATUS_SEGMENT_NOTIFICATION"; - case STATUS_LOCAL_USER_SESSION_KEY: - return (PUCHAR)"STATUS_LOCAL_USER_SESSION_KEY"; - case STATUS_BAD_CURRENT_DIRECTORY: - return (PUCHAR)"STATUS_BAD_CURRENT_DIRECTORY"; - case STATUS_SERIAL_MORE_WRITES: - return (PUCHAR)"STATUS_SERIAL_MORE_WRITES"; - case STATUS_REGISTRY_RECOVERED: - return (PUCHAR)"STATUS_REGISTRY_RECOVERED"; - case STATUS_FT_READ_RECOVERY_FROM_BACKUP: - return (PUCHAR)"STATUS_FT_READ_RECOVERY_FROM_BACKUP"; - case STATUS_FT_WRITE_RECOVERY: - return (PUCHAR)"STATUS_FT_WRITE_RECOVERY"; - case STATUS_SERIAL_COUNTER_TIMEOUT: - return (PUCHAR)"STATUS_SERIAL_COUNTER_TIMEOUT"; - case STATUS_NULL_LM_PASSWORD: - return (PUCHAR)"STATUS_NULL_LM_PASSWORD"; - case STATUS_IMAGE_MACHINE_TYPE_MISMATCH: - return (PUCHAR)"STATUS_IMAGE_MACHINE_TYPE_MISMATCH"; - case STATUS_RECEIVE_PARTIAL: - return (PUCHAR)"STATUS_RECEIVE_PARTIAL"; - case STATUS_RECEIVE_EXPEDITED: - return (PUCHAR)"STATUS_RECEIVE_EXPEDITED"; - case STATUS_RECEIVE_PARTIAL_EXPEDITED: - return (PUCHAR)"STATUS_RECEIVE_PARTIAL_EXPEDITED"; - case STATUS_EVENT_DONE: - return (PUCHAR)"STATUS_EVENT_DONE"; - case STATUS_EVENT_PENDING: - return (PUCHAR)"STATUS_EVENT_PENDING"; - case STATUS_CHECKING_FILE_SYSTEM: - return (PUCHAR)"STATUS_CHECKING_FILE_SYSTEM"; - case STATUS_FATAL_APP_EXIT: - return (PUCHAR)"STATUS_FATAL_APP_EXIT"; - case STATUS_PREDEFINED_HANDLE: - return (PUCHAR)"STATUS_PREDEFINED_HANDLE"; - case STATUS_WAS_UNLOCKED: - return (PUCHAR)"STATUS_WAS_UNLOCKED"; - case STATUS_SERVICE_NOTIFICATION: - return (PUCHAR)"STATUS_SERVICE_NOTIFICATION"; - case STATUS_WAS_LOCKED: - return (PUCHAR)"STATUS_WAS_LOCKED"; - case STATUS_LOG_HARD_ERROR: - return (PUCHAR)"STATUS_LOG_HARD_ERROR"; - case STATUS_ALREADY_WIN32: - return (PUCHAR)"STATUS_ALREADY_WIN32"; - case STATUS_WX86_UNSIMULATE: - return (PUCHAR)"STATUS_WX86_UNSIMULATE"; - case STATUS_WX86_CONTINUE: - return (PUCHAR)"STATUS_WX86_CONTINUE"; - case STATUS_WX86_SINGLE_STEP: - return (PUCHAR)"STATUS_WX86_SINGLE_STEP"; - case STATUS_WX86_BREAKPOINT: - return (PUCHAR)"STATUS_WX86_BREAKPOINT"; - case STATUS_WX86_EXCEPTION_CONTINUE: - return (PUCHAR)"STATUS_WX86_EXCEPTION_CONTINUE"; - case STATUS_WX86_EXCEPTION_LASTCHANCE: - return (PUCHAR)"STATUS_WX86_EXCEPTION_LASTCHANCE"; - case STATUS_WX86_EXCEPTION_CHAIN: - return (PUCHAR)"STATUS_WX86_EXCEPTION_CHAIN"; - case STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE: - return (PUCHAR)"STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE"; - case STATUS_NO_YIELD_PERFORMED: - return (PUCHAR)"STATUS_NO_YIELD_PERFORMED"; - case STATUS_TIMER_RESUME_IGNORED: - return (PUCHAR)"STATUS_TIMER_RESUME_IGNORED"; - case STATUS_ARBITRATION_UNHANDLED: - return (PUCHAR)"STATUS_ARBITRATION_UNHANDLED"; - case STATUS_CARDBUS_NOT_SUPPORTED: - return (PUCHAR)"STATUS_CARDBUS_NOT_SUPPORTED"; - case STATUS_WX86_CREATEWX86TIB: - return (PUCHAR)"STATUS_WX86_CREATEWX86TIB"; - case STATUS_MP_PROCESSOR_MISMATCH: - return (PUCHAR)"STATUS_MP_PROCESSOR_MISMATCH"; -#if (VER_PRODUCT_BUILD >= 2600) - case STATUS_HIBERNATED: - return (PUCHAR)"STATUS_HIBERNATED"; - case STATUS_RESUME_HIBERNATION: - return (PUCHAR)"STATUS_RESUME_HIBERNATION"; -#endif -#if (VER_PRODUCT_BUILD > 2600) - case STATUS_FIRMWARE_UPDATED: - return (PUCHAR)"STATUS_FIRMWARE_UPDATED"; - case STATUS_DRIVERS_LEAKING_LOCKED_PAGES: - return (PUCHAR)"STATUS_DRIVERS_LEAKING_LOCKED_PAGES"; -#endif - case STATUS_WAKE_SYSTEM: - return (PUCHAR)"STATUS_WAKE_SYSTEM"; -#if (VER_PRODUCT_BUILD >= 2600) - case STATUS_DS_SHUTTING_DOWN: - return (PUCHAR)"STATUS_DS_SHUTTING_DOWN"; -#endif - case DBG_REPLY_LATER: - return (PUCHAR)"DBG_REPLY_LATER"; - case DBG_UNABLE_TO_PROVIDE_HANDLE: - return (PUCHAR)"DBG_UNABLE_TO_PROVIDE_HANDLE"; - case DBG_TERMINATE_THREAD: - return (PUCHAR)"DBG_TERMINATE_THREAD"; - case DBG_TERMINATE_PROCESS: - return (PUCHAR)"DBG_TERMINATE_PROCESS"; - case DBG_CONTROL_C: - return (PUCHAR)"DBG_CONTROL_C"; - case DBG_PRINTEXCEPTION_C: - return (PUCHAR)"DBG_PRINTEXCEPTION_C"; - case DBG_RIPEXCEPTION: - return (PUCHAR)"DBG_RIPEXCEPTION"; - case DBG_CONTROL_BREAK: - return (PUCHAR)"DBG_CONTROL_BREAK"; -#if (VER_PRODUCT_BUILD > 2600) - case DBG_COMMAND_EXCEPTION: - return (PUCHAR)"DBG_COMMAND_EXCEPTION"; -#endif - case RPC_NT_UUID_LOCAL_ONLY: - return (PUCHAR)"RPC_NT_UUID_LOCAL_ONLY"; - case RPC_NT_SEND_INCOMPLETE: - return (PUCHAR)"RPC_NT_SEND_INCOMPLETE"; - case STATUS_CTX_CDM_CONNECT: - return (PUCHAR)"STATUS_CTX_CDM_CONNECT"; - case STATUS_CTX_CDM_DISCONNECT: - return (PUCHAR)"STATUS_CTX_CDM_DISCONNECT"; - case STATUS_GUARD_PAGE_VIOLATION: - return (PUCHAR)"STATUS_GUARD_PAGE_VIOLATION"; - case STATUS_DATATYPE_MISALIGNMENT: - return (PUCHAR)"STATUS_DATATYPE_MISALIGNMENT"; - case STATUS_BREAKPOINT: - return (PUCHAR)"STATUS_BREAKPOINT"; - case STATUS_SINGLE_STEP: - return (PUCHAR)"STATUS_SINGLE_STEP"; - case STATUS_BUFFER_OVERFLOW: - return (PUCHAR)"STATUS_BUFFER_OVERFLOW"; - case STATUS_NO_MORE_FILES: - return (PUCHAR)"STATUS_NO_MORE_FILES"; - case STATUS_WAKE_SYSTEM_DEBUGGER: - return (PUCHAR)"STATUS_WAKE_SYSTEM_DEBUGGER"; - case STATUS_HANDLES_CLOSED: - return (PUCHAR)"STATUS_HANDLES_CLOSED"; - case STATUS_NO_INHERITANCE: - return (PUCHAR)"STATUS_NO_INHERITANCE"; - case STATUS_GUID_SUBSTITUTION_MADE: - return (PUCHAR)"STATUS_GUID_SUBSTITUTION_MADE"; - case STATUS_PARTIAL_COPY: - return (PUCHAR)"STATUS_PARTIAL_COPY"; - case STATUS_DEVICE_PAPER_EMPTY: - return (PUCHAR)"STATUS_DEVICE_PAPER_EMPTY"; - case STATUS_DEVICE_POWERED_OFF: - return (PUCHAR)"STATUS_DEVICE_POWERED_OFF"; - case STATUS_DEVICE_OFF_LINE: - return (PUCHAR)"STATUS_DEVICE_OFF_LINE"; - case STATUS_DEVICE_BUSY: - return (PUCHAR)"STATUS_DEVICE_BUSY"; - case STATUS_NO_MORE_EAS: - return (PUCHAR)"STATUS_NO_MORE_EAS"; - case STATUS_INVALID_EA_NAME: - return (PUCHAR)"STATUS_INVALID_EA_NAME"; - case STATUS_EA_LIST_INCONSISTENT: - return (PUCHAR)"STATUS_EA_LIST_INCONSISTENT"; - case STATUS_INVALID_EA_FLAG: - return (PUCHAR)"STATUS_INVALID_EA_FLAG"; - case STATUS_VERIFY_REQUIRED: - return (PUCHAR)"STATUS_VERIFY_REQUIRED"; - case STATUS_EXTRANEOUS_INFORMATION: - return (PUCHAR)"STATUS_EXTRANEOUS_INFORMATION"; - case STATUS_RXACT_COMMIT_NECESSARY: - return (PUCHAR)"STATUS_RXACT_COMMIT_NECESSARY"; - case STATUS_NO_MORE_ENTRIES: - return (PUCHAR)"STATUS_NO_MORE_ENTRIES"; - case STATUS_FILEMARK_DETECTED: - return (PUCHAR)"STATUS_FILEMARK_DETECTED"; - case STATUS_MEDIA_CHANGED: - return (PUCHAR)"STATUS_MEDIA_CHANGED"; - case STATUS_BUS_RESET: - return (PUCHAR)"STATUS_BUS_RESET"; - case STATUS_END_OF_MEDIA: - return (PUCHAR)"STATUS_END_OF_MEDIA"; - case STATUS_BEGINNING_OF_MEDIA: - return (PUCHAR)"STATUS_BEGINNING_OF_MEDIA"; - case STATUS_MEDIA_CHECK: - return (PUCHAR)"STATUS_MEDIA_CHECK"; - case STATUS_SETMARK_DETECTED: - return (PUCHAR)"STATUS_SETMARK_DETECTED"; - case STATUS_NO_DATA_DETECTED: - return (PUCHAR)"STATUS_NO_DATA_DETECTED"; - case STATUS_REDIRECTOR_HAS_OPEN_HANDLES: - return (PUCHAR)"STATUS_REDIRECTOR_HAS_OPEN_HANDLES"; - case STATUS_SERVER_HAS_OPEN_HANDLES: - return (PUCHAR)"STATUS_SERVER_HAS_OPEN_HANDLES"; - case STATUS_ALREADY_DISCONNECTED: - return (PUCHAR)"STATUS_ALREADY_DISCONNECTED"; - case STATUS_LONGJUMP: - return (PUCHAR)"STATUS_LONGJUMP"; -#if (VER_PRODUCT_BUILD >= 2600) - case STATUS_CLEANER_CARTRIDGE_INSTALLED: - return (PUCHAR)"STATUS_CLEANER_CARTRIDGE_INSTALLED"; - case STATUS_PLUGPLAY_QUERY_VETOED: - return (PUCHAR)"STATUS_PLUGPLAY_QUERY_VETOED"; - case STATUS_UNWIND_CONSOLIDATE: - return (PUCHAR)"STATUS_UNWIND_CONSOLIDATE"; -#endif -#if (VER_PRODUCT_BUILD > 2600) - case STATUS_REGISTRY_HIVE_RECOVERED: - return (PUCHAR)"STATUS_REGISTRY_HIVE_RECOVERED"; - case STATUS_DLL_MIGHT_BE_INSECURE: - return (PUCHAR)"STATUS_DLL_MIGHT_BE_INSECURE"; - case STATUS_DLL_MIGHT_BE_INCOMPATIBLE: - return (PUCHAR)"STATUS_DLL_MIGHT_BE_INCOMPATIBLE"; -#endif - case STATUS_DEVICE_REQUIRES_CLEANING: - return (PUCHAR)"STATUS_DEVICE_REQUIRES_CLEANING"; - case STATUS_DEVICE_DOOR_OPEN: - return (PUCHAR)"STATUS_DEVICE_DOOR_OPEN"; - case DBG_EXCEPTION_NOT_HANDLED: - return (PUCHAR)"DBG_EXCEPTION_NOT_HANDLED"; -#if (VER_PRODUCT_BUILD >= 2600) - case STATUS_CLUSTER_NODE_ALREADY_UP: - return (PUCHAR)"STATUS_CLUSTER_NODE_ALREADY_UP"; - case STATUS_CLUSTER_NODE_ALREADY_DOWN: - return (PUCHAR)"STATUS_CLUSTER_NODE_ALREADY_DOWN"; - case STATUS_CLUSTER_NETWORK_ALREADY_ONLINE: - return (PUCHAR)"STATUS_CLUSTER_NETWORK_ALREADY_ONLINE"; - case STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE: - return (PUCHAR)"STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE"; - case STATUS_CLUSTER_NODE_ALREADY_MEMBER: - return (PUCHAR)"STATUS_CLUSTER_NODE_ALREADY_MEMBER"; -#endif - case STATUS_UNSUCCESSFUL: - return (PUCHAR)"STATUS_UNSUCCESSFUL"; - case STATUS_NOT_IMPLEMENTED: - return (PUCHAR)"STATUS_NOT_IMPLEMENTED"; - case STATUS_INVALID_INFO_CLASS: - return (PUCHAR)"STATUS_INVALID_INFO_CLASS"; - case STATUS_INFO_LENGTH_MISMATCH: - return (PUCHAR)"STATUS_INFO_LENGTH_MISMATCH"; - case STATUS_ACCESS_VIOLATION: - return (PUCHAR)"STATUS_ACCESS_VIOLATION"; - case STATUS_IN_PAGE_ERROR: - return (PUCHAR)"STATUS_IN_PAGE_ERROR"; - case STATUS_PAGEFILE_QUOTA: - return (PUCHAR)"STATUS_PAGEFILE_QUOTA"; - case STATUS_INVALID_HANDLE: - return (PUCHAR)"STATUS_INVALID_HANDLE"; - case STATUS_BAD_INITIAL_STACK: - return (PUCHAR)"STATUS_BAD_INITIAL_STACK"; - case STATUS_BAD_INITIAL_PC: - return (PUCHAR)"STATUS_BAD_INITIAL_PC"; - case STATUS_INVALID_CID: - return (PUCHAR)"STATUS_INVALID_CID"; - case STATUS_TIMER_NOT_CANCELED: - return (PUCHAR)"STATUS_TIMER_NOT_CANCELED"; - case STATUS_INVALID_PARAMETER: - return (PUCHAR)"STATUS_INVALID_PARAMETER"; - case STATUS_NO_SUCH_DEVICE: - return (PUCHAR)"STATUS_NO_SUCH_DEVICE"; - case STATUS_NO_SUCH_FILE: - return (PUCHAR)"STATUS_NO_SUCH_FILE"; - case STATUS_INVALID_DEVICE_REQUEST: - return (PUCHAR)"STATUS_INVALID_DEVICE_REQUEST"; - case STATUS_END_OF_FILE: - return (PUCHAR)"STATUS_END_OF_FILE"; - case STATUS_WRONG_VOLUME: - return (PUCHAR)"STATUS_WRONG_VOLUME"; - case STATUS_NO_MEDIA_IN_DEVICE: - return (PUCHAR)"STATUS_NO_MEDIA_IN_DEVICE"; - case STATUS_UNRECOGNIZED_MEDIA: - return (PUCHAR)"STATUS_UNRECOGNIZED_MEDIA"; - case STATUS_NONEXISTENT_SECTOR: - return (PUCHAR)"STATUS_NONEXISTENT_SECTOR"; - case STATUS_MORE_PROCESSING_REQUIRED: - return (PUCHAR)"STATUS_MORE_PROCESSING_REQUIRED"; - case STATUS_NO_MEMORY: - return (PUCHAR)"STATUS_NO_MEMORY"; - case STATUS_CONFLICTING_ADDRESSES: - return (PUCHAR)"STATUS_CONFLICTING_ADDRESSES"; - case STATUS_NOT_MAPPED_VIEW: - return (PUCHAR)"STATUS_NOT_MAPPED_VIEW"; - case STATUS_UNABLE_TO_FREE_VM: - return (PUCHAR)"STATUS_UNABLE_TO_FREE_VM"; - case STATUS_UNABLE_TO_DELETE_SECTION: - return (PUCHAR)"STATUS_UNABLE_TO_DELETE_SECTION"; - case STATUS_INVALID_SYSTEM_SERVICE: - return (PUCHAR)"STATUS_INVALID_SYSTEM_SERVICE"; - case STATUS_ILLEGAL_INSTRUCTION: - return (PUCHAR)"STATUS_ILLEGAL_INSTRUCTION"; - case STATUS_INVALID_LOCK_SEQUENCE: - return (PUCHAR)"STATUS_INVALID_LOCK_SEQUENCE"; - case STATUS_INVALID_VIEW_SIZE: - return (PUCHAR)"STATUS_INVALID_VIEW_SIZE"; - case STATUS_INVALID_FILE_FOR_SECTION: - return (PUCHAR)"STATUS_INVALID_FILE_FOR_SECTION"; - case STATUS_ALREADY_COMMITTED: - return (PUCHAR)"STATUS_ALREADY_COMMITTED"; - case STATUS_ACCESS_DENIED: - return (PUCHAR)"STATUS_ACCESS_DENIED"; - case STATUS_BUFFER_TOO_SMALL: - return (PUCHAR)"STATUS_BUFFER_TOO_SMALL"; - case STATUS_OBJECT_TYPE_MISMATCH: - return (PUCHAR)"STATUS_OBJECT_TYPE_MISMATCH"; - case STATUS_NONCONTINUABLE_EXCEPTION: - return (PUCHAR)"STATUS_NONCONTINUABLE_EXCEPTION"; - case STATUS_INVALID_DISPOSITION: - return (PUCHAR)"STATUS_INVALID_DISPOSITION"; - case STATUS_UNWIND: - return (PUCHAR)"STATUS_UNWIND"; - case STATUS_BAD_STACK: - return (PUCHAR)"STATUS_BAD_STACK"; - case STATUS_INVALID_UNWIND_TARGET: - return (PUCHAR)"STATUS_INVALID_UNWIND_TARGET"; - case STATUS_NOT_LOCKED: - return (PUCHAR)"STATUS_NOT_LOCKED"; - case STATUS_PARITY_ERROR: - return (PUCHAR)"STATUS_PARITY_ERROR"; - case STATUS_UNABLE_TO_DECOMMIT_VM: - return (PUCHAR)"STATUS_UNABLE_TO_DECOMMIT_VM"; - case STATUS_NOT_COMMITTED: - return (PUCHAR)"STATUS_NOT_COMMITTED"; - case STATUS_INVALID_PORT_ATTRIBUTES: - return (PUCHAR)"STATUS_INVALID_PORT_ATTRIBUTES"; - case STATUS_PORT_MESSAGE_TOO_LONG: - return (PUCHAR)"STATUS_PORT_MESSAGE_TOO_LONG"; - case STATUS_INVALID_PARAMETER_MIX: - return (PUCHAR)"STATUS_INVALID_PARAMETER_MIX"; - case STATUS_INVALID_QUOTA_LOWER: - return (PUCHAR)"STATUS_INVALID_QUOTA_LOWER"; - case STATUS_DISK_CORRUPT_ERROR: - return (PUCHAR)"STATUS_DISK_CORRUPT_ERROR"; - case STATUS_OBJECT_NAME_INVALID: - return (PUCHAR)"STATUS_OBJECT_NAME_INVALID"; - case STATUS_OBJECT_NAME_NOT_FOUND: - return (PUCHAR)"STATUS_OBJECT_NAME_NOT_FOUND"; - case STATUS_OBJECT_NAME_COLLISION: - return (PUCHAR)"STATUS_OBJECT_NAME_COLLISION"; - case STATUS_PORT_DISCONNECTED: - return (PUCHAR)"STATUS_PORT_DISCONNECTED"; - case STATUS_DEVICE_ALREADY_ATTACHED: - return (PUCHAR)"STATUS_DEVICE_ALREADY_ATTACHED"; - case STATUS_OBJECT_PATH_INVALID: - return (PUCHAR)"STATUS_OBJECT_PATH_INVALID"; - case STATUS_OBJECT_PATH_NOT_FOUND: - return (PUCHAR)"STATUS_OBJECT_PATH_NOT_FOUND"; - case STATUS_OBJECT_PATH_SYNTAX_BAD: - return (PUCHAR)"STATUS_OBJECT_PATH_SYNTAX_BAD"; - case STATUS_DATA_OVERRUN: - return (PUCHAR)"STATUS_DATA_OVERRUN"; - case STATUS_DATA_LATE_ERROR: - return (PUCHAR)"STATUS_DATA_LATE_ERROR"; - case STATUS_DATA_ERROR: - return (PUCHAR)"STATUS_DATA_ERROR"; - case STATUS_CRC_ERROR: - return (PUCHAR)"STATUS_CRC_ERROR"; - case STATUS_SECTION_TOO_BIG: - return (PUCHAR)"STATUS_SECTION_TOO_BIG"; - case STATUS_PORT_CONNECTION_REFUSED: - return (PUCHAR)"STATUS_PORT_CONNECTION_REFUSED"; - case STATUS_INVALID_PORT_HANDLE: - return (PUCHAR)"STATUS_INVALID_PORT_HANDLE"; - case STATUS_SHARING_VIOLATION: - return (PUCHAR)"STATUS_SHARING_VIOLATION"; - case STATUS_QUOTA_EXCEEDED: - return (PUCHAR)"STATUS_QUOTA_EXCEEDED"; - case STATUS_INVALID_PAGE_PROTECTION: - return (PUCHAR)"STATUS_INVALID_PAGE_PROTECTION"; - case STATUS_MUTANT_NOT_OWNED: - return (PUCHAR)"STATUS_MUTANT_NOT_OWNED"; - case STATUS_SEMAPHORE_LIMIT_EXCEEDED: - return (PUCHAR)"STATUS_SEMAPHORE_LIMIT_EXCEEDED"; - case STATUS_PORT_ALREADY_SET: - return (PUCHAR)"STATUS_PORT_ALREADY_SET"; - case STATUS_SECTION_NOT_IMAGE: - return (PUCHAR)"STATUS_SECTION_NOT_IMAGE"; - case STATUS_SUSPEND_COUNT_EXCEEDED: - return (PUCHAR)"STATUS_SUSPEND_COUNT_EXCEEDED"; - case STATUS_THREAD_IS_TERMINATING: - return (PUCHAR)"STATUS_THREAD_IS_TERMINATING"; - case STATUS_BAD_WORKING_SET_LIMIT: - return (PUCHAR)"STATUS_BAD_WORKING_SET_LIMIT"; - case STATUS_INCOMPATIBLE_FILE_MAP: - return (PUCHAR)"STATUS_INCOMPATIBLE_FILE_MAP"; - case STATUS_SECTION_PROTECTION: - return (PUCHAR)"STATUS_SECTION_PROTECTION"; - case STATUS_EAS_NOT_SUPPORTED: - return (PUCHAR)"STATUS_EAS_NOT_SUPPORTED"; - case STATUS_EA_TOO_LARGE: - return (PUCHAR)"STATUS_EA_TOO_LARGE"; - case STATUS_NONEXISTENT_EA_ENTRY: - return (PUCHAR)"STATUS_NONEXISTENT_EA_ENTRY"; - case STATUS_NO_EAS_ON_FILE: - return (PUCHAR)"STATUS_NO_EAS_ON_FILE"; - case STATUS_EA_CORRUPT_ERROR: - return (PUCHAR)"STATUS_EA_CORRUPT_ERROR"; - case STATUS_FILE_LOCK_CONFLICT: - return (PUCHAR)"STATUS_FILE_LOCK_CONFLICT"; - case STATUS_LOCK_NOT_GRANTED: - return (PUCHAR)"STATUS_LOCK_NOT_GRANTED"; - case STATUS_DELETE_PENDING: - return (PUCHAR)"STATUS_DELETE_PENDING"; - case STATUS_CTL_FILE_NOT_SUPPORTED: - return (PUCHAR)"STATUS_CTL_FILE_NOT_SUPPORTED"; - case STATUS_UNKNOWN_REVISION: - return (PUCHAR)"STATUS_UNKNOWN_REVISION"; - case STATUS_REVISION_MISMATCH: - return (PUCHAR)"STATUS_REVISION_MISMATCH"; - case STATUS_INVALID_OWNER: - return (PUCHAR)"STATUS_INVALID_OWNER"; - case STATUS_INVALID_PRIMARY_GROUP: - return (PUCHAR)"STATUS_INVALID_PRIMARY_GROUP"; - case STATUS_NO_IMPERSONATION_TOKEN: - return (PUCHAR)"STATUS_NO_IMPERSONATION_TOKEN"; - case STATUS_CANT_DISABLE_MANDATORY: - return (PUCHAR)"STATUS_CANT_DISABLE_MANDATORY"; - case STATUS_NO_LOGON_SERVERS: - return (PUCHAR)"STATUS_NO_LOGON_SERVERS"; - case STATUS_NO_SUCH_LOGON_SESSION: - return (PUCHAR)"STATUS_NO_SUCH_LOGON_SESSION"; - case STATUS_NO_SUCH_PRIVILEGE: - return (PUCHAR)"STATUS_NO_SUCH_PRIVILEGE"; - case STATUS_PRIVILEGE_NOT_HELD: - return (PUCHAR)"STATUS_PRIVILEGE_NOT_HELD"; - case STATUS_INVALID_ACCOUNT_NAME: - return (PUCHAR)"STATUS_INVALID_ACCOUNT_NAME"; - case STATUS_USER_EXISTS: - return (PUCHAR)"STATUS_USER_EXISTS"; - case STATUS_NO_SUCH_USER: - return (PUCHAR)"STATUS_NO_SUCH_USER"; - case STATUS_GROUP_EXISTS: - return (PUCHAR)"STATUS_GROUP_EXISTS"; - case STATUS_NO_SUCH_GROUP: - return (PUCHAR)"STATUS_NO_SUCH_GROUP"; - case STATUS_MEMBER_IN_GROUP: - return (PUCHAR)"STATUS_MEMBER_IN_GROUP"; - case STATUS_MEMBER_NOT_IN_GROUP: - return (PUCHAR)"STATUS_MEMBER_NOT_IN_GROUP"; - case STATUS_LAST_ADMIN: - return (PUCHAR)"STATUS_LAST_ADMIN"; - case STATUS_WRONG_PASSWORD: - return (PUCHAR)"STATUS_WRONG_PASSWORD"; - case STATUS_ILL_FORMED_PASSWORD: - return (PUCHAR)"STATUS_ILL_FORMED_PASSWORD"; - case STATUS_PASSWORD_RESTRICTION: - return (PUCHAR)"STATUS_PASSWORD_RESTRICTION"; - case STATUS_LOGON_FAILURE: - return (PUCHAR)"STATUS_LOGON_FAILURE"; - case STATUS_ACCOUNT_RESTRICTION: - return (PUCHAR)"STATUS_ACCOUNT_RESTRICTION"; - case STATUS_INVALID_LOGON_HOURS: - return (PUCHAR)"STATUS_INVALID_LOGON_HOURS"; - case STATUS_INVALID_WORKSTATION: - return (PUCHAR)"STATUS_INVALID_WORKSTATION"; - case STATUS_PASSWORD_EXPIRED: - return (PUCHAR)"STATUS_PASSWORD_EXPIRED"; - case STATUS_ACCOUNT_DISABLED: - return (PUCHAR)"STATUS_ACCOUNT_DISABLED"; - case STATUS_NONE_MAPPED: - return (PUCHAR)"STATUS_NONE_MAPPED"; - case STATUS_TOO_MANY_LUIDS_REQUESTED: - return (PUCHAR)"STATUS_TOO_MANY_LUIDS_REQUESTED"; - case STATUS_LUIDS_EXHAUSTED: - return (PUCHAR)"STATUS_LUIDS_EXHAUSTED"; - case STATUS_INVALID_SUB_AUTHORITY: - return (PUCHAR)"STATUS_INVALID_SUB_AUTHORITY"; - case STATUS_INVALID_ACL: - return (PUCHAR)"STATUS_INVALID_ACL"; - case STATUS_INVALID_SID: - return (PUCHAR)"STATUS_INVALID_SID"; - case STATUS_INVALID_SECURITY_DESCR: - return (PUCHAR)"STATUS_INVALID_SECURITY_DESCR"; - case STATUS_PROCEDURE_NOT_FOUND: - return (PUCHAR)"STATUS_PROCEDURE_NOT_FOUND"; - case STATUS_INVALID_IMAGE_FORMAT: - return (PUCHAR)"STATUS_INVALID_IMAGE_FORMAT"; - case STATUS_NO_TOKEN: - return (PUCHAR)"STATUS_NO_TOKEN"; - case STATUS_BAD_INHERITANCE_ACL: - return (PUCHAR)"STATUS_BAD_INHERITANCE_ACL"; - case STATUS_RANGE_NOT_LOCKED: - return (PUCHAR)"STATUS_RANGE_NOT_LOCKED"; - case STATUS_DISK_FULL: - return (PUCHAR)"STATUS_DISK_FULL"; - case STATUS_SERVER_DISABLED: - return (PUCHAR)"STATUS_SERVER_DISABLED"; - case STATUS_SERVER_NOT_DISABLED: - return (PUCHAR)"STATUS_SERVER_NOT_DISABLED"; - case STATUS_TOO_MANY_GUIDS_REQUESTED: - return (PUCHAR)"STATUS_TOO_MANY_GUIDS_REQUESTED"; - case STATUS_GUIDS_EXHAUSTED: - return (PUCHAR)"STATUS_GUIDS_EXHAUSTED"; - case STATUS_INVALID_ID_AUTHORITY: - return (PUCHAR)"STATUS_INVALID_ID_AUTHORITY"; - case STATUS_AGENTS_EXHAUSTED: - return (PUCHAR)"STATUS_AGENTS_EXHAUSTED"; - case STATUS_INVALID_VOLUME_LABEL: - return (PUCHAR)"STATUS_INVALID_VOLUME_LABEL"; - case STATUS_SECTION_NOT_EXTENDED: - return (PUCHAR)"STATUS_SECTION_NOT_EXTENDED"; - case STATUS_NOT_MAPPED_DATA: - return (PUCHAR)"STATUS_NOT_MAPPED_DATA"; - case STATUS_RESOURCE_DATA_NOT_FOUND: - return (PUCHAR)"STATUS_RESOURCE_DATA_NOT_FOUND"; - case STATUS_RESOURCE_TYPE_NOT_FOUND: - return (PUCHAR)"STATUS_RESOURCE_TYPE_NOT_FOUND"; - case STATUS_RESOURCE_NAME_NOT_FOUND: - return (PUCHAR)"STATUS_RESOURCE_NAME_NOT_FOUND"; - case STATUS_ARRAY_BOUNDS_EXCEEDED: - return (PUCHAR)"STATUS_ARRAY_BOUNDS_EXCEEDED"; - case STATUS_FLOAT_DENORMAL_OPERAND: - return (PUCHAR)"STATUS_FLOAT_DENORMAL_OPERAND"; - case STATUS_FLOAT_DIVIDE_BY_ZERO: - return (PUCHAR)"STATUS_FLOAT_DIVIDE_BY_ZERO"; - case STATUS_FLOAT_INEXACT_RESULT: - return (PUCHAR)"STATUS_FLOAT_INEXACT_RESULT"; - case STATUS_FLOAT_INVALID_OPERATION: - return (PUCHAR)"STATUS_FLOAT_INVALID_OPERATION"; - case STATUS_FLOAT_OVERFLOW: - return (PUCHAR)"STATUS_FLOAT_OVERFLOW"; - case STATUS_FLOAT_STACK_CHECK: - return (PUCHAR)"STATUS_FLOAT_STACK_CHECK"; - case STATUS_FLOAT_UNDERFLOW: - return (PUCHAR)"STATUS_FLOAT_UNDERFLOW"; - case STATUS_INTEGER_DIVIDE_BY_ZERO: - return (PUCHAR)"STATUS_INTEGER_DIVIDE_BY_ZERO"; - case STATUS_INTEGER_OVERFLOW: - return (PUCHAR)"STATUS_INTEGER_OVERFLOW"; - case STATUS_PRIVILEGED_INSTRUCTION: - return (PUCHAR)"STATUS_PRIVILEGED_INSTRUCTION"; - case STATUS_TOO_MANY_PAGING_FILES: - return (PUCHAR)"STATUS_TOO_MANY_PAGING_FILES"; - case STATUS_FILE_INVALID: - return (PUCHAR)"STATUS_FILE_INVALID"; - case STATUS_ALLOTTED_SPACE_EXCEEDED: - return (PUCHAR)"STATUS_ALLOTTED_SPACE_EXCEEDED"; - case STATUS_INSUFFICIENT_RESOURCES: - return (PUCHAR)"STATUS_INSUFFICIENT_RESOURCES"; - case STATUS_DFS_EXIT_PATH_FOUND: - return (PUCHAR)"STATUS_DFS_EXIT_PATH_FOUND"; - case STATUS_DEVICE_DATA_ERROR: - return (PUCHAR)"STATUS_DEVICE_DATA_ERROR"; - case STATUS_DEVICE_NOT_CONNECTED: - return (PUCHAR)"STATUS_DEVICE_NOT_CONNECTED"; - case STATUS_DEVICE_POWER_FAILURE: - return (PUCHAR)"STATUS_DEVICE_POWER_FAILURE"; - case STATUS_FREE_VM_NOT_AT_BASE: - return (PUCHAR)"STATUS_FREE_VM_NOT_AT_BASE"; - case STATUS_MEMORY_NOT_ALLOCATED: - return (PUCHAR)"STATUS_MEMORY_NOT_ALLOCATED"; - case STATUS_WORKING_SET_QUOTA: - return (PUCHAR)"STATUS_WORKING_SET_QUOTA"; - case STATUS_MEDIA_WRITE_PROTECTED: - return (PUCHAR)"STATUS_MEDIA_WRITE_PROTECTED"; - case STATUS_DEVICE_NOT_READY: - return (PUCHAR)"STATUS_DEVICE_NOT_READY"; - case STATUS_INVALID_GROUP_ATTRIBUTES: - return (PUCHAR)"STATUS_INVALID_GROUP_ATTRIBUTES"; - case STATUS_BAD_IMPERSONATION_LEVEL: - return (PUCHAR)"STATUS_BAD_IMPERSONATION_LEVEL"; - case STATUS_CANT_OPEN_ANONYMOUS: - return (PUCHAR)"STATUS_CANT_OPEN_ANONYMOUS"; - case STATUS_BAD_VALIDATION_CLASS: - return (PUCHAR)"STATUS_BAD_VALIDATION_CLASS"; - case STATUS_BAD_TOKEN_TYPE: - return (PUCHAR)"STATUS_BAD_TOKEN_TYPE"; - case STATUS_BAD_MASTER_BOOT_RECORD: - return (PUCHAR)"STATUS_BAD_MASTER_BOOT_RECORD"; - case STATUS_INSTRUCTION_MISALIGNMENT: - return (PUCHAR)"STATUS_INSTRUCTION_MISALIGNMENT"; - case STATUS_INSTANCE_NOT_AVAILABLE: - return (PUCHAR)"STATUS_INSTANCE_NOT_AVAILABLE"; - case STATUS_PIPE_NOT_AVAILABLE: - return (PUCHAR)"STATUS_PIPE_NOT_AVAILABLE"; - case STATUS_INVALID_PIPE_STATE: - return (PUCHAR)"STATUS_INVALID_PIPE_STATE"; - case STATUS_PIPE_BUSY: - return (PUCHAR)"STATUS_PIPE_BUSY"; - case STATUS_ILLEGAL_FUNCTION: - return (PUCHAR)"STATUS_ILLEGAL_FUNCTION"; - case STATUS_PIPE_DISCONNECTED: - return (PUCHAR)"STATUS_PIPE_DISCONNECTED"; - case STATUS_PIPE_CLOSING: - return (PUCHAR)"STATUS_PIPE_CLOSING"; - case STATUS_PIPE_CONNECTED: - return (PUCHAR)"STATUS_PIPE_CONNECTED"; - case STATUS_PIPE_LISTENING: - return (PUCHAR)"STATUS_PIPE_LISTENING"; - case STATUS_INVALID_READ_MODE: - return (PUCHAR)"STATUS_INVALID_READ_MODE"; - case STATUS_IO_TIMEOUT: - return (PUCHAR)"STATUS_IO_TIMEOUT"; - case STATUS_FILE_FORCED_CLOSED: - return (PUCHAR)"STATUS_FILE_FORCED_CLOSED"; - case STATUS_PROFILING_NOT_STARTED: - return (PUCHAR)"STATUS_PROFILING_NOT_STARTED"; - case STATUS_PROFILING_NOT_STOPPED: - return (PUCHAR)"STATUS_PROFILING_NOT_STOPPED"; - case STATUS_COULD_NOT_INTERPRET: - return (PUCHAR)"STATUS_COULD_NOT_INTERPRET"; - case STATUS_FILE_IS_A_DIRECTORY: - return (PUCHAR)"STATUS_FILE_IS_A_DIRECTORY"; - case STATUS_NOT_SUPPORTED: - return (PUCHAR)"STATUS_NOT_SUPPORTED"; - case STATUS_REMOTE_NOT_LISTENING: - return (PUCHAR)"STATUS_REMOTE_NOT_LISTENING"; - case STATUS_DUPLICATE_NAME: - return (PUCHAR)"STATUS_DUPLICATE_NAME"; - case STATUS_BAD_NETWORK_PATH: - return (PUCHAR)"STATUS_BAD_NETWORK_PATH"; - case STATUS_NETWORK_BUSY: - return (PUCHAR)"STATUS_NETWORK_BUSY"; - case STATUS_DEVICE_DOES_NOT_EXIST: - return (PUCHAR)"STATUS_DEVICE_DOES_NOT_EXIST"; - case STATUS_TOO_MANY_COMMANDS: - return (PUCHAR)"STATUS_TOO_MANY_COMMANDS"; - case STATUS_ADAPTER_HARDWARE_ERROR: - return (PUCHAR)"STATUS_ADAPTER_HARDWARE_ERROR"; - case STATUS_INVALID_NETWORK_RESPONSE: - return (PUCHAR)"STATUS_INVALID_NETWORK_RESPONSE"; - case STATUS_UNEXPECTED_NETWORK_ERROR: - return (PUCHAR)"STATUS_UNEXPECTED_NETWORK_ERROR"; - case STATUS_BAD_REMOTE_ADAPTER: - return (PUCHAR)"STATUS_BAD_REMOTE_ADAPTER"; - case STATUS_PRINT_QUEUE_FULL: - return (PUCHAR)"STATUS_PRINT_QUEUE_FULL"; - case STATUS_NO_SPOOL_SPACE: - return (PUCHAR)"STATUS_NO_SPOOL_SPACE"; - case STATUS_PRINT_CANCELLED: - return (PUCHAR)"STATUS_PRINT_CANCELLED"; - case STATUS_NETWORK_NAME_DELETED: - return (PUCHAR)"STATUS_NETWORK_NAME_DELETED"; - case STATUS_NETWORK_ACCESS_DENIED: - return (PUCHAR)"STATUS_NETWORK_ACCESS_DENIED"; - case STATUS_BAD_DEVICE_TYPE: - return (PUCHAR)"STATUS_BAD_DEVICE_TYPE"; - case STATUS_BAD_NETWORK_NAME: - return (PUCHAR)"STATUS_BAD_NETWORK_NAME"; - case STATUS_TOO_MANY_NAMES: - return (PUCHAR)"STATUS_TOO_MANY_NAMES"; - case STATUS_TOO_MANY_SESSIONS: - return (PUCHAR)"STATUS_TOO_MANY_SESSIONS"; - case STATUS_SHARING_PAUSED: - return (PUCHAR)"STATUS_SHARING_PAUSED"; - case STATUS_REQUEST_NOT_ACCEPTED: - return (PUCHAR)"STATUS_REQUEST_NOT_ACCEPTED"; - case STATUS_REDIRECTOR_PAUSED: - return (PUCHAR)"STATUS_REDIRECTOR_PAUSED"; - case STATUS_NET_WRITE_FAULT: - return (PUCHAR)"STATUS_NET_WRITE_FAULT"; - case STATUS_PROFILING_AT_LIMIT: - return (PUCHAR)"STATUS_PROFILING_AT_LIMIT"; - case STATUS_NOT_SAME_DEVICE: - return (PUCHAR)"STATUS_NOT_SAME_DEVICE"; - case STATUS_FILE_RENAMED: - return (PUCHAR)"STATUS_FILE_RENAMED"; - case STATUS_VIRTUAL_CIRCUIT_CLOSED: - return (PUCHAR)"STATUS_VIRTUAL_CIRCUIT_CLOSED"; - case STATUS_NO_SECURITY_ON_OBJECT: - return (PUCHAR)"STATUS_NO_SECURITY_ON_OBJECT"; - case STATUS_CANT_WAIT: - return (PUCHAR)"STATUS_CANT_WAIT"; - case STATUS_PIPE_EMPTY: - return (PUCHAR)"STATUS_PIPE_EMPTY"; - case STATUS_CANT_ACCESS_DOMAIN_INFO: - return (PUCHAR)"STATUS_CANT_ACCESS_DOMAIN_INFO"; - case STATUS_CANT_TERMINATE_SELF: - return (PUCHAR)"STATUS_CANT_TERMINATE_SELF"; - case STATUS_INVALID_SERVER_STATE: - return (PUCHAR)"STATUS_INVALID_SERVER_STATE"; - case STATUS_INVALID_DOMAIN_STATE: - return (PUCHAR)"STATUS_INVALID_DOMAIN_STATE"; - case STATUS_INVALID_DOMAIN_ROLE: - return (PUCHAR)"STATUS_INVALID_DOMAIN_ROLE"; - case STATUS_NO_SUCH_DOMAIN: - return (PUCHAR)"STATUS_NO_SUCH_DOMAIN"; - case STATUS_DOMAIN_EXISTS: - return (PUCHAR)"STATUS_DOMAIN_EXISTS"; - case STATUS_DOMAIN_LIMIT_EXCEEDED: - return (PUCHAR)"STATUS_DOMAIN_LIMIT_EXCEEDED"; - case STATUS_OPLOCK_NOT_GRANTED: - return (PUCHAR)"STATUS_OPLOCK_NOT_GRANTED"; - case STATUS_INVALID_OPLOCK_PROTOCOL: - return (PUCHAR)"STATUS_INVALID_OPLOCK_PROTOCOL"; - case STATUS_INTERNAL_DB_CORRUPTION: - return (PUCHAR)"STATUS_INTERNAL_DB_CORRUPTION"; - case STATUS_INTERNAL_ERROR: - return (PUCHAR)"STATUS_INTERNAL_ERROR"; - case STATUS_GENERIC_NOT_MAPPED: - return (PUCHAR)"STATUS_GENERIC_NOT_MAPPED"; - case STATUS_BAD_DESCRIPTOR_FORMAT: - return (PUCHAR)"STATUS_BAD_DESCRIPTOR_FORMAT"; - case STATUS_INVALID_USER_BUFFER: - return (PUCHAR)"STATUS_INVALID_USER_BUFFER"; - case STATUS_UNEXPECTED_IO_ERROR: - return (PUCHAR)"STATUS_UNEXPECTED_IO_ERROR"; - case STATUS_UNEXPECTED_MM_CREATE_ERR: - return (PUCHAR)"STATUS_UNEXPECTED_MM_CREATE_ERR"; - case STATUS_UNEXPECTED_MM_MAP_ERROR: - return (PUCHAR)"STATUS_UNEXPECTED_MM_MAP_ERROR"; - case STATUS_UNEXPECTED_MM_EXTEND_ERR: - return (PUCHAR)"STATUS_UNEXPECTED_MM_EXTEND_ERR"; - case STATUS_NOT_LOGON_PROCESS: - return (PUCHAR)"STATUS_NOT_LOGON_PROCESS"; - case STATUS_LOGON_SESSION_EXISTS: - return (PUCHAR)"STATUS_LOGON_SESSION_EXISTS"; - case STATUS_INVALID_PARAMETER_1: - return (PUCHAR)"STATUS_INVALID_PARAMETER_1"; - case STATUS_INVALID_PARAMETER_2: - return (PUCHAR)"STATUS_INVALID_PARAMETER_2"; - case STATUS_INVALID_PARAMETER_3: - return (PUCHAR)"STATUS_INVALID_PARAMETER_3"; - case STATUS_INVALID_PARAMETER_4: - return (PUCHAR)"STATUS_INVALID_PARAMETER_4"; - case STATUS_INVALID_PARAMETER_5: - return (PUCHAR)"STATUS_INVALID_PARAMETER_5"; - case STATUS_INVALID_PARAMETER_6: - return (PUCHAR)"STATUS_INVALID_PARAMETER_6"; - case STATUS_INVALID_PARAMETER_7: - return (PUCHAR)"STATUS_INVALID_PARAMETER_7"; - case STATUS_INVALID_PARAMETER_8: - return (PUCHAR)"STATUS_INVALID_PARAMETER_8"; - case STATUS_INVALID_PARAMETER_9: - return (PUCHAR)"STATUS_INVALID_PARAMETER_9"; - case STATUS_INVALID_PARAMETER_10: - return (PUCHAR)"STATUS_INVALID_PARAMETER_10"; - case STATUS_INVALID_PARAMETER_11: - return (PUCHAR)"STATUS_INVALID_PARAMETER_11"; - case STATUS_INVALID_PARAMETER_12: - return (PUCHAR)"STATUS_INVALID_PARAMETER_12"; - case STATUS_REDIRECTOR_NOT_STARTED: - return (PUCHAR)"STATUS_REDIRECTOR_NOT_STARTED"; - case STATUS_REDIRECTOR_STARTED: - return (PUCHAR)"STATUS_REDIRECTOR_STARTED"; - case STATUS_STACK_OVERFLOW: - return (PUCHAR)"STATUS_STACK_OVERFLOW"; - case STATUS_NO_SUCH_PACKAGE: - return (PUCHAR)"STATUS_NO_SUCH_PACKAGE"; - case STATUS_BAD_FUNCTION_TABLE: - return (PUCHAR)"STATUS_BAD_FUNCTION_TABLE"; - case STATUS_VARIABLE_NOT_FOUND: - return (PUCHAR)"STATUS_VARIABLE_NOT_FOUND"; - case STATUS_DIRECTORY_NOT_EMPTY: - return (PUCHAR)"STATUS_DIRECTORY_NOT_EMPTY"; - case STATUS_FILE_CORRUPT_ERROR: - return (PUCHAR)"STATUS_FILE_CORRUPT_ERROR"; - case STATUS_NOT_A_DIRECTORY: - return (PUCHAR)"STATUS_NOT_A_DIRECTORY"; - case STATUS_BAD_LOGON_SESSION_STATE: - return (PUCHAR)"STATUS_BAD_LOGON_SESSION_STATE"; - case STATUS_LOGON_SESSION_COLLISION: - return (PUCHAR)"STATUS_LOGON_SESSION_COLLISION"; - case STATUS_NAME_TOO_LONG: - return (PUCHAR)"STATUS_NAME_TOO_LONG"; - case STATUS_FILES_OPEN: - return (PUCHAR)"STATUS_FILES_OPEN"; - case STATUS_CONNECTION_IN_USE: - return (PUCHAR)"STATUS_CONNECTION_IN_USE"; - case STATUS_MESSAGE_NOT_FOUND: - return (PUCHAR)"STATUS_MESSAGE_NOT_FOUND"; - case STATUS_PROCESS_IS_TERMINATING: - return (PUCHAR)"STATUS_PROCESS_IS_TERMINATING"; - case STATUS_INVALID_LOGON_TYPE: - return (PUCHAR)"STATUS_INVALID_LOGON_TYPE"; - case STATUS_NO_GUID_TRANSLATION: - return (PUCHAR)"STATUS_NO_GUID_TRANSLATION"; - case STATUS_CANNOT_IMPERSONATE: - return (PUCHAR)"STATUS_CANNOT_IMPERSONATE"; - case STATUS_IMAGE_ALREADY_LOADED: - return (PUCHAR)"STATUS_IMAGE_ALREADY_LOADED"; - case STATUS_ABIOS_NOT_PRESENT: - return (PUCHAR)"STATUS_ABIOS_NOT_PRESENT"; - case STATUS_ABIOS_LID_NOT_EXIST: - return (PUCHAR)"STATUS_ABIOS_LID_NOT_EXIST"; - case STATUS_ABIOS_LID_ALREADY_OWNED: - return (PUCHAR)"STATUS_ABIOS_LID_ALREADY_OWNED"; - case STATUS_ABIOS_NOT_LID_OWNER: - return (PUCHAR)"STATUS_ABIOS_NOT_LID_OWNER"; - case STATUS_ABIOS_INVALID_COMMAND: - return (PUCHAR)"STATUS_ABIOS_INVALID_COMMAND"; - case STATUS_ABIOS_INVALID_LID: - return (PUCHAR)"STATUS_ABIOS_INVALID_LID"; - case STATUS_ABIOS_SELECTOR_NOT_AVAILABLE: - return (PUCHAR)"STATUS_ABIOS_SELECTOR_NOT_AVAILABLE"; - case STATUS_ABIOS_INVALID_SELECTOR: - return (PUCHAR)"STATUS_ABIOS_INVALID_SELECTOR"; - case STATUS_NO_LDT: - return (PUCHAR)"STATUS_NO_LDT"; - case STATUS_INVALID_LDT_SIZE: - return (PUCHAR)"STATUS_INVALID_LDT_SIZE"; - case STATUS_INVALID_LDT_OFFSET: - return (PUCHAR)"STATUS_INVALID_LDT_OFFSET"; - case STATUS_INVALID_LDT_DESCRIPTOR: - return (PUCHAR)"STATUS_INVALID_LDT_DESCRIPTOR"; - case STATUS_INVALID_IMAGE_NE_FORMAT: - return (PUCHAR)"STATUS_INVALID_IMAGE_NE_FORMAT"; - case STATUS_RXACT_INVALID_STATE: - return (PUCHAR)"STATUS_RXACT_INVALID_STATE"; - case STATUS_RXACT_COMMIT_FAILURE: - return (PUCHAR)"STATUS_RXACT_COMMIT_FAILURE"; - case STATUS_MAPPED_FILE_SIZE_ZERO: - return (PUCHAR)"STATUS_MAPPED_FILE_SIZE_ZERO"; - case STATUS_TOO_MANY_OPENED_FILES: - return (PUCHAR)"STATUS_TOO_MANY_OPENED_FILES"; - case STATUS_CANCELLED: - return (PUCHAR)"STATUS_CANCELLED"; - case STATUS_CANNOT_DELETE: - return (PUCHAR)"STATUS_CANNOT_DELETE"; - case STATUS_INVALID_COMPUTER_NAME: - return (PUCHAR)"STATUS_INVALID_COMPUTER_NAME"; - case STATUS_FILE_DELETED: - return (PUCHAR)"STATUS_FILE_DELETED"; - case STATUS_SPECIAL_ACCOUNT: - return (PUCHAR)"STATUS_SPECIAL_ACCOUNT"; - case STATUS_SPECIAL_GROUP: - return (PUCHAR)"STATUS_SPECIAL_GROUP"; - case STATUS_SPECIAL_USER: - return (PUCHAR)"STATUS_SPECIAL_USER"; - case STATUS_MEMBERS_PRIMARY_GROUP: - return (PUCHAR)"STATUS_MEMBERS_PRIMARY_GROUP"; - case STATUS_FILE_CLOSED: - return (PUCHAR)"STATUS_FILE_CLOSED"; - case STATUS_TOO_MANY_THREADS: - return (PUCHAR)"STATUS_TOO_MANY_THREADS"; - case STATUS_THREAD_NOT_IN_PROCESS: - return (PUCHAR)"STATUS_THREAD_NOT_IN_PROCESS"; - case STATUS_TOKEN_ALREADY_IN_USE: - return (PUCHAR)"STATUS_TOKEN_ALREADY_IN_USE"; - case STATUS_PAGEFILE_QUOTA_EXCEEDED: - return (PUCHAR)"STATUS_PAGEFILE_QUOTA_EXCEEDED"; - case STATUS_COMMITMENT_LIMIT: - return (PUCHAR)"STATUS_COMMITMENT_LIMIT"; - case STATUS_INVALID_IMAGE_LE_FORMAT: - return (PUCHAR)"STATUS_INVALID_IMAGE_LE_FORMAT"; - case STATUS_INVALID_IMAGE_NOT_MZ: - return (PUCHAR)"STATUS_INVALID_IMAGE_NOT_MZ"; - case STATUS_INVALID_IMAGE_PROTECT: - return (PUCHAR)"STATUS_INVALID_IMAGE_PROTECT"; - case STATUS_INVALID_IMAGE_WIN_16: - return (PUCHAR)"STATUS_INVALID_IMAGE_WIN_16"; - case STATUS_LOGON_SERVER_CONFLICT: - return (PUCHAR)"STATUS_LOGON_SERVER_CONFLICT"; - case STATUS_TIME_DIFFERENCE_AT_DC: - return (PUCHAR)"STATUS_TIME_DIFFERENCE_AT_DC"; - case STATUS_SYNCHRONIZATION_REQUIRED: - return (PUCHAR)"STATUS_SYNCHRONIZATION_REQUIRED"; - case STATUS_DLL_NOT_FOUND: - return (PUCHAR)"STATUS_DLL_NOT_FOUND"; - case STATUS_OPEN_FAILED: - return (PUCHAR)"STATUS_OPEN_FAILED"; - case STATUS_IO_PRIVILEGE_FAILED: - return (PUCHAR)"STATUS_IO_PRIVILEGE_FAILED"; - case STATUS_ORDINAL_NOT_FOUND: - return (PUCHAR)"STATUS_ORDINAL_NOT_FOUND"; - case STATUS_ENTRYPOINT_NOT_FOUND: - return (PUCHAR)"STATUS_ENTRYPOINT_NOT_FOUND"; - case STATUS_CONTROL_C_EXIT: - return (PUCHAR)"STATUS_CONTROL_C_EXIT"; - case STATUS_LOCAL_DISCONNECT: - return (PUCHAR)"STATUS_LOCAL_DISCONNECT"; - case STATUS_REMOTE_DISCONNECT: - return (PUCHAR)"STATUS_REMOTE_DISCONNECT"; - case STATUS_REMOTE_RESOURCES: - return (PUCHAR)"STATUS_REMOTE_RESOURCES"; - case STATUS_LINK_FAILED: - return (PUCHAR)"STATUS_LINK_FAILED"; - case STATUS_LINK_TIMEOUT: - return (PUCHAR)"STATUS_LINK_TIMEOUT"; - case STATUS_INVALID_CONNECTION: - return (PUCHAR)"STATUS_INVALID_CONNECTION"; - case STATUS_INVALID_ADDRESS: - return (PUCHAR)"STATUS_INVALID_ADDRESS"; - case STATUS_DLL_INIT_FAILED: - return (PUCHAR)"STATUS_DLL_INIT_FAILED"; - case STATUS_MISSING_SYSTEMFILE: - return (PUCHAR)"STATUS_MISSING_SYSTEMFILE"; - case STATUS_UNHANDLED_EXCEPTION: - return (PUCHAR)"STATUS_UNHANDLED_EXCEPTION"; - case STATUS_APP_INIT_FAILURE: - return (PUCHAR)"STATUS_APP_INIT_FAILURE"; - case STATUS_PAGEFILE_CREATE_FAILED: - return (PUCHAR)"STATUS_PAGEFILE_CREATE_FAILED"; - case STATUS_NO_PAGEFILE: - return (PUCHAR)"STATUS_NO_PAGEFILE"; - case STATUS_INVALID_LEVEL: - return (PUCHAR)"STATUS_INVALID_LEVEL"; - case STATUS_WRONG_PASSWORD_CORE: - return (PUCHAR)"STATUS_WRONG_PASSWORD_CORE"; - case STATUS_ILLEGAL_FLOAT_CONTEXT: - return (PUCHAR)"STATUS_ILLEGAL_FLOAT_CONTEXT"; - case STATUS_PIPE_BROKEN: - return (PUCHAR)"STATUS_PIPE_BROKEN"; - case STATUS_REGISTRY_CORRUPT: - return (PUCHAR)"STATUS_REGISTRY_CORRUPT"; - case STATUS_REGISTRY_IO_FAILED: - return (PUCHAR)"STATUS_REGISTRY_IO_FAILED"; - case STATUS_NO_EVENT_PAIR: - return (PUCHAR)"STATUS_NO_EVENT_PAIR"; - case STATUS_UNRECOGNIZED_VOLUME: - return (PUCHAR)"STATUS_UNRECOGNIZED_VOLUME"; - case STATUS_SERIAL_NO_DEVICE_INITED: - return (PUCHAR)"STATUS_SERIAL_NO_DEVICE_INITED"; - case STATUS_NO_SUCH_ALIAS: - return (PUCHAR)"STATUS_NO_SUCH_ALIAS"; - case STATUS_MEMBER_NOT_IN_ALIAS: - return (PUCHAR)"STATUS_MEMBER_NOT_IN_ALIAS"; - case STATUS_MEMBER_IN_ALIAS: - return (PUCHAR)"STATUS_MEMBER_IN_ALIAS"; - case STATUS_ALIAS_EXISTS: - return (PUCHAR)"STATUS_ALIAS_EXISTS"; - case STATUS_LOGON_NOT_GRANTED: - return (PUCHAR)"STATUS_LOGON_NOT_GRANTED"; - case STATUS_TOO_MANY_SECRETS: - return (PUCHAR)"STATUS_TOO_MANY_SECRETS"; - case STATUS_SECRET_TOO_LONG: - return (PUCHAR)"STATUS_SECRET_TOO_LONG"; - case STATUS_INTERNAL_DB_ERROR: - return (PUCHAR)"STATUS_INTERNAL_DB_ERROR"; - case STATUS_FULLSCREEN_MODE: - return (PUCHAR)"STATUS_FULLSCREEN_MODE"; - case STATUS_TOO_MANY_CONTEXT_IDS: - return (PUCHAR)"STATUS_TOO_MANY_CONTEXT_IDS"; - case STATUS_LOGON_TYPE_NOT_GRANTED: - return (PUCHAR)"STATUS_LOGON_TYPE_NOT_GRANTED"; - case STATUS_NOT_REGISTRY_FILE: - return (PUCHAR)"STATUS_NOT_REGISTRY_FILE"; - case STATUS_NT_CROSS_ENCRYPTION_REQUIRED: - return (PUCHAR)"STATUS_NT_CROSS_ENCRYPTION_REQUIRED"; - case STATUS_DOMAIN_CTRLR_CONFIG_ERROR: - return (PUCHAR)"STATUS_DOMAIN_CTRLR_CONFIG_ERROR"; - case STATUS_FT_MISSING_MEMBER: - return (PUCHAR)"STATUS_FT_MISSING_MEMBER"; - case STATUS_ILL_FORMED_SERVICE_ENTRY: - return (PUCHAR)"STATUS_ILL_FORMED_SERVICE_ENTRY"; - case STATUS_ILLEGAL_CHARACTER: - return (PUCHAR)"STATUS_ILLEGAL_CHARACTER"; - case STATUS_UNMAPPABLE_CHARACTER: - return (PUCHAR)"STATUS_UNMAPPABLE_CHARACTER"; - case STATUS_UNDEFINED_CHARACTER: - return (PUCHAR)"STATUS_UNDEFINED_CHARACTER"; - case STATUS_FLOPPY_VOLUME: - return (PUCHAR)"STATUS_FLOPPY_VOLUME"; - case STATUS_FLOPPY_ID_MARK_NOT_FOUND: - return (PUCHAR)"STATUS_FLOPPY_ID_MARK_NOT_FOUND"; - case STATUS_FLOPPY_WRONG_CYLINDER: - return (PUCHAR)"STATUS_FLOPPY_WRONG_CYLINDER"; - case STATUS_FLOPPY_UNKNOWN_ERROR: - return (PUCHAR)"STATUS_FLOPPY_UNKNOWN_ERROR"; - case STATUS_FLOPPY_BAD_REGISTERS: - return (PUCHAR)"STATUS_FLOPPY_BAD_REGISTERS"; - case STATUS_DISK_RECALIBRATE_FAILED: - return (PUCHAR)"STATUS_DISK_RECALIBRATE_FAILED"; - case STATUS_DISK_OPERATION_FAILED: - return (PUCHAR)"STATUS_DISK_OPERATION_FAILED"; - case STATUS_DISK_RESET_FAILED: - return (PUCHAR)"STATUS_DISK_RESET_FAILED"; - case STATUS_SHARED_IRQ_BUSY: - return (PUCHAR)"STATUS_SHARED_IRQ_BUSY"; - case STATUS_FT_ORPHANING: - return (PUCHAR)"STATUS_FT_ORPHANING"; - case STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT: - return (PUCHAR)"STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT"; - case STATUS_PARTITION_FAILURE: - return (PUCHAR)"STATUS_PARTITION_FAILURE"; - case STATUS_INVALID_BLOCK_LENGTH: - return (PUCHAR)"STATUS_INVALID_BLOCK_LENGTH"; - case STATUS_DEVICE_NOT_PARTITIONED: - return (PUCHAR)"STATUS_DEVICE_NOT_PARTITIONED"; - case STATUS_UNABLE_TO_LOCK_MEDIA: - return (PUCHAR)"STATUS_UNABLE_TO_LOCK_MEDIA"; - case STATUS_UNABLE_TO_UNLOAD_MEDIA: - return (PUCHAR)"STATUS_UNABLE_TO_UNLOAD_MEDIA"; - case STATUS_EOM_OVERFLOW: - return (PUCHAR)"STATUS_EOM_OVERFLOW"; - case STATUS_NO_MEDIA: - return (PUCHAR)"STATUS_NO_MEDIA"; - case STATUS_NO_SUCH_MEMBER: - return (PUCHAR)"STATUS_NO_SUCH_MEMBER"; - case STATUS_INVALID_MEMBER: - return (PUCHAR)"STATUS_INVALID_MEMBER"; - case STATUS_KEY_DELETED: - return (PUCHAR)"STATUS_KEY_DELETED"; - case STATUS_NO_LOG_SPACE: - return (PUCHAR)"STATUS_NO_LOG_SPACE"; - case STATUS_TOO_MANY_SIDS: - return (PUCHAR)"STATUS_TOO_MANY_SIDS"; - case STATUS_LM_CROSS_ENCRYPTION_REQUIRED: - return (PUCHAR)"STATUS_LM_CROSS_ENCRYPTION_REQUIRED"; - case STATUS_KEY_HAS_CHILDREN: - return (PUCHAR)"STATUS_KEY_HAS_CHILDREN"; - case STATUS_CHILD_MUST_BE_VOLATILE: - return (PUCHAR)"STATUS_CHILD_MUST_BE_VOLATILE"; - case STATUS_DEVICE_CONFIGURATION_ERROR: - return (PUCHAR)"STATUS_DEVICE_CONFIGURATION_ERROR"; - case STATUS_DRIVER_INTERNAL_ERROR: - return (PUCHAR)"STATUS_DRIVER_INTERNAL_ERROR"; - case STATUS_INVALID_DEVICE_STATE: - return (PUCHAR)"STATUS_INVALID_DEVICE_STATE"; - case STATUS_IO_DEVICE_ERROR: - return (PUCHAR)"STATUS_IO_DEVICE_ERROR"; - case STATUS_DEVICE_PROTOCOL_ERROR: - return (PUCHAR)"STATUS_DEVICE_PROTOCOL_ERROR"; - case STATUS_BACKUP_CONTROLLER: - return (PUCHAR)"STATUS_BACKUP_CONTROLLER"; - case STATUS_LOG_FILE_FULL: - return (PUCHAR)"STATUS_LOG_FILE_FULL"; - case STATUS_TOO_LATE: - return (PUCHAR)"STATUS_TOO_LATE"; - case STATUS_NO_TRUST_LSA_SECRET: - return (PUCHAR)"STATUS_NO_TRUST_LSA_SECRET"; - case STATUS_NO_TRUST_SAM_ACCOUNT: - return (PUCHAR)"STATUS_NO_TRUST_SAM_ACCOUNT"; - case STATUS_TRUSTED_DOMAIN_FAILURE: - return (PUCHAR)"STATUS_TRUSTED_DOMAIN_FAILURE"; - case STATUS_TRUSTED_RELATIONSHIP_FAILURE: - return (PUCHAR)"STATUS_TRUSTED_RELATIONSHIP_FAILURE"; - case STATUS_EVENTLOG_FILE_CORRUPT: - return (PUCHAR)"STATUS_EVENTLOG_FILE_CORRUPT"; - case STATUS_EVENTLOG_CANT_START: - return (PUCHAR)"STATUS_EVENTLOG_CANT_START"; - case STATUS_TRUST_FAILURE: - return (PUCHAR)"STATUS_TRUST_FAILURE"; - case STATUS_MUTANT_LIMIT_EXCEEDED: - return (PUCHAR)"STATUS_MUTANT_LIMIT_EXCEEDED"; - case STATUS_NETLOGON_NOT_STARTED: - return (PUCHAR)"STATUS_NETLOGON_NOT_STARTED"; - case STATUS_ACCOUNT_EXPIRED: - return (PUCHAR)"STATUS_ACCOUNT_EXPIRED"; - case STATUS_POSSIBLE_DEADLOCK: - return (PUCHAR)"STATUS_POSSIBLE_DEADLOCK"; - case STATUS_NETWORK_CREDENTIAL_CONFLICT: - return (PUCHAR)"STATUS_NETWORK_CREDENTIAL_CONFLICT"; - case STATUS_REMOTE_SESSION_LIMIT: - return (PUCHAR)"STATUS_REMOTE_SESSION_LIMIT"; - case STATUS_EVENTLOG_FILE_CHANGED: - return (PUCHAR)"STATUS_EVENTLOG_FILE_CHANGED"; - case STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT: - return (PUCHAR)"STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT"; - case STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT: - return (PUCHAR)"STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT"; - case STATUS_NOLOGON_SERVER_TRUST_ACCOUNT: - return (PUCHAR)"STATUS_NOLOGON_SERVER_TRUST_ACCOUNT"; - case STATUS_DOMAIN_TRUST_INCONSISTENT: - return (PUCHAR)"STATUS_DOMAIN_TRUST_INCONSISTENT"; - case STATUS_FS_DRIVER_REQUIRED: - return (PUCHAR)"STATUS_FS_DRIVER_REQUIRED"; - case STATUS_NO_USER_SESSION_KEY: - return (PUCHAR)"STATUS_NO_USER_SESSION_KEY"; - case STATUS_USER_SESSION_DELETED: - return (PUCHAR)"STATUS_USER_SESSION_DELETED"; - case STATUS_RESOURCE_LANG_NOT_FOUND: - return (PUCHAR)"STATUS_RESOURCE_LANG_NOT_FOUND"; - case STATUS_INSUFF_SERVER_RESOURCES: - return (PUCHAR)"STATUS_INSUFF_SERVER_RESOURCES"; - case STATUS_INVALID_BUFFER_SIZE: - return (PUCHAR)"STATUS_INVALID_BUFFER_SIZE"; - case STATUS_INVALID_ADDRESS_COMPONENT: - return (PUCHAR)"STATUS_INVALID_ADDRESS_COMPONENT"; - case STATUS_INVALID_ADDRESS_WILDCARD: - return (PUCHAR)"STATUS_INVALID_ADDRESS_WILDCARD"; - case STATUS_TOO_MANY_ADDRESSES: - return (PUCHAR)"STATUS_TOO_MANY_ADDRESSES"; - case STATUS_ADDRESS_ALREADY_EXISTS: - return (PUCHAR)"STATUS_ADDRESS_ALREADY_EXISTS"; - case STATUS_ADDRESS_CLOSED: - return (PUCHAR)"STATUS_ADDRESS_CLOSED"; - case STATUS_CONNECTION_DISCONNECTED: - return (PUCHAR)"STATUS_CONNECTION_DISCONNECTED"; - case STATUS_CONNECTION_RESET: - return (PUCHAR)"STATUS_CONNECTION_RESET"; - case STATUS_TOO_MANY_NODES: - return (PUCHAR)"STATUS_TOO_MANY_NODES"; - case STATUS_TRANSACTION_ABORTED: - return (PUCHAR)"STATUS_TRANSACTION_ABORTED"; - case STATUS_TRANSACTION_TIMED_OUT: - return (PUCHAR)"STATUS_TRANSACTION_TIMED_OUT"; - case STATUS_TRANSACTION_NO_RELEASE: - return (PUCHAR)"STATUS_TRANSACTION_NO_RELEASE"; - case STATUS_TRANSACTION_NO_MATCH: - return (PUCHAR)"STATUS_TRANSACTION_NO_MATCH"; - case STATUS_TRANSACTION_RESPONDED: - return (PUCHAR)"STATUS_TRANSACTION_RESPONDED"; - case STATUS_TRANSACTION_INVALID_ID: - return (PUCHAR)"STATUS_TRANSACTION_INVALID_ID"; - case STATUS_TRANSACTION_INVALID_TYPE: - return (PUCHAR)"STATUS_TRANSACTION_INVALID_TYPE"; - case STATUS_NOT_SERVER_SESSION: - return (PUCHAR)"STATUS_NOT_SERVER_SESSION"; - case STATUS_NOT_CLIENT_SESSION: - return (PUCHAR)"STATUS_NOT_CLIENT_SESSION"; - case STATUS_CANNOT_LOAD_REGISTRY_FILE: - return (PUCHAR)"STATUS_CANNOT_LOAD_REGISTRY_FILE"; - case STATUS_DEBUG_ATTACH_FAILED: - return (PUCHAR)"STATUS_DEBUG_ATTACH_FAILED"; - case STATUS_SYSTEM_PROCESS_TERMINATED: - return (PUCHAR)"STATUS_SYSTEM_PROCESS_TERMINATED"; - case STATUS_DATA_NOT_ACCEPTED: - return (PUCHAR)"STATUS_DATA_NOT_ACCEPTED"; - case STATUS_NO_BROWSER_SERVERS_FOUND: - return (PUCHAR)"STATUS_NO_BROWSER_SERVERS_FOUND"; - case STATUS_VDM_HARD_ERROR: - return (PUCHAR)"STATUS_VDM_HARD_ERROR"; - case STATUS_DRIVER_CANCEL_TIMEOUT: - return (PUCHAR)"STATUS_DRIVER_CANCEL_TIMEOUT"; - case STATUS_REPLY_MESSAGE_MISMATCH: - return (PUCHAR)"STATUS_REPLY_MESSAGE_MISMATCH"; - case STATUS_MAPPED_ALIGNMENT: - return (PUCHAR)"STATUS_MAPPED_ALIGNMENT"; - case STATUS_IMAGE_CHECKSUM_MISMATCH: - return (PUCHAR)"STATUS_IMAGE_CHECKSUM_MISMATCH"; - case STATUS_LOST_WRITEBEHIND_DATA: - return (PUCHAR)"STATUS_LOST_WRITEBEHIND_DATA"; - case STATUS_CLIENT_SERVER_PARAMETERS_INVALID: - return (PUCHAR)"STATUS_CLIENT_SERVER_PARAMETERS_INVALID"; - case STATUS_PASSWORD_MUST_CHANGE: - return (PUCHAR)"STATUS_PASSWORD_MUST_CHANGE"; - case STATUS_NOT_FOUND: - return (PUCHAR)"STATUS_NOT_FOUND"; - case STATUS_NOT_TINY_STREAM: - return (PUCHAR)"STATUS_NOT_TINY_STREAM"; - case STATUS_RECOVERY_FAILURE: - return (PUCHAR)"STATUS_RECOVERY_FAILURE"; - case STATUS_STACK_OVERFLOW_READ: - return (PUCHAR)"STATUS_STACK_OVERFLOW_READ"; - case STATUS_FAIL_CHECK: - return (PUCHAR)"STATUS_FAIL_CHECK"; - case STATUS_DUPLICATE_OBJECTID: - return (PUCHAR)"STATUS_DUPLICATE_OBJECTID"; - case STATUS_OBJECTID_EXISTS: - return (PUCHAR)"STATUS_OBJECTID_EXISTS"; - case STATUS_CONVERT_TO_LARGE: - return (PUCHAR)"STATUS_CONVERT_TO_LARGE"; - case STATUS_RETRY: - return (PUCHAR)"STATUS_RETRY"; - case STATUS_FOUND_OUT_OF_SCOPE: - return (PUCHAR)"STATUS_FOUND_OUT_OF_SCOPE"; - case STATUS_ALLOCATE_BUCKET: - return (PUCHAR)"STATUS_ALLOCATE_BUCKET"; - case STATUS_PROPSET_NOT_FOUND: - return (PUCHAR)"STATUS_PROPSET_NOT_FOUND"; - case STATUS_MARSHALL_OVERFLOW: - return (PUCHAR)"STATUS_MARSHALL_OVERFLOW"; - case STATUS_INVALID_VARIANT: - return (PUCHAR)"STATUS_INVALID_VARIANT"; - case STATUS_DOMAIN_CONTROLLER_NOT_FOUND: - return (PUCHAR)"STATUS_DOMAIN_CONTROLLER_NOT_FOUND"; - case STATUS_ACCOUNT_LOCKED_OUT: - return (PUCHAR)"STATUS_ACCOUNT_LOCKED_OUT"; - case STATUS_HANDLE_NOT_CLOSABLE: - return (PUCHAR)"STATUS_HANDLE_NOT_CLOSABLE"; - case STATUS_CONNECTION_REFUSED: - return (PUCHAR)"STATUS_CONNECTION_REFUSED"; - case STATUS_GRACEFUL_DISCONNECT: - return (PUCHAR)"STATUS_GRACEFUL_DISCONNECT"; - case STATUS_ADDRESS_ALREADY_ASSOCIATED: - return (PUCHAR)"STATUS_ADDRESS_ALREADY_ASSOCIATED"; - case STATUS_ADDRESS_NOT_ASSOCIATED: - return (PUCHAR)"STATUS_ADDRESS_NOT_ASSOCIATED"; - case STATUS_CONNECTION_INVALID: - return (PUCHAR)"STATUS_CONNECTION_INVALID"; - case STATUS_CONNECTION_ACTIVE: - return (PUCHAR)"STATUS_CONNECTION_ACTIVE"; - case STATUS_NETWORK_UNREACHABLE: - return (PUCHAR)"STATUS_NETWORK_UNREACHABLE"; - case STATUS_HOST_UNREACHABLE: - return (PUCHAR)"STATUS_HOST_UNREACHABLE"; - case STATUS_PROTOCOL_UNREACHABLE: - return (PUCHAR)"STATUS_PROTOCOL_UNREACHABLE"; - case STATUS_PORT_UNREACHABLE: - return (PUCHAR)"STATUS_PORT_UNREACHABLE"; - case STATUS_REQUEST_ABORTED: - return (PUCHAR)"STATUS_REQUEST_ABORTED"; - case STATUS_CONNECTION_ABORTED: - return (PUCHAR)"STATUS_CONNECTION_ABORTED"; - case STATUS_BAD_COMPRESSION_BUFFER: - return (PUCHAR)"STATUS_BAD_COMPRESSION_BUFFER"; - case STATUS_USER_MAPPED_FILE: - return (PUCHAR)"STATUS_USER_MAPPED_FILE"; - case STATUS_AUDIT_FAILED: - return (PUCHAR)"STATUS_AUDIT_FAILED"; - case STATUS_TIMER_RESOLUTION_NOT_SET: - return (PUCHAR)"STATUS_TIMER_RESOLUTION_NOT_SET"; - case STATUS_CONNECTION_COUNT_LIMIT: - return (PUCHAR)"STATUS_CONNECTION_COUNT_LIMIT"; - case STATUS_LOGIN_TIME_RESTRICTION: - return (PUCHAR)"STATUS_LOGIN_TIME_RESTRICTION"; - case STATUS_LOGIN_WKSTA_RESTRICTION: - return (PUCHAR)"STATUS_LOGIN_WKSTA_RESTRICTION"; - case STATUS_IMAGE_MP_UP_MISMATCH: - return (PUCHAR)"STATUS_IMAGE_MP_UP_MISMATCH"; - case STATUS_INSUFFICIENT_LOGON_INFO: - return (PUCHAR)"STATUS_INSUFFICIENT_LOGON_INFO"; - case STATUS_BAD_DLL_ENTRYPOINT: - return (PUCHAR)"STATUS_BAD_DLL_ENTRYPOINT"; - case STATUS_BAD_SERVICE_ENTRYPOINT: - return (PUCHAR)"STATUS_BAD_SERVICE_ENTRYPOINT"; - case STATUS_LPC_REPLY_LOST: - return (PUCHAR)"STATUS_LPC_REPLY_LOST"; - case STATUS_IP_ADDRESS_CONFLICT1: - return (PUCHAR)"STATUS_IP_ADDRESS_CONFLICT1"; - case STATUS_IP_ADDRESS_CONFLICT2: - return (PUCHAR)"STATUS_IP_ADDRESS_CONFLICT2"; - case STATUS_REGISTRY_QUOTA_LIMIT: - return (PUCHAR)"STATUS_REGISTRY_QUOTA_LIMIT"; - case STATUS_PATH_NOT_COVERED: - return (PUCHAR)"STATUS_PATH_NOT_COVERED"; - case STATUS_NO_CALLBACK_ACTIVE: - return (PUCHAR)"STATUS_NO_CALLBACK_ACTIVE"; - case STATUS_LICENSE_QUOTA_EXCEEDED: - return (PUCHAR)"STATUS_LICENSE_QUOTA_EXCEEDED"; - case STATUS_PWD_TOO_SHORT: - return (PUCHAR)"STATUS_PWD_TOO_SHORT"; - case STATUS_PWD_TOO_RECENT: - return (PUCHAR)"STATUS_PWD_TOO_RECENT"; - case STATUS_PWD_HISTORY_CONFLICT: - return (PUCHAR)"STATUS_PWD_HISTORY_CONFLICT"; - case STATUS_PLUGPLAY_NO_DEVICE: - return (PUCHAR)"STATUS_PLUGPLAY_NO_DEVICE"; - case STATUS_UNSUPPORTED_COMPRESSION: - return (PUCHAR)"STATUS_UNSUPPORTED_COMPRESSION"; - case STATUS_INVALID_HW_PROFILE: - return (PUCHAR)"STATUS_INVALID_HW_PROFILE"; - case STATUS_INVALID_PLUGPLAY_DEVICE_PATH: - return (PUCHAR)"STATUS_INVALID_PLUGPLAY_DEVICE_PATH"; - case STATUS_DRIVER_ORDINAL_NOT_FOUND: - return (PUCHAR)"STATUS_DRIVER_ORDINAL_NOT_FOUND"; - case STATUS_DRIVER_ENTRYPOINT_NOT_FOUND: - return (PUCHAR)"STATUS_DRIVER_ENTRYPOINT_NOT_FOUND"; - case STATUS_RESOURCE_NOT_OWNED: - return (PUCHAR)"STATUS_RESOURCE_NOT_OWNED"; - case STATUS_TOO_MANY_LINKS: - return (PUCHAR)"STATUS_TOO_MANY_LINKS"; - case STATUS_QUOTA_LIST_INCONSISTENT: - return (PUCHAR)"STATUS_QUOTA_LIST_INCONSISTENT"; - case STATUS_FILE_IS_OFFLINE: - return (PUCHAR)"STATUS_FILE_IS_OFFLINE"; - case STATUS_EVALUATION_EXPIRATION: - return (PUCHAR)"STATUS_EVALUATION_EXPIRATION"; - case STATUS_ILLEGAL_DLL_RELOCATION: - return (PUCHAR)"STATUS_ILLEGAL_DLL_RELOCATION"; - case STATUS_LICENSE_VIOLATION: - return (PUCHAR)"STATUS_LICENSE_VIOLATION"; - case STATUS_DLL_INIT_FAILED_LOGOFF: - return (PUCHAR)"STATUS_DLL_INIT_FAILED_LOGOFF"; - case STATUS_DRIVER_UNABLE_TO_LOAD: - return (PUCHAR)"STATUS_DRIVER_UNABLE_TO_LOAD"; - case STATUS_DFS_UNAVAILABLE: - return (PUCHAR)"STATUS_DFS_UNAVAILABLE"; - case STATUS_VOLUME_DISMOUNTED: - return (PUCHAR)"STATUS_VOLUME_DISMOUNTED"; - case STATUS_WX86_INTERNAL_ERROR: - return (PUCHAR)"STATUS_WX86_INTERNAL_ERROR"; - case STATUS_WX86_FLOAT_STACK_CHECK: - return (PUCHAR)"STATUS_WX86_FLOAT_STACK_CHECK"; - case STATUS_VALIDATE_CONTINUE: - return (PUCHAR)"STATUS_VALIDATE_CONTINUE"; - case STATUS_NO_MATCH: - return (PUCHAR)"STATUS_NO_MATCH"; - case STATUS_NO_MORE_MATCHES: - return (PUCHAR)"STATUS_NO_MORE_MATCHES"; - case STATUS_NOT_A_REPARSE_POINT: - return (PUCHAR)"STATUS_NOT_A_REPARSE_POINT"; - case STATUS_IO_REPARSE_TAG_INVALID: - return (PUCHAR)"STATUS_IO_REPARSE_TAG_INVALID"; - case STATUS_IO_REPARSE_TAG_MISMATCH: - return (PUCHAR)"STATUS_IO_REPARSE_TAG_MISMATCH"; - case STATUS_IO_REPARSE_DATA_INVALID: - return (PUCHAR)"STATUS_IO_REPARSE_DATA_INVALID"; - case STATUS_IO_REPARSE_TAG_NOT_HANDLED: - return (PUCHAR)"STATUS_IO_REPARSE_TAG_NOT_HANDLED"; - case STATUS_REPARSE_POINT_NOT_RESOLVED: - return (PUCHAR)"STATUS_REPARSE_POINT_NOT_RESOLVED"; - case STATUS_DIRECTORY_IS_A_REPARSE_POINT: - return (PUCHAR)"STATUS_DIRECTORY_IS_A_REPARSE_POINT"; - case STATUS_RANGE_LIST_CONFLICT: - return (PUCHAR)"STATUS_RANGE_LIST_CONFLICT"; - case STATUS_SOURCE_ELEMENT_EMPTY: - return (PUCHAR)"STATUS_SOURCE_ELEMENT_EMPTY"; - case STATUS_DESTINATION_ELEMENT_FULL: - return (PUCHAR)"STATUS_DESTINATION_ELEMENT_FULL"; - case STATUS_ILLEGAL_ELEMENT_ADDRESS: - return (PUCHAR)"STATUS_ILLEGAL_ELEMENT_ADDRESS"; - case STATUS_MAGAZINE_NOT_PRESENT: - return (PUCHAR)"STATUS_MAGAZINE_NOT_PRESENT"; - case STATUS_REINITIALIZATION_NEEDED: - return (PUCHAR)"STATUS_REINITIALIZATION_NEEDED"; - case STATUS_ENCRYPTION_FAILED: - return (PUCHAR)"STATUS_ENCRYPTION_FAILED"; - case STATUS_DECRYPTION_FAILED: - return (PUCHAR)"STATUS_DECRYPTION_FAILED"; - case STATUS_RANGE_NOT_FOUND: - return (PUCHAR)"STATUS_RANGE_NOT_FOUND"; - case STATUS_NO_RECOVERY_POLICY: - return (PUCHAR)"STATUS_NO_RECOVERY_POLICY"; - case STATUS_NO_EFS: - return (PUCHAR)"STATUS_NO_EFS"; - case STATUS_WRONG_EFS: - return (PUCHAR)"STATUS_WRONG_EFS"; - case STATUS_NO_USER_KEYS: - return (PUCHAR)"STATUS_NO_USER_KEYS"; - case STATUS_FILE_NOT_ENCRYPTED: - return (PUCHAR)"STATUS_FILE_NOT_ENCRYPTED"; - case STATUS_NOT_EXPORT_FORMAT: - return (PUCHAR)"STATUS_NOT_EXPORT_FORMAT"; - case STATUS_FILE_ENCRYPTED: - return (PUCHAR)"STATUS_FILE_ENCRYPTED"; - case STATUS_WMI_GUID_NOT_FOUND: - return (PUCHAR)"STATUS_WMI_GUID_NOT_FOUND"; - case STATUS_WMI_INSTANCE_NOT_FOUND: - return (PUCHAR)"STATUS_WMI_INSTANCE_NOT_FOUND"; - case STATUS_WMI_ITEMID_NOT_FOUND: - return (PUCHAR)"STATUS_WMI_ITEMID_NOT_FOUND"; - case STATUS_WMI_TRY_AGAIN: - return (PUCHAR)"STATUS_WMI_TRY_AGAIN"; - case STATUS_SHARED_POLICY: - return (PUCHAR)"STATUS_SHARED_POLICY"; - case STATUS_POLICY_OBJECT_NOT_FOUND: - return (PUCHAR)"STATUS_POLICY_OBJECT_NOT_FOUND"; - case STATUS_POLICY_ONLY_IN_DS: - return (PUCHAR)"STATUS_POLICY_ONLY_IN_DS"; - case STATUS_VOLUME_NOT_UPGRADED: - return (PUCHAR)"STATUS_VOLUME_NOT_UPGRADED"; - case STATUS_REMOTE_STORAGE_NOT_ACTIVE: - return (PUCHAR)"STATUS_REMOTE_STORAGE_NOT_ACTIVE"; - case STATUS_REMOTE_STORAGE_MEDIA_ERROR: - return (PUCHAR)"STATUS_REMOTE_STORAGE_MEDIA_ERROR"; - case STATUS_NO_TRACKING_SERVICE: - return (PUCHAR)"STATUS_NO_TRACKING_SERVICE"; - case STATUS_SERVER_SID_MISMATCH: - return (PUCHAR)"STATUS_SERVER_SID_MISMATCH"; - case STATUS_DS_NO_ATTRIBUTE_OR_VALUE: - return (PUCHAR)"STATUS_DS_NO_ATTRIBUTE_OR_VALUE"; - case STATUS_DS_INVALID_ATTRIBUTE_SYNTAX: - return (PUCHAR)"STATUS_DS_INVALID_ATTRIBUTE_SYNTAX"; - case STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED: - return (PUCHAR)"STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED"; - case STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS: - return (PUCHAR)"STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS"; - case STATUS_DS_BUSY: - return (PUCHAR)"STATUS_DS_BUSY"; - case STATUS_DS_UNAVAILABLE: - return (PUCHAR)"STATUS_DS_UNAVAILABLE"; - case STATUS_DS_NO_RIDS_ALLOCATED: - return (PUCHAR)"STATUS_DS_NO_RIDS_ALLOCATED"; - case STATUS_DS_NO_MORE_RIDS: - return (PUCHAR)"STATUS_DS_NO_MORE_RIDS"; - case STATUS_DS_INCORRECT_ROLE_OWNER: - return (PUCHAR)"STATUS_DS_INCORRECT_ROLE_OWNER"; - case STATUS_DS_RIDMGR_INIT_ERROR: - return (PUCHAR)"STATUS_DS_RIDMGR_INIT_ERROR"; - case STATUS_DS_OBJ_CLASS_VIOLATION: - return (PUCHAR)"STATUS_DS_OBJ_CLASS_VIOLATION"; - case STATUS_DS_CANT_ON_NON_LEAF: - return (PUCHAR)"STATUS_DS_CANT_ON_NON_LEAF"; - case STATUS_DS_CANT_ON_RDN: - return (PUCHAR)"STATUS_DS_CANT_ON_RDN"; - case STATUS_DS_CANT_MOD_OBJ_CLASS: - return (PUCHAR)"STATUS_DS_CANT_MOD_OBJ_CLASS"; - case STATUS_DS_CROSS_DOM_MOVE_FAILED: - return (PUCHAR)"STATUS_DS_CROSS_DOM_MOVE_FAILED"; - case STATUS_DS_GC_NOT_AVAILABLE: - return (PUCHAR)"STATUS_DS_GC_NOT_AVAILABLE"; - case STATUS_DIRECTORY_SERVICE_REQUIRED: - return (PUCHAR)"STATUS_DIRECTORY_SERVICE_REQUIRED"; - case STATUS_REPARSE_ATTRIBUTE_CONFLICT: - return (PUCHAR)"STATUS_REPARSE_ATTRIBUTE_CONFLICT"; - case STATUS_CANT_ENABLE_DENY_ONLY: - return (PUCHAR)"STATUS_CANT_ENABLE_DENY_ONLY"; - case STATUS_FLOAT_MULTIPLE_FAULTS: - return (PUCHAR)"STATUS_FLOAT_MULTIPLE_FAULTS"; - case STATUS_FLOAT_MULTIPLE_TRAPS: - return (PUCHAR)"STATUS_FLOAT_MULTIPLE_TRAPS"; - case STATUS_DEVICE_REMOVED: - return (PUCHAR)"STATUS_DEVICE_REMOVED"; - case STATUS_JOURNAL_DELETE_IN_PROGRESS: - return (PUCHAR)"STATUS_JOURNAL_DELETE_IN_PROGRESS"; - case STATUS_JOURNAL_NOT_ACTIVE: - return (PUCHAR)"STATUS_JOURNAL_NOT_ACTIVE"; - case STATUS_NOINTERFACE: - return (PUCHAR)"STATUS_NOINTERFACE"; - case STATUS_DS_ADMIN_LIMIT_EXCEEDED: - return (PUCHAR)"STATUS_DS_ADMIN_LIMIT_EXCEEDED"; - case STATUS_DRIVER_FAILED_SLEEP: - return (PUCHAR)"STATUS_DRIVER_FAILED_SLEEP"; - case STATUS_MUTUAL_AUTHENTICATION_FAILED: - return (PUCHAR)"STATUS_MUTUAL_AUTHENTICATION_FAILED"; - case STATUS_CORRUPT_SYSTEM_FILE: - return (PUCHAR)"STATUS_CORRUPT_SYSTEM_FILE"; - case STATUS_DATATYPE_MISALIGNMENT_ERROR: - return (PUCHAR)"STATUS_DATATYPE_MISALIGNMENT_ERROR"; - case STATUS_WMI_READ_ONLY: - return (PUCHAR)"STATUS_WMI_READ_ONLY"; - case STATUS_WMI_SET_FAILURE: - return (PUCHAR)"STATUS_WMI_SET_FAILURE"; - case STATUS_COMMITMENT_MINIMUM: - return (PUCHAR)"STATUS_COMMITMENT_MINIMUM"; - case STATUS_REG_NAT_CONSUMPTION: - return (PUCHAR)"STATUS_REG_NAT_CONSUMPTION"; - case STATUS_TRANSPORT_FULL: - return (PUCHAR)"STATUS_TRANSPORT_FULL"; - case STATUS_DS_SAM_INIT_FAILURE: - return (PUCHAR)"STATUS_DS_SAM_INIT_FAILURE"; - case STATUS_ONLY_IF_CONNECTED: - return (PUCHAR)"STATUS_ONLY_IF_CONNECTED"; - case STATUS_DS_SENSITIVE_GROUP_VIOLATION: - return (PUCHAR)"STATUS_DS_SENSITIVE_GROUP_VIOLATION"; - case STATUS_PNP_RESTART_ENUMERATION: - return (PUCHAR)"STATUS_PNP_RESTART_ENUMERATION"; - case STATUS_JOURNAL_ENTRY_DELETED: - return (PUCHAR)"STATUS_JOURNAL_ENTRY_DELETED"; - case STATUS_DS_CANT_MOD_PRIMARYGROUPID: - return (PUCHAR)"STATUS_DS_CANT_MOD_PRIMARYGROUPID"; - case STATUS_SYSTEM_IMAGE_BAD_SIGNATURE: - return (PUCHAR)"STATUS_SYSTEM_IMAGE_BAD_SIGNATURE"; - case STATUS_PNP_REBOOT_REQUIRED: - return (PUCHAR)"STATUS_PNP_REBOOT_REQUIRED"; - case STATUS_POWER_STATE_INVALID: - return (PUCHAR)"STATUS_POWER_STATE_INVALID"; - case STATUS_DS_INVALID_GROUP_TYPE: - return (PUCHAR)"STATUS_DS_INVALID_GROUP_TYPE"; - case STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN: - return (PUCHAR)"STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN"; - case STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN: - return (PUCHAR)"STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN"; - case STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER: - return (PUCHAR)"STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER"; - case STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER: - return (PUCHAR)"STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER"; - case STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER: - return (PUCHAR)"STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER"; - case STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER: - return (PUCHAR)"STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER"; - case STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER: - return (PUCHAR)"STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER"; - case STATUS_DS_HAVE_PRIMARY_MEMBERS: - return (PUCHAR)"STATUS_DS_HAVE_PRIMARY_MEMBERS"; - case STATUS_WMI_NOT_SUPPORTED: - return (PUCHAR)"STATUS_WMI_NOT_SUPPORTED"; - case STATUS_INSUFFICIENT_POWER: - return (PUCHAR)"STATUS_INSUFFICIENT_POWER"; - case STATUS_SAM_NEED_BOOTKEY_PASSWORD: - return (PUCHAR)"STATUS_SAM_NEED_BOOTKEY_PASSWORD"; - case STATUS_SAM_NEED_BOOTKEY_FLOPPY: - return (PUCHAR)"STATUS_SAM_NEED_BOOTKEY_FLOPPY"; - case STATUS_DS_CANT_START: - return (PUCHAR)"STATUS_DS_CANT_START"; - case STATUS_DS_INIT_FAILURE: - return (PUCHAR)"STATUS_DS_INIT_FAILURE"; - case STATUS_SAM_INIT_FAILURE: - return (PUCHAR)"STATUS_SAM_INIT_FAILURE"; - case STATUS_DS_GC_REQUIRED: - return (PUCHAR)"STATUS_DS_GC_REQUIRED"; - case STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY: - return (PUCHAR)"STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY"; - case STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS: - return (PUCHAR)"STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS"; - case STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED: - return (PUCHAR)"STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED"; - case STATUS_MULTIPLE_FAULT_VIOLATION: - return (PUCHAR)"STATUS_MULTIPLE_FAULT_VIOLATION"; -#if (VER_PRODUCT_BUILD >= 2600) - case STATUS_CURRENT_DOMAIN_NOT_ALLOWED: - return (PUCHAR)"STATUS_CURRENT_DOMAIN_NOT_ALLOWED"; - case STATUS_CANNOT_MAKE: - return (PUCHAR)"STATUS_CANNOT_MAKE"; - case STATUS_SYSTEM_SHUTDOWN: - return (PUCHAR)"STATUS_SYSTEM_SHUTDOWN"; - case STATUS_DS_INIT_FAILURE_CONSOLE: - return (PUCHAR)"STATUS_DS_INIT_FAILURE_CONSOLE"; - case STATUS_DS_SAM_INIT_FAILURE_CONSOLE: - return (PUCHAR)"STATUS_DS_SAM_INIT_FAILURE_CONSOLE"; - case STATUS_UNFINISHED_CONTEXT_DELETED: - return (PUCHAR)"STATUS_UNFINISHED_CONTEXT_DELETED"; - case STATUS_NO_TGT_REPLY: - return (PUCHAR)"STATUS_NO_TGT_REPLY"; - case STATUS_OBJECTID_NOT_FOUND: - return (PUCHAR)"STATUS_OBJECTID_NOT_FOUND"; - case STATUS_NO_IP_ADDRESSES: - return (PUCHAR)"STATUS_NO_IP_ADDRESSES"; - case STATUS_WRONG_CREDENTIAL_HANDLE: - return (PUCHAR)"STATUS_WRONG_CREDENTIAL_HANDLE"; - case STATUS_CRYPTO_SYSTEM_INVALID: - return (PUCHAR)"STATUS_CRYPTO_SYSTEM_INVALID"; - case STATUS_MAX_REFERRALS_EXCEEDED: - return (PUCHAR)"STATUS_MAX_REFERRALS_EXCEEDED"; - case STATUS_MUST_BE_KDC: - return (PUCHAR)"STATUS_MUST_BE_KDC"; - case STATUS_STRONG_CRYPTO_NOT_SUPPORTED: - return (PUCHAR)"STATUS_STRONG_CRYPTO_NOT_SUPPORTED"; - case STATUS_TOO_MANY_PRINCIPALS: - return (PUCHAR)"STATUS_TOO_MANY_PRINCIPALS"; - case STATUS_NO_PA_DATA: - return (PUCHAR)"STATUS_NO_PA_DATA"; - case STATUS_PKINIT_NAME_MISMATCH: - return (PUCHAR)"STATUS_PKINIT_NAME_MISMATCH"; - case STATUS_SMARTCARD_LOGON_REQUIRED: - return (PUCHAR)"STATUS_SMARTCARD_LOGON_REQUIRED"; - case STATUS_KDC_INVALID_REQUEST: - return (PUCHAR)"STATUS_KDC_INVALID_REQUEST"; - case STATUS_KDC_UNABLE_TO_REFER: - return (PUCHAR)"STATUS_KDC_UNABLE_TO_REFER"; - case STATUS_KDC_UNKNOWN_ETYPE: - return (PUCHAR)"STATUS_KDC_UNKNOWN_ETYPE"; - case STATUS_SHUTDOWN_IN_PROGRESS: - return (PUCHAR)"STATUS_SHUTDOWN_IN_PROGRESS"; - case STATUS_SERVER_SHUTDOWN_IN_PROGRESS: - return (PUCHAR)"STATUS_SERVER_SHUTDOWN_IN_PROGRESS"; -#endif - case STATUS_NOT_SUPPORTED_ON_SBS: - return (PUCHAR)"STATUS_NOT_SUPPORTED_ON_SBS"; -#if (VER_PRODUCT_BUILD >= 2600) - case STATUS_WMI_GUID_DISCONNECTED: - return (PUCHAR)"STATUS_WMI_GUID_DISCONNECTED"; - case STATUS_WMI_ALREADY_DISABLED: - return (PUCHAR)"STATUS_WMI_ALREADY_DISABLED"; - case STATUS_WMI_ALREADY_ENABLED: - return (PUCHAR)"STATUS_WMI_ALREADY_ENABLED"; - case STATUS_MFT_TOO_FRAGMENTED: - return (PUCHAR)"STATUS_MFT_TOO_FRAGMENTED"; - case STATUS_COPY_PROTECTION_FAILURE: - return (PUCHAR)"STATUS_COPY_PROTECTION_FAILURE"; - case STATUS_CSS_AUTHENTICATION_FAILURE: - return (PUCHAR)"STATUS_CSS_AUTHENTICATION_FAILURE"; - case STATUS_CSS_KEY_NOT_PRESENT: - return (PUCHAR)"STATUS_CSS_KEY_NOT_PRESENT"; - case STATUS_CSS_KEY_NOT_ESTABLISHED: - return (PUCHAR)"STATUS_CSS_KEY_NOT_ESTABLISHED"; - case STATUS_CSS_SCRAMBLED_SECTOR: - return (PUCHAR)"STATUS_CSS_SCRAMBLED_SECTOR"; - case STATUS_CSS_REGION_MISMATCH: - return (PUCHAR)"STATUS_CSS_REGION_MISMATCH"; - case STATUS_CSS_RESETS_EXHAUSTED: - return (PUCHAR)"STATUS_CSS_RESETS_EXHAUSTED"; - case STATUS_PKINIT_FAILURE: - return (PUCHAR)"STATUS_PKINIT_FAILURE"; - case STATUS_SMARTCARD_SUBSYSTEM_FAILURE: - return (PUCHAR)"STATUS_SMARTCARD_SUBSYSTEM_FAILURE"; - case STATUS_NO_KERB_KEY: - return (PUCHAR)"STATUS_NO_KERB_KEY"; - case STATUS_HOST_DOWN: - return (PUCHAR)"STATUS_HOST_DOWN"; - case STATUS_UNSUPPORTED_PREAUTH: - return (PUCHAR)"STATUS_UNSUPPORTED_PREAUTH"; - case STATUS_EFS_ALG_BLOB_TOO_BIG: - return (PUCHAR)"STATUS_EFS_ALG_BLOB_TOO_BIG"; - case STATUS_PORT_NOT_SET: - return (PUCHAR)"STATUS_PORT_NOT_SET"; - case STATUS_DEBUGGER_INACTIVE: - return (PUCHAR)"STATUS_DEBUGGER_INACTIVE"; - case STATUS_DS_VERSION_CHECK_FAILURE: - return (PUCHAR)"STATUS_DS_VERSION_CHECK_FAILURE"; - case STATUS_AUDITING_DISABLED: - return (PUCHAR)"STATUS_AUDITING_DISABLED"; - case STATUS_PRENT4_MACHINE_ACCOUNT: - return (PUCHAR)"STATUS_PRENT4_MACHINE_ACCOUNT"; - case STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER: - return (PUCHAR)"STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER"; - case STATUS_INVALID_IMAGE_WIN_32: - return (PUCHAR)"STATUS_INVALID_IMAGE_WIN_32"; - case STATUS_INVALID_IMAGE_WIN_64: - return (PUCHAR)"STATUS_INVALID_IMAGE_WIN_64"; - case STATUS_BAD_BINDINGS: - return (PUCHAR)"STATUS_BAD_BINDINGS"; - case STATUS_NETWORK_SESSION_EXPIRED: - return (PUCHAR)"STATUS_NETWORK_SESSION_EXPIRED"; - case STATUS_APPHELP_BLOCK: - return (PUCHAR)"STATUS_APPHELP_BLOCK"; - case STATUS_ALL_SIDS_FILTERED: - return (PUCHAR)"STATUS_ALL_SIDS_FILTERED"; - case STATUS_NOT_SAFE_MODE_DRIVER: - return (PUCHAR)"STATUS_NOT_SAFE_MODE_DRIVER"; - case STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT: - return (PUCHAR)"STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT"; - case STATUS_ACCESS_DISABLED_BY_POLICY_PATH: - return (PUCHAR)"STATUS_ACCESS_DISABLED_BY_POLICY_PATH"; - case STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER: - return (PUCHAR)"STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER"; - case STATUS_ACCESS_DISABLED_BY_POLICY_OTHER: - return (PUCHAR)"STATUS_ACCESS_DISABLED_BY_POLICY_OTHER"; - case STATUS_FAILED_DRIVER_ENTRY: - return (PUCHAR)"STATUS_FAILED_DRIVER_ENTRY"; - case STATUS_DEVICE_ENUMERATION_ERROR: - return (PUCHAR)"STATUS_DEVICE_ENUMERATION_ERROR"; - case STATUS_MOUNT_POINT_NOT_RESOLVED: - return (PUCHAR)"STATUS_MOUNT_POINT_NOT_RESOLVED"; - case STATUS_INVALID_DEVICE_OBJECT_PARAMETER: - return (PUCHAR)"STATUS_INVALID_DEVICE_OBJECT_PARAMETER"; - case STATUS_MCA_OCCURED: - return (PUCHAR)"STATUS_MCA_OCCURED"; - case STATUS_DRIVER_BLOCKED_CRITICAL: - return (PUCHAR)"STATUS_DRIVER_BLOCKED_CRITICAL"; - case STATUS_DRIVER_BLOCKED: - return (PUCHAR)"STATUS_DRIVER_BLOCKED"; - case STATUS_DRIVER_DATABASE_ERROR: - return (PUCHAR)"STATUS_DRIVER_DATABASE_ERROR"; - case STATUS_SYSTEM_HIVE_TOO_LARGE: - return (PUCHAR)"STATUS_SYSTEM_HIVE_TOO_LARGE"; - case STATUS_INVALID_IMPORT_OF_NON_DLL: - return (PUCHAR)"STATUS_INVALID_IMPORT_OF_NON_DLL"; - case STATUS_SMARTCARD_WRONG_PIN: - return (PUCHAR)"STATUS_SMARTCARD_WRONG_PIN"; - case STATUS_SMARTCARD_CARD_BLOCKED: - return (PUCHAR)"STATUS_SMARTCARD_CARD_BLOCKED"; - case STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED: - return (PUCHAR)"STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED"; - case STATUS_SMARTCARD_NO_CARD: - return (PUCHAR)"STATUS_SMARTCARD_NO_CARD"; - case STATUS_SMARTCARD_NO_KEY_CONTAINER: - return (PUCHAR)"STATUS_SMARTCARD_NO_KEY_CONTAINER"; - case STATUS_SMARTCARD_NO_CERTIFICATE: - return (PUCHAR)"STATUS_SMARTCARD_NO_CERTIFICATE"; - case STATUS_SMARTCARD_NO_KEYSET: - return (PUCHAR)"STATUS_SMARTCARD_NO_KEYSET"; - case STATUS_SMARTCARD_IO_ERROR: - return (PUCHAR)"STATUS_SMARTCARD_IO_ERROR"; - case STATUS_DOWNGRADE_DETECTED: - return (PUCHAR)"STATUS_DOWNGRADE_DETECTED"; - case STATUS_SMARTCARD_CERT_REVOKED: - return (PUCHAR)"STATUS_SMARTCARD_CERT_REVOKED"; - case STATUS_ISSUING_CA_UNTRUSTED: - return (PUCHAR)"STATUS_ISSUING_CA_UNTRUSTED"; - case STATUS_REVOCATION_OFFLINE_C: - return (PUCHAR)"STATUS_REVOCATION_OFFLINE_C"; - case STATUS_PKINIT_CLIENT_FAILURE: - return (PUCHAR)"STATUS_PKINIT_CLIENT_FAILURE"; - case STATUS_SMARTCARD_CERT_EXPIRED: - return (PUCHAR)"STATUS_SMARTCARD_CERT_EXPIRED"; - case STATUS_DRIVER_FAILED_PRIOR_UNLOAD: - return (PUCHAR)"STATUS_DRIVER_FAILED_PRIOR_UNLOAD"; -#endif -#if (VER_PRODUCT_BUILD > 2600) - case STATUS_SMARTCARD_SILENT_CONTEXT: - return (PUCHAR)"STATUS_SMARTCARD_SILENT_CONTEXT"; - case STATUS_PER_USER_TRUST_QUOTA_EXCEEDED: - return (PUCHAR)"STATUS_PER_USER_TRUST_QUOTA_EXCEEDED"; - case STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED: - return (PUCHAR)"STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED"; - case STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED: - return (PUCHAR)"STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED"; - case STATUS_DS_NAME_NOT_UNIQUE: - return (PUCHAR)"STATUS_DS_NAME_NOT_UNIQUE"; - case STATUS_DS_DUPLICATE_ID_FOUND: - return (PUCHAR)"STATUS_DS_DUPLICATE_ID_FOUND"; - case STATUS_DS_GROUP_CONVERSION_ERROR: - return (PUCHAR)"STATUS_DS_GROUP_CONVERSION_ERROR"; - case STATUS_VOLSNAP_PREPARE_HIBERNATE: - return (PUCHAR)"STATUS_VOLSNAP_PREPARE_HIBERNATE"; - case STATUS_USER2USER_REQUIRED: - return (PUCHAR)"STATUS_USER2USER_REQUIRED"; - case STATUS_STACK_BUFFER_OVERRUN: - return (PUCHAR)"STATUS_STACK_BUFFER_OVERRUN"; - case STATUS_NO_S4U_PROT_SUPPORT: - return (PUCHAR)"STATUS_NO_S4U_PROT_SUPPORT"; - case STATUS_CROSSREALM_DELEGATION_FAILURE: - return (PUCHAR)"STATUS_CROSSREALM_DELEGATION_FAILURE"; - case STATUS_REVOCATION_OFFLINE_KDC: - return (PUCHAR)"STATUS_REVOCATION_OFFLINE_KDC"; - case STATUS_ISSUING_CA_UNTRUSTED_KDC: - return (PUCHAR)"STATUS_ISSUING_CA_UNTRUSTED_KDC"; - case STATUS_KDC_CERT_EXPIRED: - return (PUCHAR)"STATUS_KDC_CERT_EXPIRED"; - case STATUS_KDC_CERT_REVOKED: - return (PUCHAR)"STATUS_KDC_CERT_REVOKED"; - case STATUS_PARAMETER_QUOTA_EXCEEDED: - return (PUCHAR)"STATUS_PARAMETER_QUOTA_EXCEEDED"; - case STATUS_HIBERNATION_FAILURE: - return (PUCHAR)"STATUS_HIBERNATION_FAILURE"; - case STATUS_DELAY_LOAD_FAILED: - return (PUCHAR)"STATUS_DELAY_LOAD_FAILED"; - case STATUS_AUTHENTICATION_FIREWALL_FAILED: - return (PUCHAR)"STATUS_AUTHENTICATION_FIREWALL_FAILED"; - case STATUS_VDM_DISALLOWED: - return (PUCHAR)"STATUS_VDM_DISALLOWED"; - case STATUS_HUNG_DISPLAY_DRIVER_THREAD: - return (PUCHAR)"STATUS_HUNG_DISPLAY_DRIVER_THREAD"; -#endif - case STATUS_WOW_ASSERTION: - return (PUCHAR)"STATUS_WOW_ASSERTION"; - case DBG_NO_STATE_CHANGE: - return (PUCHAR)"DBG_NO_STATE_CHANGE"; - case DBG_APP_NOT_IDLE: - return (PUCHAR)"DBG_APP_NOT_IDLE"; - case RPC_NT_INVALID_STRING_BINDING: - return (PUCHAR)"RPC_NT_INVALID_STRING_BINDING"; - case RPC_NT_WRONG_KIND_OF_BINDING: - return (PUCHAR)"RPC_NT_WRONG_KIND_OF_BINDING"; - case RPC_NT_INVALID_BINDING: - return (PUCHAR)"RPC_NT_INVALID_BINDING"; - case RPC_NT_PROTSEQ_NOT_SUPPORTED: - return (PUCHAR)"RPC_NT_PROTSEQ_NOT_SUPPORTED"; - case RPC_NT_INVALID_RPC_PROTSEQ: - return (PUCHAR)"RPC_NT_INVALID_RPC_PROTSEQ"; - case RPC_NT_INVALID_STRING_UUID: - return (PUCHAR)"RPC_NT_INVALID_STRING_UUID"; - case RPC_NT_INVALID_ENDPOINT_FORMAT: - return (PUCHAR)"RPC_NT_INVALID_ENDPOINT_FORMAT"; - case RPC_NT_INVALID_NET_ADDR: - return (PUCHAR)"RPC_NT_INVALID_NET_ADDR"; - case RPC_NT_NO_ENDPOINT_FOUND: - return (PUCHAR)"RPC_NT_NO_ENDPOINT_FOUND"; - case RPC_NT_INVALID_TIMEOUT: - return (PUCHAR)"RPC_NT_INVALID_TIMEOUT"; - case RPC_NT_OBJECT_NOT_FOUND: - return (PUCHAR)"RPC_NT_OBJECT_NOT_FOUND"; - case RPC_NT_ALREADY_REGISTERED: - return (PUCHAR)"RPC_NT_ALREADY_REGISTERED"; - case RPC_NT_TYPE_ALREADY_REGISTERED: - return (PUCHAR)"RPC_NT_TYPE_ALREADY_REGISTERED"; - case RPC_NT_ALREADY_LISTENING: - return (PUCHAR)"RPC_NT_ALREADY_LISTENING"; - case RPC_NT_NO_PROTSEQS_REGISTERED: - return (PUCHAR)"RPC_NT_NO_PROTSEQS_REGISTERED"; - case RPC_NT_NOT_LISTENING: - return (PUCHAR)"RPC_NT_NOT_LISTENING"; - case RPC_NT_UNKNOWN_MGR_TYPE: - return (PUCHAR)"RPC_NT_UNKNOWN_MGR_TYPE"; - case RPC_NT_UNKNOWN_IF: - return (PUCHAR)"RPC_NT_UNKNOWN_IF"; - case RPC_NT_NO_BINDINGS: - return (PUCHAR)"RPC_NT_NO_BINDINGS"; - case RPC_NT_NO_PROTSEQS: - return (PUCHAR)"RPC_NT_NO_PROTSEQS"; - case RPC_NT_CANT_CREATE_ENDPOINT: - return (PUCHAR)"RPC_NT_CANT_CREATE_ENDPOINT"; - case RPC_NT_OUT_OF_RESOURCES: - return (PUCHAR)"RPC_NT_OUT_OF_RESOURCES"; - case RPC_NT_SERVER_UNAVAILABLE: - return (PUCHAR)"RPC_NT_SERVER_UNAVAILABLE"; - case RPC_NT_SERVER_TOO_BUSY: - return (PUCHAR)"RPC_NT_SERVER_TOO_BUSY"; - case RPC_NT_INVALID_NETWORK_OPTIONS: - return (PUCHAR)"RPC_NT_INVALID_NETWORK_OPTIONS"; - case RPC_NT_NO_CALL_ACTIVE: - return (PUCHAR)"RPC_NT_NO_CALL_ACTIVE"; - case RPC_NT_CALL_FAILED: - return (PUCHAR)"RPC_NT_CALL_FAILED"; - case RPC_NT_CALL_FAILED_DNE: - return (PUCHAR)"RPC_NT_CALL_FAILED_DNE"; - case RPC_NT_PROTOCOL_ERROR: - return (PUCHAR)"RPC_NT_PROTOCOL_ERROR"; - case RPC_NT_UNSUPPORTED_TRANS_SYN: - return (PUCHAR)"RPC_NT_UNSUPPORTED_TRANS_SYN"; - case RPC_NT_UNSUPPORTED_TYPE: - return (PUCHAR)"RPC_NT_UNSUPPORTED_TYPE"; - case RPC_NT_INVALID_TAG: - return (PUCHAR)"RPC_NT_INVALID_TAG"; - case RPC_NT_INVALID_BOUND: - return (PUCHAR)"RPC_NT_INVALID_BOUND"; - case RPC_NT_NO_ENTRY_NAME: - return (PUCHAR)"RPC_NT_NO_ENTRY_NAME"; - case RPC_NT_INVALID_NAME_SYNTAX: - return (PUCHAR)"RPC_NT_INVALID_NAME_SYNTAX"; - case RPC_NT_UNSUPPORTED_NAME_SYNTAX: - return (PUCHAR)"RPC_NT_UNSUPPORTED_NAME_SYNTAX"; - case RPC_NT_UUID_NO_ADDRESS: - return (PUCHAR)"RPC_NT_UUID_NO_ADDRESS"; - case RPC_NT_DUPLICATE_ENDPOINT: - return (PUCHAR)"RPC_NT_DUPLICATE_ENDPOINT"; - case RPC_NT_UNKNOWN_AUTHN_TYPE: - return (PUCHAR)"RPC_NT_UNKNOWN_AUTHN_TYPE"; - case RPC_NT_MAX_CALLS_TOO_SMALL: - return (PUCHAR)"RPC_NT_MAX_CALLS_TOO_SMALL"; - case RPC_NT_STRING_TOO_LONG: - return (PUCHAR)"RPC_NT_STRING_TOO_LONG"; - case RPC_NT_PROTSEQ_NOT_FOUND: - return (PUCHAR)"RPC_NT_PROTSEQ_NOT_FOUND"; - case RPC_NT_PROCNUM_OUT_OF_RANGE: - return (PUCHAR)"RPC_NT_PROCNUM_OUT_OF_RANGE"; - case RPC_NT_BINDING_HAS_NO_AUTH: - return (PUCHAR)"RPC_NT_BINDING_HAS_NO_AUTH"; - case RPC_NT_UNKNOWN_AUTHN_SERVICE: - return (PUCHAR)"RPC_NT_UNKNOWN_AUTHN_SERVICE"; - case RPC_NT_UNKNOWN_AUTHN_LEVEL: - return (PUCHAR)"RPC_NT_UNKNOWN_AUTHN_LEVEL"; - case RPC_NT_INVALID_AUTH_IDENTITY: - return (PUCHAR)"RPC_NT_INVALID_AUTH_IDENTITY"; - case RPC_NT_UNKNOWN_AUTHZ_SERVICE: - return (PUCHAR)"RPC_NT_UNKNOWN_AUTHZ_SERVICE"; - case EPT_NT_INVALID_ENTRY: - return (PUCHAR)"EPT_NT_INVALID_ENTRY"; - case EPT_NT_CANT_PERFORM_OP: - return (PUCHAR)"EPT_NT_CANT_PERFORM_OP"; - case EPT_NT_NOT_REGISTERED: - return (PUCHAR)"EPT_NT_NOT_REGISTERED"; - case RPC_NT_NOTHING_TO_EXPORT: - return (PUCHAR)"RPC_NT_NOTHING_TO_EXPORT"; - case RPC_NT_INCOMPLETE_NAME: - return (PUCHAR)"RPC_NT_INCOMPLETE_NAME"; - case RPC_NT_INVALID_VERS_OPTION: - return (PUCHAR)"RPC_NT_INVALID_VERS_OPTION"; - case RPC_NT_NO_MORE_MEMBERS: - return (PUCHAR)"RPC_NT_NO_MORE_MEMBERS"; - case RPC_NT_NOT_ALL_OBJS_UNEXPORTED: - return (PUCHAR)"RPC_NT_NOT_ALL_OBJS_UNEXPORTED"; - case RPC_NT_INTERFACE_NOT_FOUND: - return (PUCHAR)"RPC_NT_INTERFACE_NOT_FOUND"; - case RPC_NT_ENTRY_ALREADY_EXISTS: - return (PUCHAR)"RPC_NT_ENTRY_ALREADY_EXISTS"; - case RPC_NT_ENTRY_NOT_FOUND: - return (PUCHAR)"RPC_NT_ENTRY_NOT_FOUND"; - case RPC_NT_NAME_SERVICE_UNAVAILABLE: - return (PUCHAR)"RPC_NT_NAME_SERVICE_UNAVAILABLE"; - case RPC_NT_INVALID_NAF_ID: - return (PUCHAR)"RPC_NT_INVALID_NAF_ID"; - case RPC_NT_CANNOT_SUPPORT: - return (PUCHAR)"RPC_NT_CANNOT_SUPPORT"; - case RPC_NT_NO_CONTEXT_AVAILABLE: - return (PUCHAR)"RPC_NT_NO_CONTEXT_AVAILABLE"; - case RPC_NT_INTERNAL_ERROR: - return (PUCHAR)"RPC_NT_INTERNAL_ERROR"; - case RPC_NT_ZERO_DIVIDE: - return (PUCHAR)"RPC_NT_ZERO_DIVIDE"; - case RPC_NT_ADDRESS_ERROR: - return (PUCHAR)"RPC_NT_ADDRESS_ERROR"; - case RPC_NT_FP_DIV_ZERO: - return (PUCHAR)"RPC_NT_FP_DIV_ZERO"; - case RPC_NT_FP_UNDERFLOW: - return (PUCHAR)"RPC_NT_FP_UNDERFLOW"; - case RPC_NT_FP_OVERFLOW: - return (PUCHAR)"RPC_NT_FP_OVERFLOW"; - case RPC_NT_CALL_IN_PROGRESS: - return (PUCHAR)"RPC_NT_CALL_IN_PROGRESS"; - case RPC_NT_NO_MORE_BINDINGS: - return (PUCHAR)"RPC_NT_NO_MORE_BINDINGS"; - case RPC_NT_GROUP_MEMBER_NOT_FOUND: - return (PUCHAR)"RPC_NT_GROUP_MEMBER_NOT_FOUND"; - case EPT_NT_CANT_CREATE: - return (PUCHAR)"EPT_NT_CANT_CREATE"; - case RPC_NT_INVALID_OBJECT: - return (PUCHAR)"RPC_NT_INVALID_OBJECT"; - case RPC_NT_NO_INTERFACES: - return (PUCHAR)"RPC_NT_NO_INTERFACES"; - case RPC_NT_CALL_CANCELLED: - return (PUCHAR)"RPC_NT_CALL_CANCELLED"; - case RPC_NT_BINDING_INCOMPLETE: - return (PUCHAR)"RPC_NT_BINDING_INCOMPLETE"; - case RPC_NT_COMM_FAILURE: - return (PUCHAR)"RPC_NT_COMM_FAILURE"; - case RPC_NT_UNSUPPORTED_AUTHN_LEVEL: - return (PUCHAR)"RPC_NT_UNSUPPORTED_AUTHN_LEVEL"; - case RPC_NT_NO_PRINC_NAME: - return (PUCHAR)"RPC_NT_NO_PRINC_NAME"; - case RPC_NT_NOT_RPC_ERROR: - return (PUCHAR)"RPC_NT_NOT_RPC_ERROR"; - case RPC_NT_SEC_PKG_ERROR: - return (PUCHAR)"RPC_NT_SEC_PKG_ERROR"; - case RPC_NT_NOT_CANCELLED: - return (PUCHAR)"RPC_NT_NOT_CANCELLED"; - case RPC_NT_INVALID_ASYNC_HANDLE: - return (PUCHAR)"RPC_NT_INVALID_ASYNC_HANDLE"; - case RPC_NT_INVALID_ASYNC_CALL: - return (PUCHAR)"RPC_NT_INVALID_ASYNC_CALL"; - case RPC_NT_NO_MORE_ENTRIES: - return (PUCHAR)"RPC_NT_NO_MORE_ENTRIES"; - case RPC_NT_SS_CHAR_TRANS_OPEN_FAIL: - return (PUCHAR)"RPC_NT_SS_CHAR_TRANS_OPEN_FAIL"; - case RPC_NT_SS_CHAR_TRANS_SHORT_FILE: - return (PUCHAR)"RPC_NT_SS_CHAR_TRANS_SHORT_FILE"; - case RPC_NT_SS_IN_NULL_CONTEXT: - return (PUCHAR)"RPC_NT_SS_IN_NULL_CONTEXT"; - case RPC_NT_SS_CONTEXT_MISMATCH: - return (PUCHAR)"RPC_NT_SS_CONTEXT_MISMATCH"; - case RPC_NT_SS_CONTEXT_DAMAGED: - return (PUCHAR)"RPC_NT_SS_CONTEXT_DAMAGED"; - case RPC_NT_SS_HANDLES_MISMATCH: - return (PUCHAR)"RPC_NT_SS_HANDLES_MISMATCH"; - case RPC_NT_SS_CANNOT_GET_CALL_HANDLE: - return (PUCHAR)"RPC_NT_SS_CANNOT_GET_CALL_HANDLE"; - case RPC_NT_NULL_REF_POINTER: - return (PUCHAR)"RPC_NT_NULL_REF_POINTER"; - case RPC_NT_ENUM_VALUE_OUT_OF_RANGE: - return (PUCHAR)"RPC_NT_ENUM_VALUE_OUT_OF_RANGE"; - case RPC_NT_BYTE_COUNT_TOO_SMALL: - return (PUCHAR)"RPC_NT_BYTE_COUNT_TOO_SMALL"; - case RPC_NT_BAD_STUB_DATA: - return (PUCHAR)"RPC_NT_BAD_STUB_DATA"; - case RPC_NT_INVALID_ES_ACTION: - return (PUCHAR)"RPC_NT_INVALID_ES_ACTION"; - case RPC_NT_WRONG_ES_VERSION: - return (PUCHAR)"RPC_NT_WRONG_ES_VERSION"; - case RPC_NT_WRONG_STUB_VERSION: - return (PUCHAR)"RPC_NT_WRONG_STUB_VERSION"; - case RPC_NT_INVALID_PIPE_OBJECT: - return (PUCHAR)"RPC_NT_INVALID_PIPE_OBJECT"; - case RPC_NT_INVALID_PIPE_OPERATION: - return (PUCHAR)"RPC_NT_INVALID_PIPE_OPERATION"; - case RPC_NT_WRONG_PIPE_VERSION: - return (PUCHAR)"RPC_NT_WRONG_PIPE_VERSION"; - case RPC_NT_PIPE_CLOSED: - return (PUCHAR)"RPC_NT_PIPE_CLOSED"; - case RPC_NT_PIPE_DISCIPLINE_ERROR: - return (PUCHAR)"RPC_NT_PIPE_DISCIPLINE_ERROR"; - case RPC_NT_PIPE_EMPTY: - return (PUCHAR)"RPC_NT_PIPE_EMPTY"; - case STATUS_PNP_BAD_MPS_TABLE: - return (PUCHAR)"STATUS_PNP_BAD_MPS_TABLE"; - case STATUS_PNP_TRANSLATION_FAILED: - return (PUCHAR)"STATUS_PNP_TRANSLATION_FAILED"; - case STATUS_PNP_IRQ_TRANSLATION_FAILED: - return (PUCHAR)"STATUS_PNP_IRQ_TRANSLATION_FAILED"; -#if (VER_PRODUCT_BUILD > 2600) - case STATUS_PNP_INVALID_ID: - return (PUCHAR)"STATUS_PNP_INVALID_ID"; -#endif - case STATUS_CTX_WINSTATION_NAME_INVALID: - return (PUCHAR)"STATUS_CTX_WINSTATION_NAME_INVALID"; - case STATUS_CTX_INVALID_PD: - return (PUCHAR)"STATUS_CTX_INVALID_PD"; - case STATUS_CTX_PD_NOT_FOUND: - return (PUCHAR)"STATUS_CTX_PD_NOT_FOUND"; - case STATUS_CTX_CLOSE_PENDING: - return (PUCHAR)"STATUS_CTX_CLOSE_PENDING"; - case STATUS_CTX_NO_OUTBUF: - return (PUCHAR)"STATUS_CTX_NO_OUTBUF"; - case STATUS_CTX_MODEM_INF_NOT_FOUND: - return (PUCHAR)"STATUS_CTX_MODEM_INF_NOT_FOUND"; - case STATUS_CTX_INVALID_MODEMNAME: - return (PUCHAR)"STATUS_CTX_INVALID_MODEMNAME"; - case STATUS_CTX_RESPONSE_ERROR: - return (PUCHAR)"STATUS_CTX_RESPONSE_ERROR"; - case STATUS_CTX_MODEM_RESPONSE_TIMEOUT: - return (PUCHAR)"STATUS_CTX_MODEM_RESPONSE_TIMEOUT"; - case STATUS_CTX_MODEM_RESPONSE_NO_CARRIER: - return (PUCHAR)"STATUS_CTX_MODEM_RESPONSE_NO_CARRIER"; - case STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE: - return (PUCHAR)"STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE"; - case STATUS_CTX_MODEM_RESPONSE_BUSY: - return (PUCHAR)"STATUS_CTX_MODEM_RESPONSE_BUSY"; - case STATUS_CTX_MODEM_RESPONSE_VOICE: - return (PUCHAR)"STATUS_CTX_MODEM_RESPONSE_VOICE"; - case STATUS_CTX_TD_ERROR: - return (PUCHAR)"STATUS_CTX_TD_ERROR"; - case STATUS_CTX_LICENSE_CLIENT_INVALID: - return (PUCHAR)"STATUS_CTX_LICENSE_CLIENT_INVALID"; - case STATUS_CTX_LICENSE_NOT_AVAILABLE: - return (PUCHAR)"STATUS_CTX_LICENSE_NOT_AVAILABLE"; - case STATUS_CTX_LICENSE_EXPIRED: - return (PUCHAR)"STATUS_CTX_LICENSE_EXPIRED"; - case STATUS_CTX_WINSTATION_NOT_FOUND: - return (PUCHAR)"STATUS_CTX_WINSTATION_NOT_FOUND"; - case STATUS_CTX_WINSTATION_NAME_COLLISION: - return (PUCHAR)"STATUS_CTX_WINSTATION_NAME_COLLISION"; - case STATUS_CTX_WINSTATION_BUSY: - return (PUCHAR)"STATUS_CTX_WINSTATION_BUSY"; - case STATUS_CTX_BAD_VIDEO_MODE: - return (PUCHAR)"STATUS_CTX_BAD_VIDEO_MODE"; - case STATUS_CTX_GRAPHICS_INVALID: - return (PUCHAR)"STATUS_CTX_GRAPHICS_INVALID"; - case STATUS_CTX_NOT_CONSOLE: - return (PUCHAR)"STATUS_CTX_NOT_CONSOLE"; - case STATUS_CTX_CLIENT_QUERY_TIMEOUT: - return (PUCHAR)"STATUS_CTX_CLIENT_QUERY_TIMEOUT"; - case STATUS_CTX_CONSOLE_DISCONNECT: - return (PUCHAR)"STATUS_CTX_CONSOLE_DISCONNECT"; - case STATUS_CTX_CONSOLE_CONNECT: - return (PUCHAR)"STATUS_CTX_CONSOLE_CONNECT"; - case STATUS_CTX_SHADOW_DENIED: - return (PUCHAR)"STATUS_CTX_SHADOW_DENIED"; - case STATUS_CTX_WINSTATION_ACCESS_DENIED: - return (PUCHAR)"STATUS_CTX_WINSTATION_ACCESS_DENIED"; - case STATUS_CTX_INVALID_WD: - return (PUCHAR)"STATUS_CTX_INVALID_WD"; - case STATUS_CTX_WD_NOT_FOUND: - return (PUCHAR)"STATUS_CTX_WD_NOT_FOUND"; - case STATUS_CTX_SHADOW_INVALID: - return (PUCHAR)"STATUS_CTX_SHADOW_INVALID"; - case STATUS_CTX_SHADOW_DISABLED: - return (PUCHAR)"STATUS_CTX_SHADOW_DISABLED"; - case STATUS_RDP_PROTOCOL_ERROR: - return (PUCHAR)"STATUS_RDP_PROTOCOL_ERROR"; - case STATUS_CTX_CLIENT_LICENSE_NOT_SET: - return (PUCHAR)"STATUS_CTX_CLIENT_LICENSE_NOT_SET"; - case STATUS_CTX_CLIENT_LICENSE_IN_USE: - return (PUCHAR)"STATUS_CTX_CLIENT_LICENSE_IN_USE"; -#if (VER_PRODUCT_BUILD >= 2600) - case STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE: - return (PUCHAR)"STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE"; - case STATUS_CTX_SHADOW_NOT_RUNNING: - return (PUCHAR)"STATUS_CTX_SHADOW_NOT_RUNNING"; - case STATUS_CLUSTER_INVALID_NODE: - return (PUCHAR)"STATUS_CLUSTER_INVALID_NODE"; - case STATUS_CLUSTER_NODE_EXISTS: - return (PUCHAR)"STATUS_CLUSTER_NODE_EXISTS"; - case STATUS_CLUSTER_JOIN_IN_PROGRESS: - return (PUCHAR)"STATUS_CLUSTER_JOIN_IN_PROGRESS"; - case STATUS_CLUSTER_NODE_NOT_FOUND: - return (PUCHAR)"STATUS_CLUSTER_NODE_NOT_FOUND"; - case STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND: - return (PUCHAR)"STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND"; - case STATUS_CLUSTER_NETWORK_EXISTS: - return (PUCHAR)"STATUS_CLUSTER_NETWORK_EXISTS"; - case STATUS_CLUSTER_NETWORK_NOT_FOUND: - return (PUCHAR)"STATUS_CLUSTER_NETWORK_NOT_FOUND"; - case STATUS_CLUSTER_NETINTERFACE_EXISTS: - return (PUCHAR)"STATUS_CLUSTER_NETINTERFACE_EXISTS"; - case STATUS_CLUSTER_NETINTERFACE_NOT_FOUND: - return (PUCHAR)"STATUS_CLUSTER_NETINTERFACE_NOT_FOUND"; - case STATUS_CLUSTER_INVALID_REQUEST: - return (PUCHAR)"STATUS_CLUSTER_INVALID_REQUEST"; - case STATUS_CLUSTER_INVALID_NETWORK_PROVIDER: - return (PUCHAR)"STATUS_CLUSTER_INVALID_NETWORK_PROVIDER"; - case STATUS_CLUSTER_NODE_DOWN: - return (PUCHAR)"STATUS_CLUSTER_NODE_DOWN"; - case STATUS_CLUSTER_NODE_UNREACHABLE: - return (PUCHAR)"STATUS_CLUSTER_NODE_UNREACHABLE"; - case STATUS_CLUSTER_NODE_NOT_MEMBER: - return (PUCHAR)"STATUS_CLUSTER_NODE_NOT_MEMBER"; - case STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS: - return (PUCHAR)"STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS"; - case STATUS_CLUSTER_INVALID_NETWORK: - return (PUCHAR)"STATUS_CLUSTER_INVALID_NETWORK"; - case STATUS_CLUSTER_NO_NET_ADAPTERS: - return (PUCHAR)"STATUS_CLUSTER_NO_NET_ADAPTERS"; - case STATUS_CLUSTER_NODE_UP: - return (PUCHAR)"STATUS_CLUSTER_NODE_UP"; - case STATUS_CLUSTER_NODE_PAUSED: - return (PUCHAR)"STATUS_CLUSTER_NODE_PAUSED"; - case STATUS_CLUSTER_NODE_NOT_PAUSED: - return (PUCHAR)"STATUS_CLUSTER_NODE_NOT_PAUSED"; - case STATUS_CLUSTER_NO_SECURITY_CONTEXT: - return (PUCHAR)"STATUS_CLUSTER_NO_SECURITY_CONTEXT"; - case STATUS_CLUSTER_NETWORK_NOT_INTERNAL: - return (PUCHAR)"STATUS_CLUSTER_NETWORK_NOT_INTERNAL"; - case STATUS_CLUSTER_POISONED: - return (PUCHAR)"STATUS_CLUSTER_POISONED"; -#endif - case STATUS_ACPI_INVALID_OPCODE: - return (PUCHAR)"STATUS_ACPI_INVALID_OPCODE"; - case STATUS_ACPI_STACK_OVERFLOW: - return (PUCHAR)"STATUS_ACPI_STACK_OVERFLOW"; - case STATUS_ACPI_ASSERT_FAILED: - return (PUCHAR)"STATUS_ACPI_ASSERT_FAILED"; - case STATUS_ACPI_INVALID_INDEX: - return (PUCHAR)"STATUS_ACPI_INVALID_INDEX"; - case STATUS_ACPI_INVALID_ARGUMENT: - return (PUCHAR)"STATUS_ACPI_INVALID_ARGUMENT"; - case STATUS_ACPI_FATAL: - return (PUCHAR)"STATUS_ACPI_FATAL"; - case STATUS_ACPI_INVALID_SUPERNAME: - return (PUCHAR)"STATUS_ACPI_INVALID_SUPERNAME"; - case STATUS_ACPI_INVALID_ARGTYPE: - return (PUCHAR)"STATUS_ACPI_INVALID_ARGTYPE"; - case STATUS_ACPI_INVALID_OBJTYPE: - return (PUCHAR)"STATUS_ACPI_INVALID_OBJTYPE"; - case STATUS_ACPI_INVALID_TARGETTYPE: - return (PUCHAR)"STATUS_ACPI_INVALID_TARGETTYPE"; - case STATUS_ACPI_INCORRECT_ARGUMENT_COUNT: - return (PUCHAR)"STATUS_ACPI_INCORRECT_ARGUMENT_COUNT"; - case STATUS_ACPI_ADDRESS_NOT_MAPPED: - return (PUCHAR)"STATUS_ACPI_ADDRESS_NOT_MAPPED"; - case STATUS_ACPI_INVALID_EVENTTYPE: - return (PUCHAR)"STATUS_ACPI_INVALID_EVENTTYPE"; - case STATUS_ACPI_HANDLER_COLLISION: - return (PUCHAR)"STATUS_ACPI_HANDLER_COLLISION"; - case STATUS_ACPI_INVALID_DATA: - return (PUCHAR)"STATUS_ACPI_INVALID_DATA"; - case STATUS_ACPI_INVALID_REGION: - return (PUCHAR)"STATUS_ACPI_INVALID_REGION"; - case STATUS_ACPI_INVALID_ACCESS_SIZE: - return (PUCHAR)"STATUS_ACPI_INVALID_ACCESS_SIZE"; - case STATUS_ACPI_ACQUIRE_GLOBAL_LOCK: - return (PUCHAR)"STATUS_ACPI_ACQUIRE_GLOBAL_LOCK"; - case STATUS_ACPI_ALREADY_INITIALIZED: - return (PUCHAR)"STATUS_ACPI_ALREADY_INITIALIZED"; - case STATUS_ACPI_NOT_INITIALIZED: - return (PUCHAR)"STATUS_ACPI_NOT_INITIALIZED"; - case STATUS_ACPI_INVALID_MUTEX_LEVEL: - return (PUCHAR)"STATUS_ACPI_INVALID_MUTEX_LEVEL"; - case STATUS_ACPI_MUTEX_NOT_OWNED: - return (PUCHAR)"STATUS_ACPI_MUTEX_NOT_OWNED"; - case STATUS_ACPI_MUTEX_NOT_OWNER: - return (PUCHAR)"STATUS_ACPI_MUTEX_NOT_OWNER"; - case STATUS_ACPI_RS_ACCESS: - return (PUCHAR)"STATUS_ACPI_RS_ACCESS"; - case STATUS_ACPI_INVALID_TABLE: - return (PUCHAR)"STATUS_ACPI_INVALID_TABLE"; - case STATUS_ACPI_REG_HANDLER_FAILED: - return (PUCHAR)"STATUS_ACPI_REG_HANDLER_FAILED"; - case STATUS_ACPI_POWER_REQUEST_FAILED: - return (PUCHAR)"STATUS_ACPI_POWER_REQUEST_FAILED"; -#if (VER_PRODUCT_BUILD >= 2600) - case STATUS_SXS_SECTION_NOT_FOUND: - return (PUCHAR)"STATUS_SXS_SECTION_NOT_FOUND"; - case STATUS_SXS_CANT_GEN_ACTCTX: - return (PUCHAR)"STATUS_SXS_CANT_GEN_ACTCTX"; - case STATUS_SXS_INVALID_ACTCTXDATA_FORMAT: - return (PUCHAR)"STATUS_SXS_INVALID_ACTCTXDATA_FORMAT"; - case STATUS_SXS_ASSEMBLY_NOT_FOUND: - return (PUCHAR)"STATUS_SXS_ASSEMBLY_NOT_FOUND"; - case STATUS_SXS_MANIFEST_FORMAT_ERROR: - return (PUCHAR)"STATUS_SXS_MANIFEST_FORMAT_ERROR"; - case STATUS_SXS_MANIFEST_PARSE_ERROR: - return (PUCHAR)"STATUS_SXS_MANIFEST_PARSE_ERROR"; - case STATUS_SXS_ACTIVATION_CONTEXT_DISABLED: - return (PUCHAR)"STATUS_SXS_ACTIVATION_CONTEXT_DISABLED"; - case STATUS_SXS_KEY_NOT_FOUND: - return (PUCHAR)"STATUS_SXS_KEY_NOT_FOUND"; - case STATUS_SXS_VERSION_CONFLICT: - return (PUCHAR)"STATUS_SXS_VERSION_CONFLICT"; - case STATUS_SXS_WRONG_SECTION_TYPE: - return (PUCHAR)"STATUS_SXS_WRONG_SECTION_TYPE"; - case STATUS_SXS_THREAD_QUERIES_DISABLED: - return (PUCHAR)"STATUS_SXS_THREAD_QUERIES_DISABLED"; - case STATUS_SXS_ASSEMBLY_MISSING: - return (PUCHAR)"STATUS_SXS_ASSEMBLY_MISSING"; - case STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET: - return (PUCHAR)"STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET"; - case STATUS_SXS_EARLY_DEACTIVATION: - return (PUCHAR)"STATUS_SXS_EARLY_DEACTIVATION"; - case STATUS_SXS_INVALID_DEACTIVATION: - return (PUCHAR)"STATUS_SXS_INVALID_DEACTIVATION"; - case STATUS_SXS_MULTIPLE_DEACTIVATION: - return (PUCHAR)"STATUS_SXS_MULTIPLE_DEACTIVATION"; - case STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY: - return (PUCHAR)"STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY"; - case STATUS_SXS_PROCESS_TERMINATION_REQUESTED: - return (PUCHAR)"STATUS_SXS_PROCESS_TERMINATION_REQUESTED"; -#endif -#if (VER_PRODUCT_BUILD > 2600) - case STATUS_SXS_CORRUPT_ACTIVATION_STACK: - return (PUCHAR)"STATUS_SXS_CORRUPT_ACTIVATION_STACK"; -#endif - default: - return (PUCHAR)"NTSTATSTR_UNKNOWN_STATUS"; - } -} +/***************************************************************************** + Company : Shree Ganesha Inc. + File Name : SkyWalker1Utility.cpp + Author : + Date : + Purpose : This file contains the Utility / Commanly Used Functions for the + SkyWalker1 Tuner Device Driver + + Revision History: +=============================================================================== + DATE VERSION AUTHOR REMARK +=============================================================================== + + XXth April,2009 01 Initial Version + +*****************************************************************************/ + +/* Include the Library and Other header file */ + +#include "SkyWalker1Main.h" //Common For all the Definitions, + //Declarations and Library Routines + +/* End of Inclusion the Library and Other header file */ + +/* Macro Definitions */ +/* End of Macro Definitions */ + +/* Global & Static variables Declaration */ +int nCurrentDebugLevel = EXTREME_LEVEL; +/* End of Global & Static variables Declaration */ + +/* External Variable Declaration */ +/* End of External Variable Declaration */ + +/* Declare Enumerations here */ +/* End of Enumeration declaration */ + +/* Function Prototypes */ +/* End of Function prototype definitions */ + +/***************************************************************************** + Function : PrintFunctionEntry + Description : Function to print Message while Entering the Function + IN PARAM : Function Name + OUT PARAM : NONE + PreCondition : NONE + PostCondtion : Function Entry Printed on Screen + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +void PrintFunctionEntry(IN char * pcFunctionName) +{ + + SkyWalkerDebugPrint(INTERMEDIATE_LEVEL, ("Entered into Function : %s() @ IRQ Level = %s\n", + pcFunctionName, + GetCurrentIrqlString())); + +} + +/***************************************************************************** + Function : PrintFunctionExit + Description : Function to print message while exiting the Function + IN PARAM : Function Name + Function Response + OUT PARAM : NONE + PreCondition : NONE + PostCondtion : Function Exit Printed on Screen + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +void PrintFunctionExit(IN char * pcFunctionName, IN NTSTATUS ntReturnCode) +{ + SkyWalkerDebugPrint(INTERMEDIATE_LEVEL, ("Exiting from Function : %s() @ IRQ Level = %s, with Status = %s(0x%08X)\n", + pcFunctionName, + GetCurrentIrqlString(), + NTStatusToString(ntReturnCode), + ntReturnCode)); +} + +/***************************************************************************** + Function : LowerDeviceCompletedIrp + Description : This function is a Completion Routine set when the IRP + is sent to the Lower Driver.It is called when the Lower Device + completes the IRP using the IoCompleteRequest() + IN PARAM : Reference of the Device Object whose lower device + was called + IRP Reference which sent Down + Context (In our case it is PKEVENT always) + OUT PARAM : STATUS_MORE_PROCESSING_REQUIRED always as Caller + will complete the IRP + PreCondition : IRP Passed Down to the Lower Device + PostCondtion : Event Set when the Response Received from the Lower Device + Logic : 1) Set the event is Pending Return from Lower Device + 2) return STATUS_MORE_PROCESSING_REQUIRED + Assumption : NONE + Note : This is usually called at the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS LowerDeviceCompletedIrp(IN PDEVICE_OBJECT pDeviceObject, + IN PIRP pIoRequestPacket, + IN PVOID pContext) +{ + + PrintFunctionEntry(__FUNCTION__); + + if (pIoRequestPacket->PendingReturned == TRUE) + { + // + // Set the event only if the lower driver has returned + // STATUS_PENDING earlier. This optimization removes the need to + // call KeSetEvent unnecessarily and improves performance because the + // system does not have to acquire an internal lock. + // (REFERENCE : Microsoft MSDN)http://support.microsoft.com/kb/320275 + + KeSetEvent ((PKEVENT) pContext, IO_NO_INCREMENT, FALSE); + } + + PrintFunctionExit(__FUNCTION__,STATUS_MORE_PROCESSING_REQUIRED); + // This is the only status that can be returned. + return STATUS_MORE_PROCESSING_REQUIRED; +} + +/***************************************************************************** + Function : PassDownIRPAndWaitForCompletion + Description : This function Passes the IRP to the Lower Device attached + and after passing waits for and Event which is triggerred after + the completion of the IRP (i.e. When IoCompleteRequest()is called by the + Lower Device) + IN PARAM : Reference of the Lower Device Object to be called + IRP Reference which needs to be pass Down + OUT PARAM : the Passing Down Status,Based on Status returned from + Lower Device + PreCondition : NONE + PostCondtion : IRP Passed Down to the Lower Device on successful Execution + Logic : 1) Initialize the Completion routine + 2) Copy Current IRP Stack location to Next (IoCopyCurrentIrpStackLocationToNext()) + 3) Set the IRP Completion Routine (IoSetCompeltionRoutine()) + 4) Call the Lower Device (IoCallDriver) + 5) Wait for the Irp Completion (KeWaitForSingleObject()) + Assumption : Device Extension has a Valid Lower Device Reference + Note : This is usually called at the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS PassDownIRPAndWaitForCompletion(IN PDEVICE_OBJECT pLowerDeviceObject, + IN PIRP pIoRequestPacket, + IN BOOLEAN bCopyStackLocation) +{ + NTSTATUS ntIrpProcessingStatus = STATUS_SUCCESS; + KEVENT kIrpCompleted; + PrintFunctionEntry(__FUNCTION__); + + //Initialize Kernel Event + KeInitializeEvent(&kIrpCompleted, //PKEVENT + NotificationEvent, //Type + FALSE); //State + + if(bCopyStackLocation) + { + //Copy Current IRP Stack to Next + IoCopyCurrentIrpStackLocationToNext(pIoRequestPacket); + } + + //Set Completion Routine + IoSetCompletionRoutine(pIoRequestPacket,//PIRP + LowerDeviceCompletedIrp,//Completion Routine + &kIrpCompleted, //PKEVENT (Context) + TRUE, //Flag on Success + TRUE, //Flag on Error + TRUE //Falg on Cancel + ); + //Call the Next Driver + ntIrpProcessingStatus = IoCallDriver(pLowerDeviceObject,pIoRequestPacket); + if(ntIrpProcessingStatus == STATUS_PENDING) + { + //IRP is to be processed + ntIrpProcessingStatus = KeWaitForSingleObject(&kIrpCompleted, //PKEVENT + Executive, //Wait Reason has to be Executive + KernelMode, //Must be kernel mode so + //that Stack will not Paged out + FALSE, //No Alert + NULL //Infinite Wait + ); + if(NT_SUCCESS(ntIrpProcessingStatus)) + { + //Lower Driver Completed the IRP (IoCompleteIrp()) + KeClearEvent(&kIrpCompleted); + ntIrpProcessingStatus = pIoRequestPacket->IoStatus.Status; + } + } + + PrintFunctionExit(__FUNCTION__,ntIrpProcessingStatus); + + return ntIrpProcessingStatus; + +} + + +/***************************************************************************** + Function : PassDownIRPAndForget + Description : This function Skips the IRP to the Lower Device attached + IN PARAM : Reference of the Lower Device Object to be called + IRP Reference which needs to be pass Down + OUT PARAM : the Passing Down Status,Based on Status returned from + IoCallDriver() Function + PreCondition : NONE + PostCondtion : IRP Passed Down to the Lower Device on successful Execution + Logic : 1) Skip the Current IRP + 2) Call the Lower Device Driver + Assumption : Device Extension has a Valid Lower Device Reference + Note : This is usually called at the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +NTSTATUS PassDownIRPAndForget(IN PDEVICE_OBJECT pLowerDeviceObject, + IN PIRP pIoRequestPacket) +{ + NTSTATUS ntIrpProcessingStatus = STATUS_SUCCESS; + + PrintFunctionEntry(__FUNCTION__); + + // + // As not setting a completion routine, skipping the stack + // location because it provides better performance. + // (REFERENCE : Microsoft MSDN) http://support.microsoft.com/kb/320275 + + //Skip Current IRP Stack + IoSkipCurrentIrpStackLocation(pIoRequestPacket); + + //Call the Lower Device + ntIrpProcessingStatus = IoCallDriver(pLowerDeviceObject,pIoRequestPacket); + + PrintFunctionExit(__FUNCTION__,ntIrpProcessingStatus); + + return ntIrpProcessingStatus; +} + +/***************************************************************************** + Function : CompleteIrpInDispatch + Description : This function Completes the IRP + IN PARAM : Reference of the Device Object + IRP Reference which needs to be Completed + OUT PARAM : NONE + PreCondition : NONE + PostCondtion : IRP Completed + Logic : NONE + Assumption : NONE + Note : This is usually called at the PASSIVE_LEVEL_IRQL + Revision History: + *****************************************************************************/ +VOID CompleteIrpInDispatch(IN PDEVICE_OBJECT pDeviceObject, + IN PIRP pIoRequestPacket) +{ + PrintFunctionEntry(__FUNCTION__); + + IoCompleteRequest(pIoRequestPacket,IO_NO_INCREMENT); + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); +} + +/***************************************************************************** + Function : Delay + Description : This function Delays Execution thread by the Microseconds passed + IN PARAM : Delay in MicroSeconds + OUT PARAM : NONE + PreCondition : NONE + PostCondtion : Delayed Execution Thread + Logic : NONE + Assumption : NONE + NOTE : This Function should always be called the the passive level + Revision History: + *****************************************************************************/ +VOID Delay(IN ULONG ulDelayInMicroSeconds) +{ + LARGE_INTEGER DelayIn100NanoSeconds; + PrintFunctionEntry(__FUNCTION__); + + //we need to Convert MicroSeconds to 100 nano Second Unit + //for the KeDelayExecutionThread() Call + //1000 nano Second = 1 MicroSecond + //100 nano Second = 0.1 MicroSecond + //Thus to Convert X Microseconds to Y 100 Nano Second Unit + // 1 (100 nano Second Unit) = 0.1 Micro Second Unit + // Y (100 nano Second Unit) = X Micro Second Unit + //Thus X (100 Nano Second Unit) = 0.1 Y MicroSecond Unit + //Thus Y (100 Nano Second Unit) = 10 * X (Micro Second) + //Thus Conversion Factor is 10 + + //Converting the Micro Seconds to 100 Nano Second Unit + //Negative Timeout Value is to Indicate the Relative Time from Current Time + DelayIn100NanoSeconds.QuadPart = (LONGLONG)ulDelayInMicroSeconds * (-10L); + + KeDelayExecutionThread(KernelMode, //Processor Wait Mode - Kernel + FALSE, //Lower Level Drivers are Not Alertable + &DelayIn100NanoSeconds); //Interval + + PrintFunctionExit(__FUNCTION__,STATUS_SUCCESS); +} +/***************************************************************************** + Function : GetCurrentIrqlString + Description : This function converts the IRQ Level to the String + IN PARAM : Status to be converted + OUT PARAM : NONE + PreCondition : NONE + PostCondtion : String value of the IRQ Level returned + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +char * GetCurrentIrqlString(void) +{ + ULONG ulCurrentIrql = KeGetCurrentIrql(); + + switch(ulCurrentIrql) + { + case PASSIVE_LEVEL: + return "PASSIVE_LEVEL"; + case APC_LEVEL: + return "APC_LEVEL"; + case DISPATCH_LEVEL: + return "DISPATCH_LEVEL"; + default: + return "DEVICE IRQL"; + } +} +/***************************************************************************** + Function : PrintDeviceChangeState + Description : This function Prints the Tuner State change + IN PARAM : State to which transition occurred + State From which transition occurred + OUT PARAM : NONE + PreCondition : NONE + PostCondtion : State Transition Printed + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +VOID PrintDeviceChangeState(IN KSSTATE ToState,IN KSSTATE FromState) +{ + PCHAR pFromState = NULL; + PCHAR pToState = NULL; + KSSTATE State = ToState; + PCHAR pState = NULL; + + for(INT nStateIndex = 0;nStateIndex < 2;nStateIndex++) + { + switch(State) + { + case KSSTATE_STOP: + pState = "Stop"; + break; + case KSSTATE_ACQUIRE: + pState = "Acquire"; + break; + case KSSTATE_PAUSE: + pState = "Pause"; + break; + case KSSTATE_RUN: + pState = "Run"; + break; + default : + pState = "Unknown"; + break; + + } + if(nStateIndex == 0) + { + pToState = pState; + State = FromState; + } + else + { + pFromState = pState; + } + } + + SkyWalkerDebugPrint(EXTREME_LEVEL, ("Change State From %s to %s\n",pFromState,pToState)); + +} + +/***************************************************************************** + Function : NTStatusToString + Description : This function converts the NTSTATUS value to String + IN PARAM : Status to be converted + OUT PARAM : NONE + PreCondition : NONE + PostCondtion : String value of the Status returned + Logic : NONE + Assumption : NONE + Note : NONE + Revision History: + *****************************************************************************/ +PUCHAR NTStatusToString(NTSTATUS Status) +{ + + switch (Status) { + case STATUS_SUCCESS: + return (PUCHAR)"STATUS_SUCCESS"; + case STATUS_WAIT_1: + return (PUCHAR)"STATUS_WAIT_1"; + case STATUS_WAIT_2: + return (PUCHAR)"STATUS_WAIT_2"; + case STATUS_WAIT_3: + return (PUCHAR)"STATUS_WAIT_3"; + case STATUS_WAIT_63: + return (PUCHAR)"STATUS_WAIT_63"; + case STATUS_ABANDONED: + return (PUCHAR)"STATUS_ABANDONED"; + case STATUS_ABANDONED_WAIT_63: + return (PUCHAR)"STATUS_ABANDONED_WAIT_63"; + case STATUS_USER_APC: + return (PUCHAR)"STATUS_USER_APC"; + case STATUS_KERNEL_APC: + return (PUCHAR)"STATUS_KERNEL_APC"; + case STATUS_ALERTED: + return (PUCHAR)"STATUS_ALERTED"; + case STATUS_TIMEOUT: + return (PUCHAR)"STATUS_TIMEOUT"; + case STATUS_PENDING: + return (PUCHAR)"STATUS_PENDING"; + case STATUS_REPARSE: + return (PUCHAR)"STATUS_REPARSE"; + case STATUS_MORE_ENTRIES: + return (PUCHAR)"STATUS_MORE_ENTRIES"; + case STATUS_NOT_ALL_ASSIGNED: + return (PUCHAR)"STATUS_NOT_ALL_ASSIGNED"; + case STATUS_SOME_NOT_MAPPED: + return (PUCHAR)"STATUS_SOME_NOT_MAPPED"; + case STATUS_OPLOCK_BREAK_IN_PROGRESS: + return (PUCHAR)"STATUS_OPLOCK_BREAK_IN_PROGRESS"; + case STATUS_VOLUME_MOUNTED: + return (PUCHAR)"STATUS_VOLUME_MOUNTED"; + case STATUS_RXACT_COMMITTED: + return (PUCHAR)"STATUS_RXACT_COMMITTED"; + case STATUS_NOTIFY_CLEANUP: + return (PUCHAR)"STATUS_NOTIFY_CLEANUP"; + case STATUS_NOTIFY_ENUM_DIR: + return (PUCHAR)"STATUS_NOTIFY_ENUM_DIR"; + case STATUS_NO_QUOTAS_FOR_ACCOUNT: + return (PUCHAR)"STATUS_NO_QUOTAS_FOR_ACCOUNT"; + case STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED: + return (PUCHAR)"STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED"; + case STATUS_PAGE_FAULT_TRANSITION: + return (PUCHAR)"STATUS_PAGE_FAULT_TRANSITION"; + case STATUS_PAGE_FAULT_DEMAND_ZERO: + return (PUCHAR)"STATUS_PAGE_FAULT_DEMAND_ZERO"; + case STATUS_PAGE_FAULT_COPY_ON_WRITE: + return (PUCHAR)"STATUS_PAGE_FAULT_COPY_ON_WRITE"; + case STATUS_PAGE_FAULT_GUARD_PAGE: + return (PUCHAR)"STATUS_PAGE_FAULT_GUARD_PAGE"; + case STATUS_PAGE_FAULT_PAGING_FILE: + return (PUCHAR)"STATUS_PAGE_FAULT_PAGING_FILE"; + case STATUS_CACHE_PAGE_LOCKED: + return (PUCHAR)"STATUS_CACHE_PAGE_LOCKED"; + case STATUS_CRASH_DUMP: + return (PUCHAR)"STATUS_CRASH_DUMP"; + case STATUS_BUFFER_ALL_ZEROS: + return (PUCHAR)"STATUS_BUFFER_ALL_ZEROS"; + case STATUS_REPARSE_OBJECT: + return (PUCHAR)"STATUS_REPARSE_OBJECT"; + case STATUS_RESOURCE_REQUIREMENTS_CHANGED: + return (PUCHAR)"STATUS_RESOURCE_REQUIREMENTS_CHANGED"; + case STATUS_TRANSLATION_COMPLETE: + return (PUCHAR)"STATUS_TRANSLATION_COMPLETE"; + case STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY: + return (PUCHAR)"STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY"; +#if (VER_PRODUCT_BUILD >= 2600) + case STATUS_NOTHING_TO_TERMINATE: + return (PUCHAR)"STATUS_NOTHING_TO_TERMINATE"; + case STATUS_PROCESS_NOT_IN_JOB: + return (PUCHAR)"STATUS_PROCESS_NOT_IN_JOB"; + case STATUS_PROCESS_IN_JOB: + return (PUCHAR)"STATUS_PROCESS_IN_JOB"; +#endif +#if (VER_PRODUCT_BUILD > 2600) + case STATUS_VOLSNAP_HIBERNATE_READY: + return (PUCHAR)"STATUS_VOLSNAP_HIBERNATE_READY"; + case STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY: + return (PUCHAR)"STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY"; +#endif +#if (VER_PRODUCT_BUILD >= 2600) + case STATUS_WAIT_FOR_OPLOCK: + return (PUCHAR)"STATUS_WAIT_FOR_OPLOCK"; +#endif + case DBG_EXCEPTION_HANDLED: + return (PUCHAR)"DBG_EXCEPTION_HANDLED"; + case DBG_CONTINUE: + return (PUCHAR)"DBG_CONTINUE"; + case STATUS_OBJECT_NAME_EXISTS: + return (PUCHAR)"STATUS_OBJECT_NAME_EXISTS"; + case STATUS_THREAD_WAS_SUSPENDED: + return (PUCHAR)"STATUS_THREAD_WAS_SUSPENDED"; + case STATUS_WORKING_SET_LIMIT_RANGE: + return (PUCHAR)"STATUS_WORKING_SET_LIMIT_RANGE"; + case STATUS_IMAGE_NOT_AT_BASE: + return (PUCHAR)"STATUS_IMAGE_NOT_AT_BASE"; + case STATUS_RXACT_STATE_CREATED: + return (PUCHAR)"STATUS_RXACT_STATE_CREATED"; + case STATUS_SEGMENT_NOTIFICATION: + return (PUCHAR)"STATUS_SEGMENT_NOTIFICATION"; + case STATUS_LOCAL_USER_SESSION_KEY: + return (PUCHAR)"STATUS_LOCAL_USER_SESSION_KEY"; + case STATUS_BAD_CURRENT_DIRECTORY: + return (PUCHAR)"STATUS_BAD_CURRENT_DIRECTORY"; + case STATUS_SERIAL_MORE_WRITES: + return (PUCHAR)"STATUS_SERIAL_MORE_WRITES"; + case STATUS_REGISTRY_RECOVERED: + return (PUCHAR)"STATUS_REGISTRY_RECOVERED"; + case STATUS_FT_READ_RECOVERY_FROM_BACKUP: + return (PUCHAR)"STATUS_FT_READ_RECOVERY_FROM_BACKUP"; + case STATUS_FT_WRITE_RECOVERY: + return (PUCHAR)"STATUS_FT_WRITE_RECOVERY"; + case STATUS_SERIAL_COUNTER_TIMEOUT: + return (PUCHAR)"STATUS_SERIAL_COUNTER_TIMEOUT"; + case STATUS_NULL_LM_PASSWORD: + return (PUCHAR)"STATUS_NULL_LM_PASSWORD"; + case STATUS_IMAGE_MACHINE_TYPE_MISMATCH: + return (PUCHAR)"STATUS_IMAGE_MACHINE_TYPE_MISMATCH"; + case STATUS_RECEIVE_PARTIAL: + return (PUCHAR)"STATUS_RECEIVE_PARTIAL"; + case STATUS_RECEIVE_EXPEDITED: + return (PUCHAR)"STATUS_RECEIVE_EXPEDITED"; + case STATUS_RECEIVE_PARTIAL_EXPEDITED: + return (PUCHAR)"STATUS_RECEIVE_PARTIAL_EXPEDITED"; + case STATUS_EVENT_DONE: + return (PUCHAR)"STATUS_EVENT_DONE"; + case STATUS_EVENT_PENDING: + return (PUCHAR)"STATUS_EVENT_PENDING"; + case STATUS_CHECKING_FILE_SYSTEM: + return (PUCHAR)"STATUS_CHECKING_FILE_SYSTEM"; + case STATUS_FATAL_APP_EXIT: + return (PUCHAR)"STATUS_FATAL_APP_EXIT"; + case STATUS_PREDEFINED_HANDLE: + return (PUCHAR)"STATUS_PREDEFINED_HANDLE"; + case STATUS_WAS_UNLOCKED: + return (PUCHAR)"STATUS_WAS_UNLOCKED"; + case STATUS_SERVICE_NOTIFICATION: + return (PUCHAR)"STATUS_SERVICE_NOTIFICATION"; + case STATUS_WAS_LOCKED: + return (PUCHAR)"STATUS_WAS_LOCKED"; + case STATUS_LOG_HARD_ERROR: + return (PUCHAR)"STATUS_LOG_HARD_ERROR"; + case STATUS_ALREADY_WIN32: + return (PUCHAR)"STATUS_ALREADY_WIN32"; + case STATUS_WX86_UNSIMULATE: + return (PUCHAR)"STATUS_WX86_UNSIMULATE"; + case STATUS_WX86_CONTINUE: + return (PUCHAR)"STATUS_WX86_CONTINUE"; + case STATUS_WX86_SINGLE_STEP: + return (PUCHAR)"STATUS_WX86_SINGLE_STEP"; + case STATUS_WX86_BREAKPOINT: + return (PUCHAR)"STATUS_WX86_BREAKPOINT"; + case STATUS_WX86_EXCEPTION_CONTINUE: + return (PUCHAR)"STATUS_WX86_EXCEPTION_CONTINUE"; + case STATUS_WX86_EXCEPTION_LASTCHANCE: + return (PUCHAR)"STATUS_WX86_EXCEPTION_LASTCHANCE"; + case STATUS_WX86_EXCEPTION_CHAIN: + return (PUCHAR)"STATUS_WX86_EXCEPTION_CHAIN"; + case STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE: + return (PUCHAR)"STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE"; + case STATUS_NO_YIELD_PERFORMED: + return (PUCHAR)"STATUS_NO_YIELD_PERFORMED"; + case STATUS_TIMER_RESUME_IGNORED: + return (PUCHAR)"STATUS_TIMER_RESUME_IGNORED"; + case STATUS_ARBITRATION_UNHANDLED: + return (PUCHAR)"STATUS_ARBITRATION_UNHANDLED"; + case STATUS_CARDBUS_NOT_SUPPORTED: + return (PUCHAR)"STATUS_CARDBUS_NOT_SUPPORTED"; + case STATUS_WX86_CREATEWX86TIB: + return (PUCHAR)"STATUS_WX86_CREATEWX86TIB"; + case STATUS_MP_PROCESSOR_MISMATCH: + return (PUCHAR)"STATUS_MP_PROCESSOR_MISMATCH"; +#if (VER_PRODUCT_BUILD >= 2600) + case STATUS_HIBERNATED: + return (PUCHAR)"STATUS_HIBERNATED"; + case STATUS_RESUME_HIBERNATION: + return (PUCHAR)"STATUS_RESUME_HIBERNATION"; +#endif +#if (VER_PRODUCT_BUILD > 2600) + case STATUS_FIRMWARE_UPDATED: + return (PUCHAR)"STATUS_FIRMWARE_UPDATED"; + case STATUS_DRIVERS_LEAKING_LOCKED_PAGES: + return (PUCHAR)"STATUS_DRIVERS_LEAKING_LOCKED_PAGES"; +#endif + case STATUS_WAKE_SYSTEM: + return (PUCHAR)"STATUS_WAKE_SYSTEM"; +#if (VER_PRODUCT_BUILD >= 2600) + case STATUS_DS_SHUTTING_DOWN: + return (PUCHAR)"STATUS_DS_SHUTTING_DOWN"; +#endif + case DBG_REPLY_LATER: + return (PUCHAR)"DBG_REPLY_LATER"; + case DBG_UNABLE_TO_PROVIDE_HANDLE: + return (PUCHAR)"DBG_UNABLE_TO_PROVIDE_HANDLE"; + case DBG_TERMINATE_THREAD: + return (PUCHAR)"DBG_TERMINATE_THREAD"; + case DBG_TERMINATE_PROCESS: + return (PUCHAR)"DBG_TERMINATE_PROCESS"; + case DBG_CONTROL_C: + return (PUCHAR)"DBG_CONTROL_C"; + case DBG_PRINTEXCEPTION_C: + return (PUCHAR)"DBG_PRINTEXCEPTION_C"; + case DBG_RIPEXCEPTION: + return (PUCHAR)"DBG_RIPEXCEPTION"; + case DBG_CONTROL_BREAK: + return (PUCHAR)"DBG_CONTROL_BREAK"; +#if (VER_PRODUCT_BUILD > 2600) + case DBG_COMMAND_EXCEPTION: + return (PUCHAR)"DBG_COMMAND_EXCEPTION"; +#endif + case RPC_NT_UUID_LOCAL_ONLY: + return (PUCHAR)"RPC_NT_UUID_LOCAL_ONLY"; + case RPC_NT_SEND_INCOMPLETE: + return (PUCHAR)"RPC_NT_SEND_INCOMPLETE"; + case STATUS_CTX_CDM_CONNECT: + return (PUCHAR)"STATUS_CTX_CDM_CONNECT"; + case STATUS_CTX_CDM_DISCONNECT: + return (PUCHAR)"STATUS_CTX_CDM_DISCONNECT"; + case STATUS_GUARD_PAGE_VIOLATION: + return (PUCHAR)"STATUS_GUARD_PAGE_VIOLATION"; + case STATUS_DATATYPE_MISALIGNMENT: + return (PUCHAR)"STATUS_DATATYPE_MISALIGNMENT"; + case STATUS_BREAKPOINT: + return (PUCHAR)"STATUS_BREAKPOINT"; + case STATUS_SINGLE_STEP: + return (PUCHAR)"STATUS_SINGLE_STEP"; + case STATUS_BUFFER_OVERFLOW: + return (PUCHAR)"STATUS_BUFFER_OVERFLOW"; + case STATUS_NO_MORE_FILES: + return (PUCHAR)"STATUS_NO_MORE_FILES"; + case STATUS_WAKE_SYSTEM_DEBUGGER: + return (PUCHAR)"STATUS_WAKE_SYSTEM_DEBUGGER"; + case STATUS_HANDLES_CLOSED: + return (PUCHAR)"STATUS_HANDLES_CLOSED"; + case STATUS_NO_INHERITANCE: + return (PUCHAR)"STATUS_NO_INHERITANCE"; + case STATUS_GUID_SUBSTITUTION_MADE: + return (PUCHAR)"STATUS_GUID_SUBSTITUTION_MADE"; + case STATUS_PARTIAL_COPY: + return (PUCHAR)"STATUS_PARTIAL_COPY"; + case STATUS_DEVICE_PAPER_EMPTY: + return (PUCHAR)"STATUS_DEVICE_PAPER_EMPTY"; + case STATUS_DEVICE_POWERED_OFF: + return (PUCHAR)"STATUS_DEVICE_POWERED_OFF"; + case STATUS_DEVICE_OFF_LINE: + return (PUCHAR)"STATUS_DEVICE_OFF_LINE"; + case STATUS_DEVICE_BUSY: + return (PUCHAR)"STATUS_DEVICE_BUSY"; + case STATUS_NO_MORE_EAS: + return (PUCHAR)"STATUS_NO_MORE_EAS"; + case STATUS_INVALID_EA_NAME: + return (PUCHAR)"STATUS_INVALID_EA_NAME"; + case STATUS_EA_LIST_INCONSISTENT: + return (PUCHAR)"STATUS_EA_LIST_INCONSISTENT"; + case STATUS_INVALID_EA_FLAG: + return (PUCHAR)"STATUS_INVALID_EA_FLAG"; + case STATUS_VERIFY_REQUIRED: + return (PUCHAR)"STATUS_VERIFY_REQUIRED"; + case STATUS_EXTRANEOUS_INFORMATION: + return (PUCHAR)"STATUS_EXTRANEOUS_INFORMATION"; + case STATUS_RXACT_COMMIT_NECESSARY: + return (PUCHAR)"STATUS_RXACT_COMMIT_NECESSARY"; + case STATUS_NO_MORE_ENTRIES: + return (PUCHAR)"STATUS_NO_MORE_ENTRIES"; + case STATUS_FILEMARK_DETECTED: + return (PUCHAR)"STATUS_FILEMARK_DETECTED"; + case STATUS_MEDIA_CHANGED: + return (PUCHAR)"STATUS_MEDIA_CHANGED"; + case STATUS_BUS_RESET: + return (PUCHAR)"STATUS_BUS_RESET"; + case STATUS_END_OF_MEDIA: + return (PUCHAR)"STATUS_END_OF_MEDIA"; + case STATUS_BEGINNING_OF_MEDIA: + return (PUCHAR)"STATUS_BEGINNING_OF_MEDIA"; + case STATUS_MEDIA_CHECK: + return (PUCHAR)"STATUS_MEDIA_CHECK"; + case STATUS_SETMARK_DETECTED: + return (PUCHAR)"STATUS_SETMARK_DETECTED"; + case STATUS_NO_DATA_DETECTED: + return (PUCHAR)"STATUS_NO_DATA_DETECTED"; + case STATUS_REDIRECTOR_HAS_OPEN_HANDLES: + return (PUCHAR)"STATUS_REDIRECTOR_HAS_OPEN_HANDLES"; + case STATUS_SERVER_HAS_OPEN_HANDLES: + return (PUCHAR)"STATUS_SERVER_HAS_OPEN_HANDLES"; + case STATUS_ALREADY_DISCONNECTED: + return (PUCHAR)"STATUS_ALREADY_DISCONNECTED"; + case STATUS_LONGJUMP: + return (PUCHAR)"STATUS_LONGJUMP"; +#if (VER_PRODUCT_BUILD >= 2600) + case STATUS_CLEANER_CARTRIDGE_INSTALLED: + return (PUCHAR)"STATUS_CLEANER_CARTRIDGE_INSTALLED"; + case STATUS_PLUGPLAY_QUERY_VETOED: + return (PUCHAR)"STATUS_PLUGPLAY_QUERY_VETOED"; + case STATUS_UNWIND_CONSOLIDATE: + return (PUCHAR)"STATUS_UNWIND_CONSOLIDATE"; +#endif +#if (VER_PRODUCT_BUILD > 2600) + case STATUS_REGISTRY_HIVE_RECOVERED: + return (PUCHAR)"STATUS_REGISTRY_HIVE_RECOVERED"; + case STATUS_DLL_MIGHT_BE_INSECURE: + return (PUCHAR)"STATUS_DLL_MIGHT_BE_INSECURE"; + case STATUS_DLL_MIGHT_BE_INCOMPATIBLE: + return (PUCHAR)"STATUS_DLL_MIGHT_BE_INCOMPATIBLE"; +#endif + case STATUS_DEVICE_REQUIRES_CLEANING: + return (PUCHAR)"STATUS_DEVICE_REQUIRES_CLEANING"; + case STATUS_DEVICE_DOOR_OPEN: + return (PUCHAR)"STATUS_DEVICE_DOOR_OPEN"; + case DBG_EXCEPTION_NOT_HANDLED: + return (PUCHAR)"DBG_EXCEPTION_NOT_HANDLED"; +#if (VER_PRODUCT_BUILD >= 2600) + case STATUS_CLUSTER_NODE_ALREADY_UP: + return (PUCHAR)"STATUS_CLUSTER_NODE_ALREADY_UP"; + case STATUS_CLUSTER_NODE_ALREADY_DOWN: + return (PUCHAR)"STATUS_CLUSTER_NODE_ALREADY_DOWN"; + case STATUS_CLUSTER_NETWORK_ALREADY_ONLINE: + return (PUCHAR)"STATUS_CLUSTER_NETWORK_ALREADY_ONLINE"; + case STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE: + return (PUCHAR)"STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE"; + case STATUS_CLUSTER_NODE_ALREADY_MEMBER: + return (PUCHAR)"STATUS_CLUSTER_NODE_ALREADY_MEMBER"; +#endif + case STATUS_UNSUCCESSFUL: + return (PUCHAR)"STATUS_UNSUCCESSFUL"; + case STATUS_NOT_IMPLEMENTED: + return (PUCHAR)"STATUS_NOT_IMPLEMENTED"; + case STATUS_INVALID_INFO_CLASS: + return (PUCHAR)"STATUS_INVALID_INFO_CLASS"; + case STATUS_INFO_LENGTH_MISMATCH: + return (PUCHAR)"STATUS_INFO_LENGTH_MISMATCH"; + case STATUS_ACCESS_VIOLATION: + return (PUCHAR)"STATUS_ACCESS_VIOLATION"; + case STATUS_IN_PAGE_ERROR: + return (PUCHAR)"STATUS_IN_PAGE_ERROR"; + case STATUS_PAGEFILE_QUOTA: + return (PUCHAR)"STATUS_PAGEFILE_QUOTA"; + case STATUS_INVALID_HANDLE: + return (PUCHAR)"STATUS_INVALID_HANDLE"; + case STATUS_BAD_INITIAL_STACK: + return (PUCHAR)"STATUS_BAD_INITIAL_STACK"; + case STATUS_BAD_INITIAL_PC: + return (PUCHAR)"STATUS_BAD_INITIAL_PC"; + case STATUS_INVALID_CID: + return (PUCHAR)"STATUS_INVALID_CID"; + case STATUS_TIMER_NOT_CANCELED: + return (PUCHAR)"STATUS_TIMER_NOT_CANCELED"; + case STATUS_INVALID_PARAMETER: + return (PUCHAR)"STATUS_INVALID_PARAMETER"; + case STATUS_NO_SUCH_DEVICE: + return (PUCHAR)"STATUS_NO_SUCH_DEVICE"; + case STATUS_NO_SUCH_FILE: + return (PUCHAR)"STATUS_NO_SUCH_FILE"; + case STATUS_INVALID_DEVICE_REQUEST: + return (PUCHAR)"STATUS_INVALID_DEVICE_REQUEST"; + case STATUS_END_OF_FILE: + return (PUCHAR)"STATUS_END_OF_FILE"; + case STATUS_WRONG_VOLUME: + return (PUCHAR)"STATUS_WRONG_VOLUME"; + case STATUS_NO_MEDIA_IN_DEVICE: + return (PUCHAR)"STATUS_NO_MEDIA_IN_DEVICE"; + case STATUS_UNRECOGNIZED_MEDIA: + return (PUCHAR)"STATUS_UNRECOGNIZED_MEDIA"; + case STATUS_NONEXISTENT_SECTOR: + return (PUCHAR)"STATUS_NONEXISTENT_SECTOR"; + case STATUS_MORE_PROCESSING_REQUIRED: + return (PUCHAR)"STATUS_MORE_PROCESSING_REQUIRED"; + case STATUS_NO_MEMORY: + return (PUCHAR)"STATUS_NO_MEMORY"; + case STATUS_CONFLICTING_ADDRESSES: + return (PUCHAR)"STATUS_CONFLICTING_ADDRESSES"; + case STATUS_NOT_MAPPED_VIEW: + return (PUCHAR)"STATUS_NOT_MAPPED_VIEW"; + case STATUS_UNABLE_TO_FREE_VM: + return (PUCHAR)"STATUS_UNABLE_TO_FREE_VM"; + case STATUS_UNABLE_TO_DELETE_SECTION: + return (PUCHAR)"STATUS_UNABLE_TO_DELETE_SECTION"; + case STATUS_INVALID_SYSTEM_SERVICE: + return (PUCHAR)"STATUS_INVALID_SYSTEM_SERVICE"; + case STATUS_ILLEGAL_INSTRUCTION: + return (PUCHAR)"STATUS_ILLEGAL_INSTRUCTION"; + case STATUS_INVALID_LOCK_SEQUENCE: + return (PUCHAR)"STATUS_INVALID_LOCK_SEQUENCE"; + case STATUS_INVALID_VIEW_SIZE: + return (PUCHAR)"STATUS_INVALID_VIEW_SIZE"; + case STATUS_INVALID_FILE_FOR_SECTION: + return (PUCHAR)"STATUS_INVALID_FILE_FOR_SECTION"; + case STATUS_ALREADY_COMMITTED: + return (PUCHAR)"STATUS_ALREADY_COMMITTED"; + case STATUS_ACCESS_DENIED: + return (PUCHAR)"STATUS_ACCESS_DENIED"; + case STATUS_BUFFER_TOO_SMALL: + return (PUCHAR)"STATUS_BUFFER_TOO_SMALL"; + case STATUS_OBJECT_TYPE_MISMATCH: + return (PUCHAR)"STATUS_OBJECT_TYPE_MISMATCH"; + case STATUS_NONCONTINUABLE_EXCEPTION: + return (PUCHAR)"STATUS_NONCONTINUABLE_EXCEPTION"; + case STATUS_INVALID_DISPOSITION: + return (PUCHAR)"STATUS_INVALID_DISPOSITION"; + case STATUS_UNWIND: + return (PUCHAR)"STATUS_UNWIND"; + case STATUS_BAD_STACK: + return (PUCHAR)"STATUS_BAD_STACK"; + case STATUS_INVALID_UNWIND_TARGET: + return (PUCHAR)"STATUS_INVALID_UNWIND_TARGET"; + case STATUS_NOT_LOCKED: + return (PUCHAR)"STATUS_NOT_LOCKED"; + case STATUS_PARITY_ERROR: + return (PUCHAR)"STATUS_PARITY_ERROR"; + case STATUS_UNABLE_TO_DECOMMIT_VM: + return (PUCHAR)"STATUS_UNABLE_TO_DECOMMIT_VM"; + case STATUS_NOT_COMMITTED: + return (PUCHAR)"STATUS_NOT_COMMITTED"; + case STATUS_INVALID_PORT_ATTRIBUTES: + return (PUCHAR)"STATUS_INVALID_PORT_ATTRIBUTES"; + case STATUS_PORT_MESSAGE_TOO_LONG: + return (PUCHAR)"STATUS_PORT_MESSAGE_TOO_LONG"; + case STATUS_INVALID_PARAMETER_MIX: + return (PUCHAR)"STATUS_INVALID_PARAMETER_MIX"; + case STATUS_INVALID_QUOTA_LOWER: + return (PUCHAR)"STATUS_INVALID_QUOTA_LOWER"; + case STATUS_DISK_CORRUPT_ERROR: + return (PUCHAR)"STATUS_DISK_CORRUPT_ERROR"; + case STATUS_OBJECT_NAME_INVALID: + return (PUCHAR)"STATUS_OBJECT_NAME_INVALID"; + case STATUS_OBJECT_NAME_NOT_FOUND: + return (PUCHAR)"STATUS_OBJECT_NAME_NOT_FOUND"; + case STATUS_OBJECT_NAME_COLLISION: + return (PUCHAR)"STATUS_OBJECT_NAME_COLLISION"; + case STATUS_PORT_DISCONNECTED: + return (PUCHAR)"STATUS_PORT_DISCONNECTED"; + case STATUS_DEVICE_ALREADY_ATTACHED: + return (PUCHAR)"STATUS_DEVICE_ALREADY_ATTACHED"; + case STATUS_OBJECT_PATH_INVALID: + return (PUCHAR)"STATUS_OBJECT_PATH_INVALID"; + case STATUS_OBJECT_PATH_NOT_FOUND: + return (PUCHAR)"STATUS_OBJECT_PATH_NOT_FOUND"; + case STATUS_OBJECT_PATH_SYNTAX_BAD: + return (PUCHAR)"STATUS_OBJECT_PATH_SYNTAX_BAD"; + case STATUS_DATA_OVERRUN: + return (PUCHAR)"STATUS_DATA_OVERRUN"; + case STATUS_DATA_LATE_ERROR: + return (PUCHAR)"STATUS_DATA_LATE_ERROR"; + case STATUS_DATA_ERROR: + return (PUCHAR)"STATUS_DATA_ERROR"; + case STATUS_CRC_ERROR: + return (PUCHAR)"STATUS_CRC_ERROR"; + case STATUS_SECTION_TOO_BIG: + return (PUCHAR)"STATUS_SECTION_TOO_BIG"; + case STATUS_PORT_CONNECTION_REFUSED: + return (PUCHAR)"STATUS_PORT_CONNECTION_REFUSED"; + case STATUS_INVALID_PORT_HANDLE: + return (PUCHAR)"STATUS_INVALID_PORT_HANDLE"; + case STATUS_SHARING_VIOLATION: + return (PUCHAR)"STATUS_SHARING_VIOLATION"; + case STATUS_QUOTA_EXCEEDED: + return (PUCHAR)"STATUS_QUOTA_EXCEEDED"; + case STATUS_INVALID_PAGE_PROTECTION: + return (PUCHAR)"STATUS_INVALID_PAGE_PROTECTION"; + case STATUS_MUTANT_NOT_OWNED: + return (PUCHAR)"STATUS_MUTANT_NOT_OWNED"; + case STATUS_SEMAPHORE_LIMIT_EXCEEDED: + return (PUCHAR)"STATUS_SEMAPHORE_LIMIT_EXCEEDED"; + case STATUS_PORT_ALREADY_SET: + return (PUCHAR)"STATUS_PORT_ALREADY_SET"; + case STATUS_SECTION_NOT_IMAGE: + return (PUCHAR)"STATUS_SECTION_NOT_IMAGE"; + case STATUS_SUSPEND_COUNT_EXCEEDED: + return (PUCHAR)"STATUS_SUSPEND_COUNT_EXCEEDED"; + case STATUS_THREAD_IS_TERMINATING: + return (PUCHAR)"STATUS_THREAD_IS_TERMINATING"; + case STATUS_BAD_WORKING_SET_LIMIT: + return (PUCHAR)"STATUS_BAD_WORKING_SET_LIMIT"; + case STATUS_INCOMPATIBLE_FILE_MAP: + return (PUCHAR)"STATUS_INCOMPATIBLE_FILE_MAP"; + case STATUS_SECTION_PROTECTION: + return (PUCHAR)"STATUS_SECTION_PROTECTION"; + case STATUS_EAS_NOT_SUPPORTED: + return (PUCHAR)"STATUS_EAS_NOT_SUPPORTED"; + case STATUS_EA_TOO_LARGE: + return (PUCHAR)"STATUS_EA_TOO_LARGE"; + case STATUS_NONEXISTENT_EA_ENTRY: + return (PUCHAR)"STATUS_NONEXISTENT_EA_ENTRY"; + case STATUS_NO_EAS_ON_FILE: + return (PUCHAR)"STATUS_NO_EAS_ON_FILE"; + case STATUS_EA_CORRUPT_ERROR: + return (PUCHAR)"STATUS_EA_CORRUPT_ERROR"; + case STATUS_FILE_LOCK_CONFLICT: + return (PUCHAR)"STATUS_FILE_LOCK_CONFLICT"; + case STATUS_LOCK_NOT_GRANTED: + return (PUCHAR)"STATUS_LOCK_NOT_GRANTED"; + case STATUS_DELETE_PENDING: + return (PUCHAR)"STATUS_DELETE_PENDING"; + case STATUS_CTL_FILE_NOT_SUPPORTED: + return (PUCHAR)"STATUS_CTL_FILE_NOT_SUPPORTED"; + case STATUS_UNKNOWN_REVISION: + return (PUCHAR)"STATUS_UNKNOWN_REVISION"; + case STATUS_REVISION_MISMATCH: + return (PUCHAR)"STATUS_REVISION_MISMATCH"; + case STATUS_INVALID_OWNER: + return (PUCHAR)"STATUS_INVALID_OWNER"; + case STATUS_INVALID_PRIMARY_GROUP: + return (PUCHAR)"STATUS_INVALID_PRIMARY_GROUP"; + case STATUS_NO_IMPERSONATION_TOKEN: + return (PUCHAR)"STATUS_NO_IMPERSONATION_TOKEN"; + case STATUS_CANT_DISABLE_MANDATORY: + return (PUCHAR)"STATUS_CANT_DISABLE_MANDATORY"; + case STATUS_NO_LOGON_SERVERS: + return (PUCHAR)"STATUS_NO_LOGON_SERVERS"; + case STATUS_NO_SUCH_LOGON_SESSION: + return (PUCHAR)"STATUS_NO_SUCH_LOGON_SESSION"; + case STATUS_NO_SUCH_PRIVILEGE: + return (PUCHAR)"STATUS_NO_SUCH_PRIVILEGE"; + case STATUS_PRIVILEGE_NOT_HELD: + return (PUCHAR)"STATUS_PRIVILEGE_NOT_HELD"; + case STATUS_INVALID_ACCOUNT_NAME: + return (PUCHAR)"STATUS_INVALID_ACCOUNT_NAME"; + case STATUS_USER_EXISTS: + return (PUCHAR)"STATUS_USER_EXISTS"; + case STATUS_NO_SUCH_USER: + return (PUCHAR)"STATUS_NO_SUCH_USER"; + case STATUS_GROUP_EXISTS: + return (PUCHAR)"STATUS_GROUP_EXISTS"; + case STATUS_NO_SUCH_GROUP: + return (PUCHAR)"STATUS_NO_SUCH_GROUP"; + case STATUS_MEMBER_IN_GROUP: + return (PUCHAR)"STATUS_MEMBER_IN_GROUP"; + case STATUS_MEMBER_NOT_IN_GROUP: + return (PUCHAR)"STATUS_MEMBER_NOT_IN_GROUP"; + case STATUS_LAST_ADMIN: + return (PUCHAR)"STATUS_LAST_ADMIN"; + case STATUS_WRONG_PASSWORD: + return (PUCHAR)"STATUS_WRONG_PASSWORD"; + case STATUS_ILL_FORMED_PASSWORD: + return (PUCHAR)"STATUS_ILL_FORMED_PASSWORD"; + case STATUS_PASSWORD_RESTRICTION: + return (PUCHAR)"STATUS_PASSWORD_RESTRICTION"; + case STATUS_LOGON_FAILURE: + return (PUCHAR)"STATUS_LOGON_FAILURE"; + case STATUS_ACCOUNT_RESTRICTION: + return (PUCHAR)"STATUS_ACCOUNT_RESTRICTION"; + case STATUS_INVALID_LOGON_HOURS: + return (PUCHAR)"STATUS_INVALID_LOGON_HOURS"; + case STATUS_INVALID_WORKSTATION: + return (PUCHAR)"STATUS_INVALID_WORKSTATION"; + case STATUS_PASSWORD_EXPIRED: + return (PUCHAR)"STATUS_PASSWORD_EXPIRED"; + case STATUS_ACCOUNT_DISABLED: + return (PUCHAR)"STATUS_ACCOUNT_DISABLED"; + case STATUS_NONE_MAPPED: + return (PUCHAR)"STATUS_NONE_MAPPED"; + case STATUS_TOO_MANY_LUIDS_REQUESTED: + return (PUCHAR)"STATUS_TOO_MANY_LUIDS_REQUESTED"; + case STATUS_LUIDS_EXHAUSTED: + return (PUCHAR)"STATUS_LUIDS_EXHAUSTED"; + case STATUS_INVALID_SUB_AUTHORITY: + return (PUCHAR)"STATUS_INVALID_SUB_AUTHORITY"; + case STATUS_INVALID_ACL: + return (PUCHAR)"STATUS_INVALID_ACL"; + case STATUS_INVALID_SID: + return (PUCHAR)"STATUS_INVALID_SID"; + case STATUS_INVALID_SECURITY_DESCR: + return (PUCHAR)"STATUS_INVALID_SECURITY_DESCR"; + case STATUS_PROCEDURE_NOT_FOUND: + return (PUCHAR)"STATUS_PROCEDURE_NOT_FOUND"; + case STATUS_INVALID_IMAGE_FORMAT: + return (PUCHAR)"STATUS_INVALID_IMAGE_FORMAT"; + case STATUS_NO_TOKEN: + return (PUCHAR)"STATUS_NO_TOKEN"; + case STATUS_BAD_INHERITANCE_ACL: + return (PUCHAR)"STATUS_BAD_INHERITANCE_ACL"; + case STATUS_RANGE_NOT_LOCKED: + return (PUCHAR)"STATUS_RANGE_NOT_LOCKED"; + case STATUS_DISK_FULL: + return (PUCHAR)"STATUS_DISK_FULL"; + case STATUS_SERVER_DISABLED: + return (PUCHAR)"STATUS_SERVER_DISABLED"; + case STATUS_SERVER_NOT_DISABLED: + return (PUCHAR)"STATUS_SERVER_NOT_DISABLED"; + case STATUS_TOO_MANY_GUIDS_REQUESTED: + return (PUCHAR)"STATUS_TOO_MANY_GUIDS_REQUESTED"; + case STATUS_GUIDS_EXHAUSTED: + return (PUCHAR)"STATUS_GUIDS_EXHAUSTED"; + case STATUS_INVALID_ID_AUTHORITY: + return (PUCHAR)"STATUS_INVALID_ID_AUTHORITY"; + case STATUS_AGENTS_EXHAUSTED: + return (PUCHAR)"STATUS_AGENTS_EXHAUSTED"; + case STATUS_INVALID_VOLUME_LABEL: + return (PUCHAR)"STATUS_INVALID_VOLUME_LABEL"; + case STATUS_SECTION_NOT_EXTENDED: + return (PUCHAR)"STATUS_SECTION_NOT_EXTENDED"; + case STATUS_NOT_MAPPED_DATA: + return (PUCHAR)"STATUS_NOT_MAPPED_DATA"; + case STATUS_RESOURCE_DATA_NOT_FOUND: + return (PUCHAR)"STATUS_RESOURCE_DATA_NOT_FOUND"; + case STATUS_RESOURCE_TYPE_NOT_FOUND: + return (PUCHAR)"STATUS_RESOURCE_TYPE_NOT_FOUND"; + case STATUS_RESOURCE_NAME_NOT_FOUND: + return (PUCHAR)"STATUS_RESOURCE_NAME_NOT_FOUND"; + case STATUS_ARRAY_BOUNDS_EXCEEDED: + return (PUCHAR)"STATUS_ARRAY_BOUNDS_EXCEEDED"; + case STATUS_FLOAT_DENORMAL_OPERAND: + return (PUCHAR)"STATUS_FLOAT_DENORMAL_OPERAND"; + case STATUS_FLOAT_DIVIDE_BY_ZERO: + return (PUCHAR)"STATUS_FLOAT_DIVIDE_BY_ZERO"; + case STATUS_FLOAT_INEXACT_RESULT: + return (PUCHAR)"STATUS_FLOAT_INEXACT_RESULT"; + case STATUS_FLOAT_INVALID_OPERATION: + return (PUCHAR)"STATUS_FLOAT_INVALID_OPERATION"; + case STATUS_FLOAT_OVERFLOW: + return (PUCHAR)"STATUS_FLOAT_OVERFLOW"; + case STATUS_FLOAT_STACK_CHECK: + return (PUCHAR)"STATUS_FLOAT_STACK_CHECK"; + case STATUS_FLOAT_UNDERFLOW: + return (PUCHAR)"STATUS_FLOAT_UNDERFLOW"; + case STATUS_INTEGER_DIVIDE_BY_ZERO: + return (PUCHAR)"STATUS_INTEGER_DIVIDE_BY_ZERO"; + case STATUS_INTEGER_OVERFLOW: + return (PUCHAR)"STATUS_INTEGER_OVERFLOW"; + case STATUS_PRIVILEGED_INSTRUCTION: + return (PUCHAR)"STATUS_PRIVILEGED_INSTRUCTION"; + case STATUS_TOO_MANY_PAGING_FILES: + return (PUCHAR)"STATUS_TOO_MANY_PAGING_FILES"; + case STATUS_FILE_INVALID: + return (PUCHAR)"STATUS_FILE_INVALID"; + case STATUS_ALLOTTED_SPACE_EXCEEDED: + return (PUCHAR)"STATUS_ALLOTTED_SPACE_EXCEEDED"; + case STATUS_INSUFFICIENT_RESOURCES: + return (PUCHAR)"STATUS_INSUFFICIENT_RESOURCES"; + case STATUS_DFS_EXIT_PATH_FOUND: + return (PUCHAR)"STATUS_DFS_EXIT_PATH_FOUND"; + case STATUS_DEVICE_DATA_ERROR: + return (PUCHAR)"STATUS_DEVICE_DATA_ERROR"; + case STATUS_DEVICE_NOT_CONNECTED: + return (PUCHAR)"STATUS_DEVICE_NOT_CONNECTED"; + case STATUS_DEVICE_POWER_FAILURE: + return (PUCHAR)"STATUS_DEVICE_POWER_FAILURE"; + case STATUS_FREE_VM_NOT_AT_BASE: + return (PUCHAR)"STATUS_FREE_VM_NOT_AT_BASE"; + case STATUS_MEMORY_NOT_ALLOCATED: + return (PUCHAR)"STATUS_MEMORY_NOT_ALLOCATED"; + case STATUS_WORKING_SET_QUOTA: + return (PUCHAR)"STATUS_WORKING_SET_QUOTA"; + case STATUS_MEDIA_WRITE_PROTECTED: + return (PUCHAR)"STATUS_MEDIA_WRITE_PROTECTED"; + case STATUS_DEVICE_NOT_READY: + return (PUCHAR)"STATUS_DEVICE_NOT_READY"; + case STATUS_INVALID_GROUP_ATTRIBUTES: + return (PUCHAR)"STATUS_INVALID_GROUP_ATTRIBUTES"; + case STATUS_BAD_IMPERSONATION_LEVEL: + return (PUCHAR)"STATUS_BAD_IMPERSONATION_LEVEL"; + case STATUS_CANT_OPEN_ANONYMOUS: + return (PUCHAR)"STATUS_CANT_OPEN_ANONYMOUS"; + case STATUS_BAD_VALIDATION_CLASS: + return (PUCHAR)"STATUS_BAD_VALIDATION_CLASS"; + case STATUS_BAD_TOKEN_TYPE: + return (PUCHAR)"STATUS_BAD_TOKEN_TYPE"; + case STATUS_BAD_MASTER_BOOT_RECORD: + return (PUCHAR)"STATUS_BAD_MASTER_BOOT_RECORD"; + case STATUS_INSTRUCTION_MISALIGNMENT: + return (PUCHAR)"STATUS_INSTRUCTION_MISALIGNMENT"; + case STATUS_INSTANCE_NOT_AVAILABLE: + return (PUCHAR)"STATUS_INSTANCE_NOT_AVAILABLE"; + case STATUS_PIPE_NOT_AVAILABLE: + return (PUCHAR)"STATUS_PIPE_NOT_AVAILABLE"; + case STATUS_INVALID_PIPE_STATE: + return (PUCHAR)"STATUS_INVALID_PIPE_STATE"; + case STATUS_PIPE_BUSY: + return (PUCHAR)"STATUS_PIPE_BUSY"; + case STATUS_ILLEGAL_FUNCTION: + return (PUCHAR)"STATUS_ILLEGAL_FUNCTION"; + case STATUS_PIPE_DISCONNECTED: + return (PUCHAR)"STATUS_PIPE_DISCONNECTED"; + case STATUS_PIPE_CLOSING: + return (PUCHAR)"STATUS_PIPE_CLOSING"; + case STATUS_PIPE_CONNECTED: + return (PUCHAR)"STATUS_PIPE_CONNECTED"; + case STATUS_PIPE_LISTENING: + return (PUCHAR)"STATUS_PIPE_LISTENING"; + case STATUS_INVALID_READ_MODE: + return (PUCHAR)"STATUS_INVALID_READ_MODE"; + case STATUS_IO_TIMEOUT: + return (PUCHAR)"STATUS_IO_TIMEOUT"; + case STATUS_FILE_FORCED_CLOSED: + return (PUCHAR)"STATUS_FILE_FORCED_CLOSED"; + case STATUS_PROFILING_NOT_STARTED: + return (PUCHAR)"STATUS_PROFILING_NOT_STARTED"; + case STATUS_PROFILING_NOT_STOPPED: + return (PUCHAR)"STATUS_PROFILING_NOT_STOPPED"; + case STATUS_COULD_NOT_INTERPRET: + return (PUCHAR)"STATUS_COULD_NOT_INTERPRET"; + case STATUS_FILE_IS_A_DIRECTORY: + return (PUCHAR)"STATUS_FILE_IS_A_DIRECTORY"; + case STATUS_NOT_SUPPORTED: + return (PUCHAR)"STATUS_NOT_SUPPORTED"; + case STATUS_REMOTE_NOT_LISTENING: + return (PUCHAR)"STATUS_REMOTE_NOT_LISTENING"; + case STATUS_DUPLICATE_NAME: + return (PUCHAR)"STATUS_DUPLICATE_NAME"; + case STATUS_BAD_NETWORK_PATH: + return (PUCHAR)"STATUS_BAD_NETWORK_PATH"; + case STATUS_NETWORK_BUSY: + return (PUCHAR)"STATUS_NETWORK_BUSY"; + case STATUS_DEVICE_DOES_NOT_EXIST: + return (PUCHAR)"STATUS_DEVICE_DOES_NOT_EXIST"; + case STATUS_TOO_MANY_COMMANDS: + return (PUCHAR)"STATUS_TOO_MANY_COMMANDS"; + case STATUS_ADAPTER_HARDWARE_ERROR: + return (PUCHAR)"STATUS_ADAPTER_HARDWARE_ERROR"; + case STATUS_INVALID_NETWORK_RESPONSE: + return (PUCHAR)"STATUS_INVALID_NETWORK_RESPONSE"; + case STATUS_UNEXPECTED_NETWORK_ERROR: + return (PUCHAR)"STATUS_UNEXPECTED_NETWORK_ERROR"; + case STATUS_BAD_REMOTE_ADAPTER: + return (PUCHAR)"STATUS_BAD_REMOTE_ADAPTER"; + case STATUS_PRINT_QUEUE_FULL: + return (PUCHAR)"STATUS_PRINT_QUEUE_FULL"; + case STATUS_NO_SPOOL_SPACE: + return (PUCHAR)"STATUS_NO_SPOOL_SPACE"; + case STATUS_PRINT_CANCELLED: + return (PUCHAR)"STATUS_PRINT_CANCELLED"; + case STATUS_NETWORK_NAME_DELETED: + return (PUCHAR)"STATUS_NETWORK_NAME_DELETED"; + case STATUS_NETWORK_ACCESS_DENIED: + return (PUCHAR)"STATUS_NETWORK_ACCESS_DENIED"; + case STATUS_BAD_DEVICE_TYPE: + return (PUCHAR)"STATUS_BAD_DEVICE_TYPE"; + case STATUS_BAD_NETWORK_NAME: + return (PUCHAR)"STATUS_BAD_NETWORK_NAME"; + case STATUS_TOO_MANY_NAMES: + return (PUCHAR)"STATUS_TOO_MANY_NAMES"; + case STATUS_TOO_MANY_SESSIONS: + return (PUCHAR)"STATUS_TOO_MANY_SESSIONS"; + case STATUS_SHARING_PAUSED: + return (PUCHAR)"STATUS_SHARING_PAUSED"; + case STATUS_REQUEST_NOT_ACCEPTED: + return (PUCHAR)"STATUS_REQUEST_NOT_ACCEPTED"; + case STATUS_REDIRECTOR_PAUSED: + return (PUCHAR)"STATUS_REDIRECTOR_PAUSED"; + case STATUS_NET_WRITE_FAULT: + return (PUCHAR)"STATUS_NET_WRITE_FAULT"; + case STATUS_PROFILING_AT_LIMIT: + return (PUCHAR)"STATUS_PROFILING_AT_LIMIT"; + case STATUS_NOT_SAME_DEVICE: + return (PUCHAR)"STATUS_NOT_SAME_DEVICE"; + case STATUS_FILE_RENAMED: + return (PUCHAR)"STATUS_FILE_RENAMED"; + case STATUS_VIRTUAL_CIRCUIT_CLOSED: + return (PUCHAR)"STATUS_VIRTUAL_CIRCUIT_CLOSED"; + case STATUS_NO_SECURITY_ON_OBJECT: + return (PUCHAR)"STATUS_NO_SECURITY_ON_OBJECT"; + case STATUS_CANT_WAIT: + return (PUCHAR)"STATUS_CANT_WAIT"; + case STATUS_PIPE_EMPTY: + return (PUCHAR)"STATUS_PIPE_EMPTY"; + case STATUS_CANT_ACCESS_DOMAIN_INFO: + return (PUCHAR)"STATUS_CANT_ACCESS_DOMAIN_INFO"; + case STATUS_CANT_TERMINATE_SELF: + return (PUCHAR)"STATUS_CANT_TERMINATE_SELF"; + case STATUS_INVALID_SERVER_STATE: + return (PUCHAR)"STATUS_INVALID_SERVER_STATE"; + case STATUS_INVALID_DOMAIN_STATE: + return (PUCHAR)"STATUS_INVALID_DOMAIN_STATE"; + case STATUS_INVALID_DOMAIN_ROLE: + return (PUCHAR)"STATUS_INVALID_DOMAIN_ROLE"; + case STATUS_NO_SUCH_DOMAIN: + return (PUCHAR)"STATUS_NO_SUCH_DOMAIN"; + case STATUS_DOMAIN_EXISTS: + return (PUCHAR)"STATUS_DOMAIN_EXISTS"; + case STATUS_DOMAIN_LIMIT_EXCEEDED: + return (PUCHAR)"STATUS_DOMAIN_LIMIT_EXCEEDED"; + case STATUS_OPLOCK_NOT_GRANTED: + return (PUCHAR)"STATUS_OPLOCK_NOT_GRANTED"; + case STATUS_INVALID_OPLOCK_PROTOCOL: + return (PUCHAR)"STATUS_INVALID_OPLOCK_PROTOCOL"; + case STATUS_INTERNAL_DB_CORRUPTION: + return (PUCHAR)"STATUS_INTERNAL_DB_CORRUPTION"; + case STATUS_INTERNAL_ERROR: + return (PUCHAR)"STATUS_INTERNAL_ERROR"; + case STATUS_GENERIC_NOT_MAPPED: + return (PUCHAR)"STATUS_GENERIC_NOT_MAPPED"; + case STATUS_BAD_DESCRIPTOR_FORMAT: + return (PUCHAR)"STATUS_BAD_DESCRIPTOR_FORMAT"; + case STATUS_INVALID_USER_BUFFER: + return (PUCHAR)"STATUS_INVALID_USER_BUFFER"; + case STATUS_UNEXPECTED_IO_ERROR: + return (PUCHAR)"STATUS_UNEXPECTED_IO_ERROR"; + case STATUS_UNEXPECTED_MM_CREATE_ERR: + return (PUCHAR)"STATUS_UNEXPECTED_MM_CREATE_ERR"; + case STATUS_UNEXPECTED_MM_MAP_ERROR: + return (PUCHAR)"STATUS_UNEXPECTED_MM_MAP_ERROR"; + case STATUS_UNEXPECTED_MM_EXTEND_ERR: + return (PUCHAR)"STATUS_UNEXPECTED_MM_EXTEND_ERR"; + case STATUS_NOT_LOGON_PROCESS: + return (PUCHAR)"STATUS_NOT_LOGON_PROCESS"; + case STATUS_LOGON_SESSION_EXISTS: + return (PUCHAR)"STATUS_LOGON_SESSION_EXISTS"; + case STATUS_INVALID_PARAMETER_1: + return (PUCHAR)"STATUS_INVALID_PARAMETER_1"; + case STATUS_INVALID_PARAMETER_2: + return (PUCHAR)"STATUS_INVALID_PARAMETER_2"; + case STATUS_INVALID_PARAMETER_3: + return (PUCHAR)"STATUS_INVALID_PARAMETER_3"; + case STATUS_INVALID_PARAMETER_4: + return (PUCHAR)"STATUS_INVALID_PARAMETER_4"; + case STATUS_INVALID_PARAMETER_5: + return (PUCHAR)"STATUS_INVALID_PARAMETER_5"; + case STATUS_INVALID_PARAMETER_6: + return (PUCHAR)"STATUS_INVALID_PARAMETER_6"; + case STATUS_INVALID_PARAMETER_7: + return (PUCHAR)"STATUS_INVALID_PARAMETER_7"; + case STATUS_INVALID_PARAMETER_8: + return (PUCHAR)"STATUS_INVALID_PARAMETER_8"; + case STATUS_INVALID_PARAMETER_9: + return (PUCHAR)"STATUS_INVALID_PARAMETER_9"; + case STATUS_INVALID_PARAMETER_10: + return (PUCHAR)"STATUS_INVALID_PARAMETER_10"; + case STATUS_INVALID_PARAMETER_11: + return (PUCHAR)"STATUS_INVALID_PARAMETER_11"; + case STATUS_INVALID_PARAMETER_12: + return (PUCHAR)"STATUS_INVALID_PARAMETER_12"; + case STATUS_REDIRECTOR_NOT_STARTED: + return (PUCHAR)"STATUS_REDIRECTOR_NOT_STARTED"; + case STATUS_REDIRECTOR_STARTED: + return (PUCHAR)"STATUS_REDIRECTOR_STARTED"; + case STATUS_STACK_OVERFLOW: + return (PUCHAR)"STATUS_STACK_OVERFLOW"; + case STATUS_NO_SUCH_PACKAGE: + return (PUCHAR)"STATUS_NO_SUCH_PACKAGE"; + case STATUS_BAD_FUNCTION_TABLE: + return (PUCHAR)"STATUS_BAD_FUNCTION_TABLE"; + case STATUS_VARIABLE_NOT_FOUND: + return (PUCHAR)"STATUS_VARIABLE_NOT_FOUND"; + case STATUS_DIRECTORY_NOT_EMPTY: + return (PUCHAR)"STATUS_DIRECTORY_NOT_EMPTY"; + case STATUS_FILE_CORRUPT_ERROR: + return (PUCHAR)"STATUS_FILE_CORRUPT_ERROR"; + case STATUS_NOT_A_DIRECTORY: + return (PUCHAR)"STATUS_NOT_A_DIRECTORY"; + case STATUS_BAD_LOGON_SESSION_STATE: + return (PUCHAR)"STATUS_BAD_LOGON_SESSION_STATE"; + case STATUS_LOGON_SESSION_COLLISION: + return (PUCHAR)"STATUS_LOGON_SESSION_COLLISION"; + case STATUS_NAME_TOO_LONG: + return (PUCHAR)"STATUS_NAME_TOO_LONG"; + case STATUS_FILES_OPEN: + return (PUCHAR)"STATUS_FILES_OPEN"; + case STATUS_CONNECTION_IN_USE: + return (PUCHAR)"STATUS_CONNECTION_IN_USE"; + case STATUS_MESSAGE_NOT_FOUND: + return (PUCHAR)"STATUS_MESSAGE_NOT_FOUND"; + case STATUS_PROCESS_IS_TERMINATING: + return (PUCHAR)"STATUS_PROCESS_IS_TERMINATING"; + case STATUS_INVALID_LOGON_TYPE: + return (PUCHAR)"STATUS_INVALID_LOGON_TYPE"; + case STATUS_NO_GUID_TRANSLATION: + return (PUCHAR)"STATUS_NO_GUID_TRANSLATION"; + case STATUS_CANNOT_IMPERSONATE: + return (PUCHAR)"STATUS_CANNOT_IMPERSONATE"; + case STATUS_IMAGE_ALREADY_LOADED: + return (PUCHAR)"STATUS_IMAGE_ALREADY_LOADED"; + case STATUS_ABIOS_NOT_PRESENT: + return (PUCHAR)"STATUS_ABIOS_NOT_PRESENT"; + case STATUS_ABIOS_LID_NOT_EXIST: + return (PUCHAR)"STATUS_ABIOS_LID_NOT_EXIST"; + case STATUS_ABIOS_LID_ALREADY_OWNED: + return (PUCHAR)"STATUS_ABIOS_LID_ALREADY_OWNED"; + case STATUS_ABIOS_NOT_LID_OWNER: + return (PUCHAR)"STATUS_ABIOS_NOT_LID_OWNER"; + case STATUS_ABIOS_INVALID_COMMAND: + return (PUCHAR)"STATUS_ABIOS_INVALID_COMMAND"; + case STATUS_ABIOS_INVALID_LID: + return (PUCHAR)"STATUS_ABIOS_INVALID_LID"; + case STATUS_ABIOS_SELECTOR_NOT_AVAILABLE: + return (PUCHAR)"STATUS_ABIOS_SELECTOR_NOT_AVAILABLE"; + case STATUS_ABIOS_INVALID_SELECTOR: + return (PUCHAR)"STATUS_ABIOS_INVALID_SELECTOR"; + case STATUS_NO_LDT: + return (PUCHAR)"STATUS_NO_LDT"; + case STATUS_INVALID_LDT_SIZE: + return (PUCHAR)"STATUS_INVALID_LDT_SIZE"; + case STATUS_INVALID_LDT_OFFSET: + return (PUCHAR)"STATUS_INVALID_LDT_OFFSET"; + case STATUS_INVALID_LDT_DESCRIPTOR: + return (PUCHAR)"STATUS_INVALID_LDT_DESCRIPTOR"; + case STATUS_INVALID_IMAGE_NE_FORMAT: + return (PUCHAR)"STATUS_INVALID_IMAGE_NE_FORMAT"; + case STATUS_RXACT_INVALID_STATE: + return (PUCHAR)"STATUS_RXACT_INVALID_STATE"; + case STATUS_RXACT_COMMIT_FAILURE: + return (PUCHAR)"STATUS_RXACT_COMMIT_FAILURE"; + case STATUS_MAPPED_FILE_SIZE_ZERO: + return (PUCHAR)"STATUS_MAPPED_FILE_SIZE_ZERO"; + case STATUS_TOO_MANY_OPENED_FILES: + return (PUCHAR)"STATUS_TOO_MANY_OPENED_FILES"; + case STATUS_CANCELLED: + return (PUCHAR)"STATUS_CANCELLED"; + case STATUS_CANNOT_DELETE: + return (PUCHAR)"STATUS_CANNOT_DELETE"; + case STATUS_INVALID_COMPUTER_NAME: + return (PUCHAR)"STATUS_INVALID_COMPUTER_NAME"; + case STATUS_FILE_DELETED: + return (PUCHAR)"STATUS_FILE_DELETED"; + case STATUS_SPECIAL_ACCOUNT: + return (PUCHAR)"STATUS_SPECIAL_ACCOUNT"; + case STATUS_SPECIAL_GROUP: + return (PUCHAR)"STATUS_SPECIAL_GROUP"; + case STATUS_SPECIAL_USER: + return (PUCHAR)"STATUS_SPECIAL_USER"; + case STATUS_MEMBERS_PRIMARY_GROUP: + return (PUCHAR)"STATUS_MEMBERS_PRIMARY_GROUP"; + case STATUS_FILE_CLOSED: + return (PUCHAR)"STATUS_FILE_CLOSED"; + case STATUS_TOO_MANY_THREADS: + return (PUCHAR)"STATUS_TOO_MANY_THREADS"; + case STATUS_THREAD_NOT_IN_PROCESS: + return (PUCHAR)"STATUS_THREAD_NOT_IN_PROCESS"; + case STATUS_TOKEN_ALREADY_IN_USE: + return (PUCHAR)"STATUS_TOKEN_ALREADY_IN_USE"; + case STATUS_PAGEFILE_QUOTA_EXCEEDED: + return (PUCHAR)"STATUS_PAGEFILE_QUOTA_EXCEEDED"; + case STATUS_COMMITMENT_LIMIT: + return (PUCHAR)"STATUS_COMMITMENT_LIMIT"; + case STATUS_INVALID_IMAGE_LE_FORMAT: + return (PUCHAR)"STATUS_INVALID_IMAGE_LE_FORMAT"; + case STATUS_INVALID_IMAGE_NOT_MZ: + return (PUCHAR)"STATUS_INVALID_IMAGE_NOT_MZ"; + case STATUS_INVALID_IMAGE_PROTECT: + return (PUCHAR)"STATUS_INVALID_IMAGE_PROTECT"; + case STATUS_INVALID_IMAGE_WIN_16: + return (PUCHAR)"STATUS_INVALID_IMAGE_WIN_16"; + case STATUS_LOGON_SERVER_CONFLICT: + return (PUCHAR)"STATUS_LOGON_SERVER_CONFLICT"; + case STATUS_TIME_DIFFERENCE_AT_DC: + return (PUCHAR)"STATUS_TIME_DIFFERENCE_AT_DC"; + case STATUS_SYNCHRONIZATION_REQUIRED: + return (PUCHAR)"STATUS_SYNCHRONIZATION_REQUIRED"; + case STATUS_DLL_NOT_FOUND: + return (PUCHAR)"STATUS_DLL_NOT_FOUND"; + case STATUS_OPEN_FAILED: + return (PUCHAR)"STATUS_OPEN_FAILED"; + case STATUS_IO_PRIVILEGE_FAILED: + return (PUCHAR)"STATUS_IO_PRIVILEGE_FAILED"; + case STATUS_ORDINAL_NOT_FOUND: + return (PUCHAR)"STATUS_ORDINAL_NOT_FOUND"; + case STATUS_ENTRYPOINT_NOT_FOUND: + return (PUCHAR)"STATUS_ENTRYPOINT_NOT_FOUND"; + case STATUS_CONTROL_C_EXIT: + return (PUCHAR)"STATUS_CONTROL_C_EXIT"; + case STATUS_LOCAL_DISCONNECT: + return (PUCHAR)"STATUS_LOCAL_DISCONNECT"; + case STATUS_REMOTE_DISCONNECT: + return (PUCHAR)"STATUS_REMOTE_DISCONNECT"; + case STATUS_REMOTE_RESOURCES: + return (PUCHAR)"STATUS_REMOTE_RESOURCES"; + case STATUS_LINK_FAILED: + return (PUCHAR)"STATUS_LINK_FAILED"; + case STATUS_LINK_TIMEOUT: + return (PUCHAR)"STATUS_LINK_TIMEOUT"; + case STATUS_INVALID_CONNECTION: + return (PUCHAR)"STATUS_INVALID_CONNECTION"; + case STATUS_INVALID_ADDRESS: + return (PUCHAR)"STATUS_INVALID_ADDRESS"; + case STATUS_DLL_INIT_FAILED: + return (PUCHAR)"STATUS_DLL_INIT_FAILED"; + case STATUS_MISSING_SYSTEMFILE: + return (PUCHAR)"STATUS_MISSING_SYSTEMFILE"; + case STATUS_UNHANDLED_EXCEPTION: + return (PUCHAR)"STATUS_UNHANDLED_EXCEPTION"; + case STATUS_APP_INIT_FAILURE: + return (PUCHAR)"STATUS_APP_INIT_FAILURE"; + case STATUS_PAGEFILE_CREATE_FAILED: + return (PUCHAR)"STATUS_PAGEFILE_CREATE_FAILED"; + case STATUS_NO_PAGEFILE: + return (PUCHAR)"STATUS_NO_PAGEFILE"; + case STATUS_INVALID_LEVEL: + return (PUCHAR)"STATUS_INVALID_LEVEL"; + case STATUS_WRONG_PASSWORD_CORE: + return (PUCHAR)"STATUS_WRONG_PASSWORD_CORE"; + case STATUS_ILLEGAL_FLOAT_CONTEXT: + return (PUCHAR)"STATUS_ILLEGAL_FLOAT_CONTEXT"; + case STATUS_PIPE_BROKEN: + return (PUCHAR)"STATUS_PIPE_BROKEN"; + case STATUS_REGISTRY_CORRUPT: + return (PUCHAR)"STATUS_REGISTRY_CORRUPT"; + case STATUS_REGISTRY_IO_FAILED: + return (PUCHAR)"STATUS_REGISTRY_IO_FAILED"; + case STATUS_NO_EVENT_PAIR: + return (PUCHAR)"STATUS_NO_EVENT_PAIR"; + case STATUS_UNRECOGNIZED_VOLUME: + return (PUCHAR)"STATUS_UNRECOGNIZED_VOLUME"; + case STATUS_SERIAL_NO_DEVICE_INITED: + return (PUCHAR)"STATUS_SERIAL_NO_DEVICE_INITED"; + case STATUS_NO_SUCH_ALIAS: + return (PUCHAR)"STATUS_NO_SUCH_ALIAS"; + case STATUS_MEMBER_NOT_IN_ALIAS: + return (PUCHAR)"STATUS_MEMBER_NOT_IN_ALIAS"; + case STATUS_MEMBER_IN_ALIAS: + return (PUCHAR)"STATUS_MEMBER_IN_ALIAS"; + case STATUS_ALIAS_EXISTS: + return (PUCHAR)"STATUS_ALIAS_EXISTS"; + case STATUS_LOGON_NOT_GRANTED: + return (PUCHAR)"STATUS_LOGON_NOT_GRANTED"; + case STATUS_TOO_MANY_SECRETS: + return (PUCHAR)"STATUS_TOO_MANY_SECRETS"; + case STATUS_SECRET_TOO_LONG: + return (PUCHAR)"STATUS_SECRET_TOO_LONG"; + case STATUS_INTERNAL_DB_ERROR: + return (PUCHAR)"STATUS_INTERNAL_DB_ERROR"; + case STATUS_FULLSCREEN_MODE: + return (PUCHAR)"STATUS_FULLSCREEN_MODE"; + case STATUS_TOO_MANY_CONTEXT_IDS: + return (PUCHAR)"STATUS_TOO_MANY_CONTEXT_IDS"; + case STATUS_LOGON_TYPE_NOT_GRANTED: + return (PUCHAR)"STATUS_LOGON_TYPE_NOT_GRANTED"; + case STATUS_NOT_REGISTRY_FILE: + return (PUCHAR)"STATUS_NOT_REGISTRY_FILE"; + case STATUS_NT_CROSS_ENCRYPTION_REQUIRED: + return (PUCHAR)"STATUS_NT_CROSS_ENCRYPTION_REQUIRED"; + case STATUS_DOMAIN_CTRLR_CONFIG_ERROR: + return (PUCHAR)"STATUS_DOMAIN_CTRLR_CONFIG_ERROR"; + case STATUS_FT_MISSING_MEMBER: + return (PUCHAR)"STATUS_FT_MISSING_MEMBER"; + case STATUS_ILL_FORMED_SERVICE_ENTRY: + return (PUCHAR)"STATUS_ILL_FORMED_SERVICE_ENTRY"; + case STATUS_ILLEGAL_CHARACTER: + return (PUCHAR)"STATUS_ILLEGAL_CHARACTER"; + case STATUS_UNMAPPABLE_CHARACTER: + return (PUCHAR)"STATUS_UNMAPPABLE_CHARACTER"; + case STATUS_UNDEFINED_CHARACTER: + return (PUCHAR)"STATUS_UNDEFINED_CHARACTER"; + case STATUS_FLOPPY_VOLUME: + return (PUCHAR)"STATUS_FLOPPY_VOLUME"; + case STATUS_FLOPPY_ID_MARK_NOT_FOUND: + return (PUCHAR)"STATUS_FLOPPY_ID_MARK_NOT_FOUND"; + case STATUS_FLOPPY_WRONG_CYLINDER: + return (PUCHAR)"STATUS_FLOPPY_WRONG_CYLINDER"; + case STATUS_FLOPPY_UNKNOWN_ERROR: + return (PUCHAR)"STATUS_FLOPPY_UNKNOWN_ERROR"; + case STATUS_FLOPPY_BAD_REGISTERS: + return (PUCHAR)"STATUS_FLOPPY_BAD_REGISTERS"; + case STATUS_DISK_RECALIBRATE_FAILED: + return (PUCHAR)"STATUS_DISK_RECALIBRATE_FAILED"; + case STATUS_DISK_OPERATION_FAILED: + return (PUCHAR)"STATUS_DISK_OPERATION_FAILED"; + case STATUS_DISK_RESET_FAILED: + return (PUCHAR)"STATUS_DISK_RESET_FAILED"; + case STATUS_SHARED_IRQ_BUSY: + return (PUCHAR)"STATUS_SHARED_IRQ_BUSY"; + case STATUS_FT_ORPHANING: + return (PUCHAR)"STATUS_FT_ORPHANING"; + case STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT: + return (PUCHAR)"STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT"; + case STATUS_PARTITION_FAILURE: + return (PUCHAR)"STATUS_PARTITION_FAILURE"; + case STATUS_INVALID_BLOCK_LENGTH: + return (PUCHAR)"STATUS_INVALID_BLOCK_LENGTH"; + case STATUS_DEVICE_NOT_PARTITIONED: + return (PUCHAR)"STATUS_DEVICE_NOT_PARTITIONED"; + case STATUS_UNABLE_TO_LOCK_MEDIA: + return (PUCHAR)"STATUS_UNABLE_TO_LOCK_MEDIA"; + case STATUS_UNABLE_TO_UNLOAD_MEDIA: + return (PUCHAR)"STATUS_UNABLE_TO_UNLOAD_MEDIA"; + case STATUS_EOM_OVERFLOW: + return (PUCHAR)"STATUS_EOM_OVERFLOW"; + case STATUS_NO_MEDIA: + return (PUCHAR)"STATUS_NO_MEDIA"; + case STATUS_NO_SUCH_MEMBER: + return (PUCHAR)"STATUS_NO_SUCH_MEMBER"; + case STATUS_INVALID_MEMBER: + return (PUCHAR)"STATUS_INVALID_MEMBER"; + case STATUS_KEY_DELETED: + return (PUCHAR)"STATUS_KEY_DELETED"; + case STATUS_NO_LOG_SPACE: + return (PUCHAR)"STATUS_NO_LOG_SPACE"; + case STATUS_TOO_MANY_SIDS: + return (PUCHAR)"STATUS_TOO_MANY_SIDS"; + case STATUS_LM_CROSS_ENCRYPTION_REQUIRED: + return (PUCHAR)"STATUS_LM_CROSS_ENCRYPTION_REQUIRED"; + case STATUS_KEY_HAS_CHILDREN: + return (PUCHAR)"STATUS_KEY_HAS_CHILDREN"; + case STATUS_CHILD_MUST_BE_VOLATILE: + return (PUCHAR)"STATUS_CHILD_MUST_BE_VOLATILE"; + case STATUS_DEVICE_CONFIGURATION_ERROR: + return (PUCHAR)"STATUS_DEVICE_CONFIGURATION_ERROR"; + case STATUS_DRIVER_INTERNAL_ERROR: + return (PUCHAR)"STATUS_DRIVER_INTERNAL_ERROR"; + case STATUS_INVALID_DEVICE_STATE: + return (PUCHAR)"STATUS_INVALID_DEVICE_STATE"; + case STATUS_IO_DEVICE_ERROR: + return (PUCHAR)"STATUS_IO_DEVICE_ERROR"; + case STATUS_DEVICE_PROTOCOL_ERROR: + return (PUCHAR)"STATUS_DEVICE_PROTOCOL_ERROR"; + case STATUS_BACKUP_CONTROLLER: + return (PUCHAR)"STATUS_BACKUP_CONTROLLER"; + case STATUS_LOG_FILE_FULL: + return (PUCHAR)"STATUS_LOG_FILE_FULL"; + case STATUS_TOO_LATE: + return (PUCHAR)"STATUS_TOO_LATE"; + case STATUS_NO_TRUST_LSA_SECRET: + return (PUCHAR)"STATUS_NO_TRUST_LSA_SECRET"; + case STATUS_NO_TRUST_SAM_ACCOUNT: + return (PUCHAR)"STATUS_NO_TRUST_SAM_ACCOUNT"; + case STATUS_TRUSTED_DOMAIN_FAILURE: + return (PUCHAR)"STATUS_TRUSTED_DOMAIN_FAILURE"; + case STATUS_TRUSTED_RELATIONSHIP_FAILURE: + return (PUCHAR)"STATUS_TRUSTED_RELATIONSHIP_FAILURE"; + case STATUS_EVENTLOG_FILE_CORRUPT: + return (PUCHAR)"STATUS_EVENTLOG_FILE_CORRUPT"; + case STATUS_EVENTLOG_CANT_START: + return (PUCHAR)"STATUS_EVENTLOG_CANT_START"; + case STATUS_TRUST_FAILURE: + return (PUCHAR)"STATUS_TRUST_FAILURE"; + case STATUS_MUTANT_LIMIT_EXCEEDED: + return (PUCHAR)"STATUS_MUTANT_LIMIT_EXCEEDED"; + case STATUS_NETLOGON_NOT_STARTED: + return (PUCHAR)"STATUS_NETLOGON_NOT_STARTED"; + case STATUS_ACCOUNT_EXPIRED: + return (PUCHAR)"STATUS_ACCOUNT_EXPIRED"; + case STATUS_POSSIBLE_DEADLOCK: + return (PUCHAR)"STATUS_POSSIBLE_DEADLOCK"; + case STATUS_NETWORK_CREDENTIAL_CONFLICT: + return (PUCHAR)"STATUS_NETWORK_CREDENTIAL_CONFLICT"; + case STATUS_REMOTE_SESSION_LIMIT: + return (PUCHAR)"STATUS_REMOTE_SESSION_LIMIT"; + case STATUS_EVENTLOG_FILE_CHANGED: + return (PUCHAR)"STATUS_EVENTLOG_FILE_CHANGED"; + case STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT: + return (PUCHAR)"STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT"; + case STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT: + return (PUCHAR)"STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT"; + case STATUS_NOLOGON_SERVER_TRUST_ACCOUNT: + return (PUCHAR)"STATUS_NOLOGON_SERVER_TRUST_ACCOUNT"; + case STATUS_DOMAIN_TRUST_INCONSISTENT: + return (PUCHAR)"STATUS_DOMAIN_TRUST_INCONSISTENT"; + case STATUS_FS_DRIVER_REQUIRED: + return (PUCHAR)"STATUS_FS_DRIVER_REQUIRED"; + case STATUS_NO_USER_SESSION_KEY: + return (PUCHAR)"STATUS_NO_USER_SESSION_KEY"; + case STATUS_USER_SESSION_DELETED: + return (PUCHAR)"STATUS_USER_SESSION_DELETED"; + case STATUS_RESOURCE_LANG_NOT_FOUND: + return (PUCHAR)"STATUS_RESOURCE_LANG_NOT_FOUND"; + case STATUS_INSUFF_SERVER_RESOURCES: + return (PUCHAR)"STATUS_INSUFF_SERVER_RESOURCES"; + case STATUS_INVALID_BUFFER_SIZE: + return (PUCHAR)"STATUS_INVALID_BUFFER_SIZE"; + case STATUS_INVALID_ADDRESS_COMPONENT: + return (PUCHAR)"STATUS_INVALID_ADDRESS_COMPONENT"; + case STATUS_INVALID_ADDRESS_WILDCARD: + return (PUCHAR)"STATUS_INVALID_ADDRESS_WILDCARD"; + case STATUS_TOO_MANY_ADDRESSES: + return (PUCHAR)"STATUS_TOO_MANY_ADDRESSES"; + case STATUS_ADDRESS_ALREADY_EXISTS: + return (PUCHAR)"STATUS_ADDRESS_ALREADY_EXISTS"; + case STATUS_ADDRESS_CLOSED: + return (PUCHAR)"STATUS_ADDRESS_CLOSED"; + case STATUS_CONNECTION_DISCONNECTED: + return (PUCHAR)"STATUS_CONNECTION_DISCONNECTED"; + case STATUS_CONNECTION_RESET: + return (PUCHAR)"STATUS_CONNECTION_RESET"; + case STATUS_TOO_MANY_NODES: + return (PUCHAR)"STATUS_TOO_MANY_NODES"; + case STATUS_TRANSACTION_ABORTED: + return (PUCHAR)"STATUS_TRANSACTION_ABORTED"; + case STATUS_TRANSACTION_TIMED_OUT: + return (PUCHAR)"STATUS_TRANSACTION_TIMED_OUT"; + case STATUS_TRANSACTION_NO_RELEASE: + return (PUCHAR)"STATUS_TRANSACTION_NO_RELEASE"; + case STATUS_TRANSACTION_NO_MATCH: + return (PUCHAR)"STATUS_TRANSACTION_NO_MATCH"; + case STATUS_TRANSACTION_RESPONDED: + return (PUCHAR)"STATUS_TRANSACTION_RESPONDED"; + case STATUS_TRANSACTION_INVALID_ID: + return (PUCHAR)"STATUS_TRANSACTION_INVALID_ID"; + case STATUS_TRANSACTION_INVALID_TYPE: + return (PUCHAR)"STATUS_TRANSACTION_INVALID_TYPE"; + case STATUS_NOT_SERVER_SESSION: + return (PUCHAR)"STATUS_NOT_SERVER_SESSION"; + case STATUS_NOT_CLIENT_SESSION: + return (PUCHAR)"STATUS_NOT_CLIENT_SESSION"; + case STATUS_CANNOT_LOAD_REGISTRY_FILE: + return (PUCHAR)"STATUS_CANNOT_LOAD_REGISTRY_FILE"; + case STATUS_DEBUG_ATTACH_FAILED: + return (PUCHAR)"STATUS_DEBUG_ATTACH_FAILED"; + case STATUS_SYSTEM_PROCESS_TERMINATED: + return (PUCHAR)"STATUS_SYSTEM_PROCESS_TERMINATED"; + case STATUS_DATA_NOT_ACCEPTED: + return (PUCHAR)"STATUS_DATA_NOT_ACCEPTED"; + case STATUS_NO_BROWSER_SERVERS_FOUND: + return (PUCHAR)"STATUS_NO_BROWSER_SERVERS_FOUND"; + case STATUS_VDM_HARD_ERROR: + return (PUCHAR)"STATUS_VDM_HARD_ERROR"; + case STATUS_DRIVER_CANCEL_TIMEOUT: + return (PUCHAR)"STATUS_DRIVER_CANCEL_TIMEOUT"; + case STATUS_REPLY_MESSAGE_MISMATCH: + return (PUCHAR)"STATUS_REPLY_MESSAGE_MISMATCH"; + case STATUS_MAPPED_ALIGNMENT: + return (PUCHAR)"STATUS_MAPPED_ALIGNMENT"; + case STATUS_IMAGE_CHECKSUM_MISMATCH: + return (PUCHAR)"STATUS_IMAGE_CHECKSUM_MISMATCH"; + case STATUS_LOST_WRITEBEHIND_DATA: + return (PUCHAR)"STATUS_LOST_WRITEBEHIND_DATA"; + case STATUS_CLIENT_SERVER_PARAMETERS_INVALID: + return (PUCHAR)"STATUS_CLIENT_SERVER_PARAMETERS_INVALID"; + case STATUS_PASSWORD_MUST_CHANGE: + return (PUCHAR)"STATUS_PASSWORD_MUST_CHANGE"; + case STATUS_NOT_FOUND: + return (PUCHAR)"STATUS_NOT_FOUND"; + case STATUS_NOT_TINY_STREAM: + return (PUCHAR)"STATUS_NOT_TINY_STREAM"; + case STATUS_RECOVERY_FAILURE: + return (PUCHAR)"STATUS_RECOVERY_FAILURE"; + case STATUS_STACK_OVERFLOW_READ: + return (PUCHAR)"STATUS_STACK_OVERFLOW_READ"; + case STATUS_FAIL_CHECK: + return (PUCHAR)"STATUS_FAIL_CHECK"; + case STATUS_DUPLICATE_OBJECTID: + return (PUCHAR)"STATUS_DUPLICATE_OBJECTID"; + case STATUS_OBJECTID_EXISTS: + return (PUCHAR)"STATUS_OBJECTID_EXISTS"; + case STATUS_CONVERT_TO_LARGE: + return (PUCHAR)"STATUS_CONVERT_TO_LARGE"; + case STATUS_RETRY: + return (PUCHAR)"STATUS_RETRY"; + case STATUS_FOUND_OUT_OF_SCOPE: + return (PUCHAR)"STATUS_FOUND_OUT_OF_SCOPE"; + case STATUS_ALLOCATE_BUCKET: + return (PUCHAR)"STATUS_ALLOCATE_BUCKET"; + case STATUS_PROPSET_NOT_FOUND: + return (PUCHAR)"STATUS_PROPSET_NOT_FOUND"; + case STATUS_MARSHALL_OVERFLOW: + return (PUCHAR)"STATUS_MARSHALL_OVERFLOW"; + case STATUS_INVALID_VARIANT: + return (PUCHAR)"STATUS_INVALID_VARIANT"; + case STATUS_DOMAIN_CONTROLLER_NOT_FOUND: + return (PUCHAR)"STATUS_DOMAIN_CONTROLLER_NOT_FOUND"; + case STATUS_ACCOUNT_LOCKED_OUT: + return (PUCHAR)"STATUS_ACCOUNT_LOCKED_OUT"; + case STATUS_HANDLE_NOT_CLOSABLE: + return (PUCHAR)"STATUS_HANDLE_NOT_CLOSABLE"; + case STATUS_CONNECTION_REFUSED: + return (PUCHAR)"STATUS_CONNECTION_REFUSED"; + case STATUS_GRACEFUL_DISCONNECT: + return (PUCHAR)"STATUS_GRACEFUL_DISCONNECT"; + case STATUS_ADDRESS_ALREADY_ASSOCIATED: + return (PUCHAR)"STATUS_ADDRESS_ALREADY_ASSOCIATED"; + case STATUS_ADDRESS_NOT_ASSOCIATED: + return (PUCHAR)"STATUS_ADDRESS_NOT_ASSOCIATED"; + case STATUS_CONNECTION_INVALID: + return (PUCHAR)"STATUS_CONNECTION_INVALID"; + case STATUS_CONNECTION_ACTIVE: + return (PUCHAR)"STATUS_CONNECTION_ACTIVE"; + case STATUS_NETWORK_UNREACHABLE: + return (PUCHAR)"STATUS_NETWORK_UNREACHABLE"; + case STATUS_HOST_UNREACHABLE: + return (PUCHAR)"STATUS_HOST_UNREACHABLE"; + case STATUS_PROTOCOL_UNREACHABLE: + return (PUCHAR)"STATUS_PROTOCOL_UNREACHABLE"; + case STATUS_PORT_UNREACHABLE: + return (PUCHAR)"STATUS_PORT_UNREACHABLE"; + case STATUS_REQUEST_ABORTED: + return (PUCHAR)"STATUS_REQUEST_ABORTED"; + case STATUS_CONNECTION_ABORTED: + return (PUCHAR)"STATUS_CONNECTION_ABORTED"; + case STATUS_BAD_COMPRESSION_BUFFER: + return (PUCHAR)"STATUS_BAD_COMPRESSION_BUFFER"; + case STATUS_USER_MAPPED_FILE: + return (PUCHAR)"STATUS_USER_MAPPED_FILE"; + case STATUS_AUDIT_FAILED: + return (PUCHAR)"STATUS_AUDIT_FAILED"; + case STATUS_TIMER_RESOLUTION_NOT_SET: + return (PUCHAR)"STATUS_TIMER_RESOLUTION_NOT_SET"; + case STATUS_CONNECTION_COUNT_LIMIT: + return (PUCHAR)"STATUS_CONNECTION_COUNT_LIMIT"; + case STATUS_LOGIN_TIME_RESTRICTION: + return (PUCHAR)"STATUS_LOGIN_TIME_RESTRICTION"; + case STATUS_LOGIN_WKSTA_RESTRICTION: + return (PUCHAR)"STATUS_LOGIN_WKSTA_RESTRICTION"; + case STATUS_IMAGE_MP_UP_MISMATCH: + return (PUCHAR)"STATUS_IMAGE_MP_UP_MISMATCH"; + case STATUS_INSUFFICIENT_LOGON_INFO: + return (PUCHAR)"STATUS_INSUFFICIENT_LOGON_INFO"; + case STATUS_BAD_DLL_ENTRYPOINT: + return (PUCHAR)"STATUS_BAD_DLL_ENTRYPOINT"; + case STATUS_BAD_SERVICE_ENTRYPOINT: + return (PUCHAR)"STATUS_BAD_SERVICE_ENTRYPOINT"; + case STATUS_LPC_REPLY_LOST: + return (PUCHAR)"STATUS_LPC_REPLY_LOST"; + case STATUS_IP_ADDRESS_CONFLICT1: + return (PUCHAR)"STATUS_IP_ADDRESS_CONFLICT1"; + case STATUS_IP_ADDRESS_CONFLICT2: + return (PUCHAR)"STATUS_IP_ADDRESS_CONFLICT2"; + case STATUS_REGISTRY_QUOTA_LIMIT: + return (PUCHAR)"STATUS_REGISTRY_QUOTA_LIMIT"; + case STATUS_PATH_NOT_COVERED: + return (PUCHAR)"STATUS_PATH_NOT_COVERED"; + case STATUS_NO_CALLBACK_ACTIVE: + return (PUCHAR)"STATUS_NO_CALLBACK_ACTIVE"; + case STATUS_LICENSE_QUOTA_EXCEEDED: + return (PUCHAR)"STATUS_LICENSE_QUOTA_EXCEEDED"; + case STATUS_PWD_TOO_SHORT: + return (PUCHAR)"STATUS_PWD_TOO_SHORT"; + case STATUS_PWD_TOO_RECENT: + return (PUCHAR)"STATUS_PWD_TOO_RECENT"; + case STATUS_PWD_HISTORY_CONFLICT: + return (PUCHAR)"STATUS_PWD_HISTORY_CONFLICT"; + case STATUS_PLUGPLAY_NO_DEVICE: + return (PUCHAR)"STATUS_PLUGPLAY_NO_DEVICE"; + case STATUS_UNSUPPORTED_COMPRESSION: + return (PUCHAR)"STATUS_UNSUPPORTED_COMPRESSION"; + case STATUS_INVALID_HW_PROFILE: + return (PUCHAR)"STATUS_INVALID_HW_PROFILE"; + case STATUS_INVALID_PLUGPLAY_DEVICE_PATH: + return (PUCHAR)"STATUS_INVALID_PLUGPLAY_DEVICE_PATH"; + case STATUS_DRIVER_ORDINAL_NOT_FOUND: + return (PUCHAR)"STATUS_DRIVER_ORDINAL_NOT_FOUND"; + case STATUS_DRIVER_ENTRYPOINT_NOT_FOUND: + return (PUCHAR)"STATUS_DRIVER_ENTRYPOINT_NOT_FOUND"; + case STATUS_RESOURCE_NOT_OWNED: + return (PUCHAR)"STATUS_RESOURCE_NOT_OWNED"; + case STATUS_TOO_MANY_LINKS: + return (PUCHAR)"STATUS_TOO_MANY_LINKS"; + case STATUS_QUOTA_LIST_INCONSISTENT: + return (PUCHAR)"STATUS_QUOTA_LIST_INCONSISTENT"; + case STATUS_FILE_IS_OFFLINE: + return (PUCHAR)"STATUS_FILE_IS_OFFLINE"; + case STATUS_EVALUATION_EXPIRATION: + return (PUCHAR)"STATUS_EVALUATION_EXPIRATION"; + case STATUS_ILLEGAL_DLL_RELOCATION: + return (PUCHAR)"STATUS_ILLEGAL_DLL_RELOCATION"; + case STATUS_LICENSE_VIOLATION: + return (PUCHAR)"STATUS_LICENSE_VIOLATION"; + case STATUS_DLL_INIT_FAILED_LOGOFF: + return (PUCHAR)"STATUS_DLL_INIT_FAILED_LOGOFF"; + case STATUS_DRIVER_UNABLE_TO_LOAD: + return (PUCHAR)"STATUS_DRIVER_UNABLE_TO_LOAD"; + case STATUS_DFS_UNAVAILABLE: + return (PUCHAR)"STATUS_DFS_UNAVAILABLE"; + case STATUS_VOLUME_DISMOUNTED: + return (PUCHAR)"STATUS_VOLUME_DISMOUNTED"; + case STATUS_WX86_INTERNAL_ERROR: + return (PUCHAR)"STATUS_WX86_INTERNAL_ERROR"; + case STATUS_WX86_FLOAT_STACK_CHECK: + return (PUCHAR)"STATUS_WX86_FLOAT_STACK_CHECK"; + case STATUS_VALIDATE_CONTINUE: + return (PUCHAR)"STATUS_VALIDATE_CONTINUE"; + case STATUS_NO_MATCH: + return (PUCHAR)"STATUS_NO_MATCH"; + case STATUS_NO_MORE_MATCHES: + return (PUCHAR)"STATUS_NO_MORE_MATCHES"; + case STATUS_NOT_A_REPARSE_POINT: + return (PUCHAR)"STATUS_NOT_A_REPARSE_POINT"; + case STATUS_IO_REPARSE_TAG_INVALID: + return (PUCHAR)"STATUS_IO_REPARSE_TAG_INVALID"; + case STATUS_IO_REPARSE_TAG_MISMATCH: + return (PUCHAR)"STATUS_IO_REPARSE_TAG_MISMATCH"; + case STATUS_IO_REPARSE_DATA_INVALID: + return (PUCHAR)"STATUS_IO_REPARSE_DATA_INVALID"; + case STATUS_IO_REPARSE_TAG_NOT_HANDLED: + return (PUCHAR)"STATUS_IO_REPARSE_TAG_NOT_HANDLED"; + case STATUS_REPARSE_POINT_NOT_RESOLVED: + return (PUCHAR)"STATUS_REPARSE_POINT_NOT_RESOLVED"; + case STATUS_DIRECTORY_IS_A_REPARSE_POINT: + return (PUCHAR)"STATUS_DIRECTORY_IS_A_REPARSE_POINT"; + case STATUS_RANGE_LIST_CONFLICT: + return (PUCHAR)"STATUS_RANGE_LIST_CONFLICT"; + case STATUS_SOURCE_ELEMENT_EMPTY: + return (PUCHAR)"STATUS_SOURCE_ELEMENT_EMPTY"; + case STATUS_DESTINATION_ELEMENT_FULL: + return (PUCHAR)"STATUS_DESTINATION_ELEMENT_FULL"; + case STATUS_ILLEGAL_ELEMENT_ADDRESS: + return (PUCHAR)"STATUS_ILLEGAL_ELEMENT_ADDRESS"; + case STATUS_MAGAZINE_NOT_PRESENT: + return (PUCHAR)"STATUS_MAGAZINE_NOT_PRESENT"; + case STATUS_REINITIALIZATION_NEEDED: + return (PUCHAR)"STATUS_REINITIALIZATION_NEEDED"; + case STATUS_ENCRYPTION_FAILED: + return (PUCHAR)"STATUS_ENCRYPTION_FAILED"; + case STATUS_DECRYPTION_FAILED: + return (PUCHAR)"STATUS_DECRYPTION_FAILED"; + case STATUS_RANGE_NOT_FOUND: + return (PUCHAR)"STATUS_RANGE_NOT_FOUND"; + case STATUS_NO_RECOVERY_POLICY: + return (PUCHAR)"STATUS_NO_RECOVERY_POLICY"; + case STATUS_NO_EFS: + return (PUCHAR)"STATUS_NO_EFS"; + case STATUS_WRONG_EFS: + return (PUCHAR)"STATUS_WRONG_EFS"; + case STATUS_NO_USER_KEYS: + return (PUCHAR)"STATUS_NO_USER_KEYS"; + case STATUS_FILE_NOT_ENCRYPTED: + return (PUCHAR)"STATUS_FILE_NOT_ENCRYPTED"; + case STATUS_NOT_EXPORT_FORMAT: + return (PUCHAR)"STATUS_NOT_EXPORT_FORMAT"; + case STATUS_FILE_ENCRYPTED: + return (PUCHAR)"STATUS_FILE_ENCRYPTED"; + case STATUS_WMI_GUID_NOT_FOUND: + return (PUCHAR)"STATUS_WMI_GUID_NOT_FOUND"; + case STATUS_WMI_INSTANCE_NOT_FOUND: + return (PUCHAR)"STATUS_WMI_INSTANCE_NOT_FOUND"; + case STATUS_WMI_ITEMID_NOT_FOUND: + return (PUCHAR)"STATUS_WMI_ITEMID_NOT_FOUND"; + case STATUS_WMI_TRY_AGAIN: + return (PUCHAR)"STATUS_WMI_TRY_AGAIN"; + case STATUS_SHARED_POLICY: + return (PUCHAR)"STATUS_SHARED_POLICY"; + case STATUS_POLICY_OBJECT_NOT_FOUND: + return (PUCHAR)"STATUS_POLICY_OBJECT_NOT_FOUND"; + case STATUS_POLICY_ONLY_IN_DS: + return (PUCHAR)"STATUS_POLICY_ONLY_IN_DS"; + case STATUS_VOLUME_NOT_UPGRADED: + return (PUCHAR)"STATUS_VOLUME_NOT_UPGRADED"; + case STATUS_REMOTE_STORAGE_NOT_ACTIVE: + return (PUCHAR)"STATUS_REMOTE_STORAGE_NOT_ACTIVE"; + case STATUS_REMOTE_STORAGE_MEDIA_ERROR: + return (PUCHAR)"STATUS_REMOTE_STORAGE_MEDIA_ERROR"; + case STATUS_NO_TRACKING_SERVICE: + return (PUCHAR)"STATUS_NO_TRACKING_SERVICE"; + case STATUS_SERVER_SID_MISMATCH: + return (PUCHAR)"STATUS_SERVER_SID_MISMATCH"; + case STATUS_DS_NO_ATTRIBUTE_OR_VALUE: + return (PUCHAR)"STATUS_DS_NO_ATTRIBUTE_OR_VALUE"; + case STATUS_DS_INVALID_ATTRIBUTE_SYNTAX: + return (PUCHAR)"STATUS_DS_INVALID_ATTRIBUTE_SYNTAX"; + case STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED: + return (PUCHAR)"STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED"; + case STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS: + return (PUCHAR)"STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS"; + case STATUS_DS_BUSY: + return (PUCHAR)"STATUS_DS_BUSY"; + case STATUS_DS_UNAVAILABLE: + return (PUCHAR)"STATUS_DS_UNAVAILABLE"; + case STATUS_DS_NO_RIDS_ALLOCATED: + return (PUCHAR)"STATUS_DS_NO_RIDS_ALLOCATED"; + case STATUS_DS_NO_MORE_RIDS: + return (PUCHAR)"STATUS_DS_NO_MORE_RIDS"; + case STATUS_DS_INCORRECT_ROLE_OWNER: + return (PUCHAR)"STATUS_DS_INCORRECT_ROLE_OWNER"; + case STATUS_DS_RIDMGR_INIT_ERROR: + return (PUCHAR)"STATUS_DS_RIDMGR_INIT_ERROR"; + case STATUS_DS_OBJ_CLASS_VIOLATION: + return (PUCHAR)"STATUS_DS_OBJ_CLASS_VIOLATION"; + case STATUS_DS_CANT_ON_NON_LEAF: + return (PUCHAR)"STATUS_DS_CANT_ON_NON_LEAF"; + case STATUS_DS_CANT_ON_RDN: + return (PUCHAR)"STATUS_DS_CANT_ON_RDN"; + case STATUS_DS_CANT_MOD_OBJ_CLASS: + return (PUCHAR)"STATUS_DS_CANT_MOD_OBJ_CLASS"; + case STATUS_DS_CROSS_DOM_MOVE_FAILED: + return (PUCHAR)"STATUS_DS_CROSS_DOM_MOVE_FAILED"; + case STATUS_DS_GC_NOT_AVAILABLE: + return (PUCHAR)"STATUS_DS_GC_NOT_AVAILABLE"; + case STATUS_DIRECTORY_SERVICE_REQUIRED: + return (PUCHAR)"STATUS_DIRECTORY_SERVICE_REQUIRED"; + case STATUS_REPARSE_ATTRIBUTE_CONFLICT: + return (PUCHAR)"STATUS_REPARSE_ATTRIBUTE_CONFLICT"; + case STATUS_CANT_ENABLE_DENY_ONLY: + return (PUCHAR)"STATUS_CANT_ENABLE_DENY_ONLY"; + case STATUS_FLOAT_MULTIPLE_FAULTS: + return (PUCHAR)"STATUS_FLOAT_MULTIPLE_FAULTS"; + case STATUS_FLOAT_MULTIPLE_TRAPS: + return (PUCHAR)"STATUS_FLOAT_MULTIPLE_TRAPS"; + case STATUS_DEVICE_REMOVED: + return (PUCHAR)"STATUS_DEVICE_REMOVED"; + case STATUS_JOURNAL_DELETE_IN_PROGRESS: + return (PUCHAR)"STATUS_JOURNAL_DELETE_IN_PROGRESS"; + case STATUS_JOURNAL_NOT_ACTIVE: + return (PUCHAR)"STATUS_JOURNAL_NOT_ACTIVE"; + case STATUS_NOINTERFACE: + return (PUCHAR)"STATUS_NOINTERFACE"; + case STATUS_DS_ADMIN_LIMIT_EXCEEDED: + return (PUCHAR)"STATUS_DS_ADMIN_LIMIT_EXCEEDED"; + case STATUS_DRIVER_FAILED_SLEEP: + return (PUCHAR)"STATUS_DRIVER_FAILED_SLEEP"; + case STATUS_MUTUAL_AUTHENTICATION_FAILED: + return (PUCHAR)"STATUS_MUTUAL_AUTHENTICATION_FAILED"; + case STATUS_CORRUPT_SYSTEM_FILE: + return (PUCHAR)"STATUS_CORRUPT_SYSTEM_FILE"; + case STATUS_DATATYPE_MISALIGNMENT_ERROR: + return (PUCHAR)"STATUS_DATATYPE_MISALIGNMENT_ERROR"; + case STATUS_WMI_READ_ONLY: + return (PUCHAR)"STATUS_WMI_READ_ONLY"; + case STATUS_WMI_SET_FAILURE: + return (PUCHAR)"STATUS_WMI_SET_FAILURE"; + case STATUS_COMMITMENT_MINIMUM: + return (PUCHAR)"STATUS_COMMITMENT_MINIMUM"; + case STATUS_REG_NAT_CONSUMPTION: + return (PUCHAR)"STATUS_REG_NAT_CONSUMPTION"; + case STATUS_TRANSPORT_FULL: + return (PUCHAR)"STATUS_TRANSPORT_FULL"; + case STATUS_DS_SAM_INIT_FAILURE: + return (PUCHAR)"STATUS_DS_SAM_INIT_FAILURE"; + case STATUS_ONLY_IF_CONNECTED: + return (PUCHAR)"STATUS_ONLY_IF_CONNECTED"; + case STATUS_DS_SENSITIVE_GROUP_VIOLATION: + return (PUCHAR)"STATUS_DS_SENSITIVE_GROUP_VIOLATION"; + case STATUS_PNP_RESTART_ENUMERATION: + return (PUCHAR)"STATUS_PNP_RESTART_ENUMERATION"; + case STATUS_JOURNAL_ENTRY_DELETED: + return (PUCHAR)"STATUS_JOURNAL_ENTRY_DELETED"; + case STATUS_DS_CANT_MOD_PRIMARYGROUPID: + return (PUCHAR)"STATUS_DS_CANT_MOD_PRIMARYGROUPID"; + case STATUS_SYSTEM_IMAGE_BAD_SIGNATURE: + return (PUCHAR)"STATUS_SYSTEM_IMAGE_BAD_SIGNATURE"; + case STATUS_PNP_REBOOT_REQUIRED: + return (PUCHAR)"STATUS_PNP_REBOOT_REQUIRED"; + case STATUS_POWER_STATE_INVALID: + return (PUCHAR)"STATUS_POWER_STATE_INVALID"; + case STATUS_DS_INVALID_GROUP_TYPE: + return (PUCHAR)"STATUS_DS_INVALID_GROUP_TYPE"; + case STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN: + return (PUCHAR)"STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN"; + case STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN: + return (PUCHAR)"STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN"; + case STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER: + return (PUCHAR)"STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER"; + case STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER: + return (PUCHAR)"STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER"; + case STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER: + return (PUCHAR)"STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER"; + case STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER: + return (PUCHAR)"STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER"; + case STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER: + return (PUCHAR)"STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER"; + case STATUS_DS_HAVE_PRIMARY_MEMBERS: + return (PUCHAR)"STATUS_DS_HAVE_PRIMARY_MEMBERS"; + case STATUS_WMI_NOT_SUPPORTED: + return (PUCHAR)"STATUS_WMI_NOT_SUPPORTED"; + case STATUS_INSUFFICIENT_POWER: + return (PUCHAR)"STATUS_INSUFFICIENT_POWER"; + case STATUS_SAM_NEED_BOOTKEY_PASSWORD: + return (PUCHAR)"STATUS_SAM_NEED_BOOTKEY_PASSWORD"; + case STATUS_SAM_NEED_BOOTKEY_FLOPPY: + return (PUCHAR)"STATUS_SAM_NEED_BOOTKEY_FLOPPY"; + case STATUS_DS_CANT_START: + return (PUCHAR)"STATUS_DS_CANT_START"; + case STATUS_DS_INIT_FAILURE: + return (PUCHAR)"STATUS_DS_INIT_FAILURE"; + case STATUS_SAM_INIT_FAILURE: + return (PUCHAR)"STATUS_SAM_INIT_FAILURE"; + case STATUS_DS_GC_REQUIRED: + return (PUCHAR)"STATUS_DS_GC_REQUIRED"; + case STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY: + return (PUCHAR)"STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY"; + case STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS: + return (PUCHAR)"STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS"; + case STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED: + return (PUCHAR)"STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED"; + case STATUS_MULTIPLE_FAULT_VIOLATION: + return (PUCHAR)"STATUS_MULTIPLE_FAULT_VIOLATION"; +#if (VER_PRODUCT_BUILD >= 2600) + case STATUS_CURRENT_DOMAIN_NOT_ALLOWED: + return (PUCHAR)"STATUS_CURRENT_DOMAIN_NOT_ALLOWED"; + case STATUS_CANNOT_MAKE: + return (PUCHAR)"STATUS_CANNOT_MAKE"; + case STATUS_SYSTEM_SHUTDOWN: + return (PUCHAR)"STATUS_SYSTEM_SHUTDOWN"; + case STATUS_DS_INIT_FAILURE_CONSOLE: + return (PUCHAR)"STATUS_DS_INIT_FAILURE_CONSOLE"; + case STATUS_DS_SAM_INIT_FAILURE_CONSOLE: + return (PUCHAR)"STATUS_DS_SAM_INIT_FAILURE_CONSOLE"; + case STATUS_UNFINISHED_CONTEXT_DELETED: + return (PUCHAR)"STATUS_UNFINISHED_CONTEXT_DELETED"; + case STATUS_NO_TGT_REPLY: + return (PUCHAR)"STATUS_NO_TGT_REPLY"; + case STATUS_OBJECTID_NOT_FOUND: + return (PUCHAR)"STATUS_OBJECTID_NOT_FOUND"; + case STATUS_NO_IP_ADDRESSES: + return (PUCHAR)"STATUS_NO_IP_ADDRESSES"; + case STATUS_WRONG_CREDENTIAL_HANDLE: + return (PUCHAR)"STATUS_WRONG_CREDENTIAL_HANDLE"; + case STATUS_CRYPTO_SYSTEM_INVALID: + return (PUCHAR)"STATUS_CRYPTO_SYSTEM_INVALID"; + case STATUS_MAX_REFERRALS_EXCEEDED: + return (PUCHAR)"STATUS_MAX_REFERRALS_EXCEEDED"; + case STATUS_MUST_BE_KDC: + return (PUCHAR)"STATUS_MUST_BE_KDC"; + case STATUS_STRONG_CRYPTO_NOT_SUPPORTED: + return (PUCHAR)"STATUS_STRONG_CRYPTO_NOT_SUPPORTED"; + case STATUS_TOO_MANY_PRINCIPALS: + return (PUCHAR)"STATUS_TOO_MANY_PRINCIPALS"; + case STATUS_NO_PA_DATA: + return (PUCHAR)"STATUS_NO_PA_DATA"; + case STATUS_PKINIT_NAME_MISMATCH: + return (PUCHAR)"STATUS_PKINIT_NAME_MISMATCH"; + case STATUS_SMARTCARD_LOGON_REQUIRED: + return (PUCHAR)"STATUS_SMARTCARD_LOGON_REQUIRED"; + case STATUS_KDC_INVALID_REQUEST: + return (PUCHAR)"STATUS_KDC_INVALID_REQUEST"; + case STATUS_KDC_UNABLE_TO_REFER: + return (PUCHAR)"STATUS_KDC_UNABLE_TO_REFER"; + case STATUS_KDC_UNKNOWN_ETYPE: + return (PUCHAR)"STATUS_KDC_UNKNOWN_ETYPE"; + case STATUS_SHUTDOWN_IN_PROGRESS: + return (PUCHAR)"STATUS_SHUTDOWN_IN_PROGRESS"; + case STATUS_SERVER_SHUTDOWN_IN_PROGRESS: + return (PUCHAR)"STATUS_SERVER_SHUTDOWN_IN_PROGRESS"; +#endif + case STATUS_NOT_SUPPORTED_ON_SBS: + return (PUCHAR)"STATUS_NOT_SUPPORTED_ON_SBS"; +#if (VER_PRODUCT_BUILD >= 2600) + case STATUS_WMI_GUID_DISCONNECTED: + return (PUCHAR)"STATUS_WMI_GUID_DISCONNECTED"; + case STATUS_WMI_ALREADY_DISABLED: + return (PUCHAR)"STATUS_WMI_ALREADY_DISABLED"; + case STATUS_WMI_ALREADY_ENABLED: + return (PUCHAR)"STATUS_WMI_ALREADY_ENABLED"; + case STATUS_MFT_TOO_FRAGMENTED: + return (PUCHAR)"STATUS_MFT_TOO_FRAGMENTED"; + case STATUS_COPY_PROTECTION_FAILURE: + return (PUCHAR)"STATUS_COPY_PROTECTION_FAILURE"; + case STATUS_CSS_AUTHENTICATION_FAILURE: + return (PUCHAR)"STATUS_CSS_AUTHENTICATION_FAILURE"; + case STATUS_CSS_KEY_NOT_PRESENT: + return (PUCHAR)"STATUS_CSS_KEY_NOT_PRESENT"; + case STATUS_CSS_KEY_NOT_ESTABLISHED: + return (PUCHAR)"STATUS_CSS_KEY_NOT_ESTABLISHED"; + case STATUS_CSS_SCRAMBLED_SECTOR: + return (PUCHAR)"STATUS_CSS_SCRAMBLED_SECTOR"; + case STATUS_CSS_REGION_MISMATCH: + return (PUCHAR)"STATUS_CSS_REGION_MISMATCH"; + case STATUS_CSS_RESETS_EXHAUSTED: + return (PUCHAR)"STATUS_CSS_RESETS_EXHAUSTED"; + case STATUS_PKINIT_FAILURE: + return (PUCHAR)"STATUS_PKINIT_FAILURE"; + case STATUS_SMARTCARD_SUBSYSTEM_FAILURE: + return (PUCHAR)"STATUS_SMARTCARD_SUBSYSTEM_FAILURE"; + case STATUS_NO_KERB_KEY: + return (PUCHAR)"STATUS_NO_KERB_KEY"; + case STATUS_HOST_DOWN: + return (PUCHAR)"STATUS_HOST_DOWN"; + case STATUS_UNSUPPORTED_PREAUTH: + return (PUCHAR)"STATUS_UNSUPPORTED_PREAUTH"; + case STATUS_EFS_ALG_BLOB_TOO_BIG: + return (PUCHAR)"STATUS_EFS_ALG_BLOB_TOO_BIG"; + case STATUS_PORT_NOT_SET: + return (PUCHAR)"STATUS_PORT_NOT_SET"; + case STATUS_DEBUGGER_INACTIVE: + return (PUCHAR)"STATUS_DEBUGGER_INACTIVE"; + case STATUS_DS_VERSION_CHECK_FAILURE: + return (PUCHAR)"STATUS_DS_VERSION_CHECK_FAILURE"; + case STATUS_AUDITING_DISABLED: + return (PUCHAR)"STATUS_AUDITING_DISABLED"; + case STATUS_PRENT4_MACHINE_ACCOUNT: + return (PUCHAR)"STATUS_PRENT4_MACHINE_ACCOUNT"; + case STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER: + return (PUCHAR)"STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER"; + case STATUS_INVALID_IMAGE_WIN_32: + return (PUCHAR)"STATUS_INVALID_IMAGE_WIN_32"; + case STATUS_INVALID_IMAGE_WIN_64: + return (PUCHAR)"STATUS_INVALID_IMAGE_WIN_64"; + case STATUS_BAD_BINDINGS: + return (PUCHAR)"STATUS_BAD_BINDINGS"; + case STATUS_NETWORK_SESSION_EXPIRED: + return (PUCHAR)"STATUS_NETWORK_SESSION_EXPIRED"; + case STATUS_APPHELP_BLOCK: + return (PUCHAR)"STATUS_APPHELP_BLOCK"; + case STATUS_ALL_SIDS_FILTERED: + return (PUCHAR)"STATUS_ALL_SIDS_FILTERED"; + case STATUS_NOT_SAFE_MODE_DRIVER: + return (PUCHAR)"STATUS_NOT_SAFE_MODE_DRIVER"; + case STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT: + return (PUCHAR)"STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT"; + case STATUS_ACCESS_DISABLED_BY_POLICY_PATH: + return (PUCHAR)"STATUS_ACCESS_DISABLED_BY_POLICY_PATH"; + case STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER: + return (PUCHAR)"STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER"; + case STATUS_ACCESS_DISABLED_BY_POLICY_OTHER: + return (PUCHAR)"STATUS_ACCESS_DISABLED_BY_POLICY_OTHER"; + case STATUS_FAILED_DRIVER_ENTRY: + return (PUCHAR)"STATUS_FAILED_DRIVER_ENTRY"; + case STATUS_DEVICE_ENUMERATION_ERROR: + return (PUCHAR)"STATUS_DEVICE_ENUMERATION_ERROR"; + case STATUS_MOUNT_POINT_NOT_RESOLVED: + return (PUCHAR)"STATUS_MOUNT_POINT_NOT_RESOLVED"; + case STATUS_INVALID_DEVICE_OBJECT_PARAMETER: + return (PUCHAR)"STATUS_INVALID_DEVICE_OBJECT_PARAMETER"; + case STATUS_MCA_OCCURED: + return (PUCHAR)"STATUS_MCA_OCCURED"; + case STATUS_DRIVER_BLOCKED_CRITICAL: + return (PUCHAR)"STATUS_DRIVER_BLOCKED_CRITICAL"; + case STATUS_DRIVER_BLOCKED: + return (PUCHAR)"STATUS_DRIVER_BLOCKED"; + case STATUS_DRIVER_DATABASE_ERROR: + return (PUCHAR)"STATUS_DRIVER_DATABASE_ERROR"; + case STATUS_SYSTEM_HIVE_TOO_LARGE: + return (PUCHAR)"STATUS_SYSTEM_HIVE_TOO_LARGE"; + case STATUS_INVALID_IMPORT_OF_NON_DLL: + return (PUCHAR)"STATUS_INVALID_IMPORT_OF_NON_DLL"; + case STATUS_SMARTCARD_WRONG_PIN: + return (PUCHAR)"STATUS_SMARTCARD_WRONG_PIN"; + case STATUS_SMARTCARD_CARD_BLOCKED: + return (PUCHAR)"STATUS_SMARTCARD_CARD_BLOCKED"; + case STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED: + return (PUCHAR)"STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED"; + case STATUS_SMARTCARD_NO_CARD: + return (PUCHAR)"STATUS_SMARTCARD_NO_CARD"; + case STATUS_SMARTCARD_NO_KEY_CONTAINER: + return (PUCHAR)"STATUS_SMARTCARD_NO_KEY_CONTAINER"; + case STATUS_SMARTCARD_NO_CERTIFICATE: + return (PUCHAR)"STATUS_SMARTCARD_NO_CERTIFICATE"; + case STATUS_SMARTCARD_NO_KEYSET: + return (PUCHAR)"STATUS_SMARTCARD_NO_KEYSET"; + case STATUS_SMARTCARD_IO_ERROR: + return (PUCHAR)"STATUS_SMARTCARD_IO_ERROR"; + case STATUS_DOWNGRADE_DETECTED: + return (PUCHAR)"STATUS_DOWNGRADE_DETECTED"; + case STATUS_SMARTCARD_CERT_REVOKED: + return (PUCHAR)"STATUS_SMARTCARD_CERT_REVOKED"; + case STATUS_ISSUING_CA_UNTRUSTED: + return (PUCHAR)"STATUS_ISSUING_CA_UNTRUSTED"; + case STATUS_REVOCATION_OFFLINE_C: + return (PUCHAR)"STATUS_REVOCATION_OFFLINE_C"; + case STATUS_PKINIT_CLIENT_FAILURE: + return (PUCHAR)"STATUS_PKINIT_CLIENT_FAILURE"; + case STATUS_SMARTCARD_CERT_EXPIRED: + return (PUCHAR)"STATUS_SMARTCARD_CERT_EXPIRED"; + case STATUS_DRIVER_FAILED_PRIOR_UNLOAD: + return (PUCHAR)"STATUS_DRIVER_FAILED_PRIOR_UNLOAD"; +#endif +#if (VER_PRODUCT_BUILD > 2600) + case STATUS_SMARTCARD_SILENT_CONTEXT: + return (PUCHAR)"STATUS_SMARTCARD_SILENT_CONTEXT"; + case STATUS_PER_USER_TRUST_QUOTA_EXCEEDED: + return (PUCHAR)"STATUS_PER_USER_TRUST_QUOTA_EXCEEDED"; + case STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED: + return (PUCHAR)"STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED"; + case STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED: + return (PUCHAR)"STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED"; + case STATUS_DS_NAME_NOT_UNIQUE: + return (PUCHAR)"STATUS_DS_NAME_NOT_UNIQUE"; + case STATUS_DS_DUPLICATE_ID_FOUND: + return (PUCHAR)"STATUS_DS_DUPLICATE_ID_FOUND"; + case STATUS_DS_GROUP_CONVERSION_ERROR: + return (PUCHAR)"STATUS_DS_GROUP_CONVERSION_ERROR"; + case STATUS_VOLSNAP_PREPARE_HIBERNATE: + return (PUCHAR)"STATUS_VOLSNAP_PREPARE_HIBERNATE"; + case STATUS_USER2USER_REQUIRED: + return (PUCHAR)"STATUS_USER2USER_REQUIRED"; + case STATUS_STACK_BUFFER_OVERRUN: + return (PUCHAR)"STATUS_STACK_BUFFER_OVERRUN"; + case STATUS_NO_S4U_PROT_SUPPORT: + return (PUCHAR)"STATUS_NO_S4U_PROT_SUPPORT"; + case STATUS_CROSSREALM_DELEGATION_FAILURE: + return (PUCHAR)"STATUS_CROSSREALM_DELEGATION_FAILURE"; + case STATUS_REVOCATION_OFFLINE_KDC: + return (PUCHAR)"STATUS_REVOCATION_OFFLINE_KDC"; + case STATUS_ISSUING_CA_UNTRUSTED_KDC: + return (PUCHAR)"STATUS_ISSUING_CA_UNTRUSTED_KDC"; + case STATUS_KDC_CERT_EXPIRED: + return (PUCHAR)"STATUS_KDC_CERT_EXPIRED"; + case STATUS_KDC_CERT_REVOKED: + return (PUCHAR)"STATUS_KDC_CERT_REVOKED"; + case STATUS_PARAMETER_QUOTA_EXCEEDED: + return (PUCHAR)"STATUS_PARAMETER_QUOTA_EXCEEDED"; + case STATUS_HIBERNATION_FAILURE: + return (PUCHAR)"STATUS_HIBERNATION_FAILURE"; + case STATUS_DELAY_LOAD_FAILED: + return (PUCHAR)"STATUS_DELAY_LOAD_FAILED"; + case STATUS_AUTHENTICATION_FIREWALL_FAILED: + return (PUCHAR)"STATUS_AUTHENTICATION_FIREWALL_FAILED"; + case STATUS_VDM_DISALLOWED: + return (PUCHAR)"STATUS_VDM_DISALLOWED"; + case STATUS_HUNG_DISPLAY_DRIVER_THREAD: + return (PUCHAR)"STATUS_HUNG_DISPLAY_DRIVER_THREAD"; +#endif + case STATUS_WOW_ASSERTION: + return (PUCHAR)"STATUS_WOW_ASSERTION"; + case DBG_NO_STATE_CHANGE: + return (PUCHAR)"DBG_NO_STATE_CHANGE"; + case DBG_APP_NOT_IDLE: + return (PUCHAR)"DBG_APP_NOT_IDLE"; + case RPC_NT_INVALID_STRING_BINDING: + return (PUCHAR)"RPC_NT_INVALID_STRING_BINDING"; + case RPC_NT_WRONG_KIND_OF_BINDING: + return (PUCHAR)"RPC_NT_WRONG_KIND_OF_BINDING"; + case RPC_NT_INVALID_BINDING: + return (PUCHAR)"RPC_NT_INVALID_BINDING"; + case RPC_NT_PROTSEQ_NOT_SUPPORTED: + return (PUCHAR)"RPC_NT_PROTSEQ_NOT_SUPPORTED"; + case RPC_NT_INVALID_RPC_PROTSEQ: + return (PUCHAR)"RPC_NT_INVALID_RPC_PROTSEQ"; + case RPC_NT_INVALID_STRING_UUID: + return (PUCHAR)"RPC_NT_INVALID_STRING_UUID"; + case RPC_NT_INVALID_ENDPOINT_FORMAT: + return (PUCHAR)"RPC_NT_INVALID_ENDPOINT_FORMAT"; + case RPC_NT_INVALID_NET_ADDR: + return (PUCHAR)"RPC_NT_INVALID_NET_ADDR"; + case RPC_NT_NO_ENDPOINT_FOUND: + return (PUCHAR)"RPC_NT_NO_ENDPOINT_FOUND"; + case RPC_NT_INVALID_TIMEOUT: + return (PUCHAR)"RPC_NT_INVALID_TIMEOUT"; + case RPC_NT_OBJECT_NOT_FOUND: + return (PUCHAR)"RPC_NT_OBJECT_NOT_FOUND"; + case RPC_NT_ALREADY_REGISTERED: + return (PUCHAR)"RPC_NT_ALREADY_REGISTERED"; + case RPC_NT_TYPE_ALREADY_REGISTERED: + return (PUCHAR)"RPC_NT_TYPE_ALREADY_REGISTERED"; + case RPC_NT_ALREADY_LISTENING: + return (PUCHAR)"RPC_NT_ALREADY_LISTENING"; + case RPC_NT_NO_PROTSEQS_REGISTERED: + return (PUCHAR)"RPC_NT_NO_PROTSEQS_REGISTERED"; + case RPC_NT_NOT_LISTENING: + return (PUCHAR)"RPC_NT_NOT_LISTENING"; + case RPC_NT_UNKNOWN_MGR_TYPE: + return (PUCHAR)"RPC_NT_UNKNOWN_MGR_TYPE"; + case RPC_NT_UNKNOWN_IF: + return (PUCHAR)"RPC_NT_UNKNOWN_IF"; + case RPC_NT_NO_BINDINGS: + return (PUCHAR)"RPC_NT_NO_BINDINGS"; + case RPC_NT_NO_PROTSEQS: + return (PUCHAR)"RPC_NT_NO_PROTSEQS"; + case RPC_NT_CANT_CREATE_ENDPOINT: + return (PUCHAR)"RPC_NT_CANT_CREATE_ENDPOINT"; + case RPC_NT_OUT_OF_RESOURCES: + return (PUCHAR)"RPC_NT_OUT_OF_RESOURCES"; + case RPC_NT_SERVER_UNAVAILABLE: + return (PUCHAR)"RPC_NT_SERVER_UNAVAILABLE"; + case RPC_NT_SERVER_TOO_BUSY: + return (PUCHAR)"RPC_NT_SERVER_TOO_BUSY"; + case RPC_NT_INVALID_NETWORK_OPTIONS: + return (PUCHAR)"RPC_NT_INVALID_NETWORK_OPTIONS"; + case RPC_NT_NO_CALL_ACTIVE: + return (PUCHAR)"RPC_NT_NO_CALL_ACTIVE"; + case RPC_NT_CALL_FAILED: + return (PUCHAR)"RPC_NT_CALL_FAILED"; + case RPC_NT_CALL_FAILED_DNE: + return (PUCHAR)"RPC_NT_CALL_FAILED_DNE"; + case RPC_NT_PROTOCOL_ERROR: + return (PUCHAR)"RPC_NT_PROTOCOL_ERROR"; + case RPC_NT_UNSUPPORTED_TRANS_SYN: + return (PUCHAR)"RPC_NT_UNSUPPORTED_TRANS_SYN"; + case RPC_NT_UNSUPPORTED_TYPE: + return (PUCHAR)"RPC_NT_UNSUPPORTED_TYPE"; + case RPC_NT_INVALID_TAG: + return (PUCHAR)"RPC_NT_INVALID_TAG"; + case RPC_NT_INVALID_BOUND: + return (PUCHAR)"RPC_NT_INVALID_BOUND"; + case RPC_NT_NO_ENTRY_NAME: + return (PUCHAR)"RPC_NT_NO_ENTRY_NAME"; + case RPC_NT_INVALID_NAME_SYNTAX: + return (PUCHAR)"RPC_NT_INVALID_NAME_SYNTAX"; + case RPC_NT_UNSUPPORTED_NAME_SYNTAX: + return (PUCHAR)"RPC_NT_UNSUPPORTED_NAME_SYNTAX"; + case RPC_NT_UUID_NO_ADDRESS: + return (PUCHAR)"RPC_NT_UUID_NO_ADDRESS"; + case RPC_NT_DUPLICATE_ENDPOINT: + return (PUCHAR)"RPC_NT_DUPLICATE_ENDPOINT"; + case RPC_NT_UNKNOWN_AUTHN_TYPE: + return (PUCHAR)"RPC_NT_UNKNOWN_AUTHN_TYPE"; + case RPC_NT_MAX_CALLS_TOO_SMALL: + return (PUCHAR)"RPC_NT_MAX_CALLS_TOO_SMALL"; + case RPC_NT_STRING_TOO_LONG: + return (PUCHAR)"RPC_NT_STRING_TOO_LONG"; + case RPC_NT_PROTSEQ_NOT_FOUND: + return (PUCHAR)"RPC_NT_PROTSEQ_NOT_FOUND"; + case RPC_NT_PROCNUM_OUT_OF_RANGE: + return (PUCHAR)"RPC_NT_PROCNUM_OUT_OF_RANGE"; + case RPC_NT_BINDING_HAS_NO_AUTH: + return (PUCHAR)"RPC_NT_BINDING_HAS_NO_AUTH"; + case RPC_NT_UNKNOWN_AUTHN_SERVICE: + return (PUCHAR)"RPC_NT_UNKNOWN_AUTHN_SERVICE"; + case RPC_NT_UNKNOWN_AUTHN_LEVEL: + return (PUCHAR)"RPC_NT_UNKNOWN_AUTHN_LEVEL"; + case RPC_NT_INVALID_AUTH_IDENTITY: + return (PUCHAR)"RPC_NT_INVALID_AUTH_IDENTITY"; + case RPC_NT_UNKNOWN_AUTHZ_SERVICE: + return (PUCHAR)"RPC_NT_UNKNOWN_AUTHZ_SERVICE"; + case EPT_NT_INVALID_ENTRY: + return (PUCHAR)"EPT_NT_INVALID_ENTRY"; + case EPT_NT_CANT_PERFORM_OP: + return (PUCHAR)"EPT_NT_CANT_PERFORM_OP"; + case EPT_NT_NOT_REGISTERED: + return (PUCHAR)"EPT_NT_NOT_REGISTERED"; + case RPC_NT_NOTHING_TO_EXPORT: + return (PUCHAR)"RPC_NT_NOTHING_TO_EXPORT"; + case RPC_NT_INCOMPLETE_NAME: + return (PUCHAR)"RPC_NT_INCOMPLETE_NAME"; + case RPC_NT_INVALID_VERS_OPTION: + return (PUCHAR)"RPC_NT_INVALID_VERS_OPTION"; + case RPC_NT_NO_MORE_MEMBERS: + return (PUCHAR)"RPC_NT_NO_MORE_MEMBERS"; + case RPC_NT_NOT_ALL_OBJS_UNEXPORTED: + return (PUCHAR)"RPC_NT_NOT_ALL_OBJS_UNEXPORTED"; + case RPC_NT_INTERFACE_NOT_FOUND: + return (PUCHAR)"RPC_NT_INTERFACE_NOT_FOUND"; + case RPC_NT_ENTRY_ALREADY_EXISTS: + return (PUCHAR)"RPC_NT_ENTRY_ALREADY_EXISTS"; + case RPC_NT_ENTRY_NOT_FOUND: + return (PUCHAR)"RPC_NT_ENTRY_NOT_FOUND"; + case RPC_NT_NAME_SERVICE_UNAVAILABLE: + return (PUCHAR)"RPC_NT_NAME_SERVICE_UNAVAILABLE"; + case RPC_NT_INVALID_NAF_ID: + return (PUCHAR)"RPC_NT_INVALID_NAF_ID"; + case RPC_NT_CANNOT_SUPPORT: + return (PUCHAR)"RPC_NT_CANNOT_SUPPORT"; + case RPC_NT_NO_CONTEXT_AVAILABLE: + return (PUCHAR)"RPC_NT_NO_CONTEXT_AVAILABLE"; + case RPC_NT_INTERNAL_ERROR: + return (PUCHAR)"RPC_NT_INTERNAL_ERROR"; + case RPC_NT_ZERO_DIVIDE: + return (PUCHAR)"RPC_NT_ZERO_DIVIDE"; + case RPC_NT_ADDRESS_ERROR: + return (PUCHAR)"RPC_NT_ADDRESS_ERROR"; + case RPC_NT_FP_DIV_ZERO: + return (PUCHAR)"RPC_NT_FP_DIV_ZERO"; + case RPC_NT_FP_UNDERFLOW: + return (PUCHAR)"RPC_NT_FP_UNDERFLOW"; + case RPC_NT_FP_OVERFLOW: + return (PUCHAR)"RPC_NT_FP_OVERFLOW"; + case RPC_NT_CALL_IN_PROGRESS: + return (PUCHAR)"RPC_NT_CALL_IN_PROGRESS"; + case RPC_NT_NO_MORE_BINDINGS: + return (PUCHAR)"RPC_NT_NO_MORE_BINDINGS"; + case RPC_NT_GROUP_MEMBER_NOT_FOUND: + return (PUCHAR)"RPC_NT_GROUP_MEMBER_NOT_FOUND"; + case EPT_NT_CANT_CREATE: + return (PUCHAR)"EPT_NT_CANT_CREATE"; + case RPC_NT_INVALID_OBJECT: + return (PUCHAR)"RPC_NT_INVALID_OBJECT"; + case RPC_NT_NO_INTERFACES: + return (PUCHAR)"RPC_NT_NO_INTERFACES"; + case RPC_NT_CALL_CANCELLED: + return (PUCHAR)"RPC_NT_CALL_CANCELLED"; + case RPC_NT_BINDING_INCOMPLETE: + return (PUCHAR)"RPC_NT_BINDING_INCOMPLETE"; + case RPC_NT_COMM_FAILURE: + return (PUCHAR)"RPC_NT_COMM_FAILURE"; + case RPC_NT_UNSUPPORTED_AUTHN_LEVEL: + return (PUCHAR)"RPC_NT_UNSUPPORTED_AUTHN_LEVEL"; + case RPC_NT_NO_PRINC_NAME: + return (PUCHAR)"RPC_NT_NO_PRINC_NAME"; + case RPC_NT_NOT_RPC_ERROR: + return (PUCHAR)"RPC_NT_NOT_RPC_ERROR"; + case RPC_NT_SEC_PKG_ERROR: + return (PUCHAR)"RPC_NT_SEC_PKG_ERROR"; + case RPC_NT_NOT_CANCELLED: + return (PUCHAR)"RPC_NT_NOT_CANCELLED"; + case RPC_NT_INVALID_ASYNC_HANDLE: + return (PUCHAR)"RPC_NT_INVALID_ASYNC_HANDLE"; + case RPC_NT_INVALID_ASYNC_CALL: + return (PUCHAR)"RPC_NT_INVALID_ASYNC_CALL"; + case RPC_NT_NO_MORE_ENTRIES: + return (PUCHAR)"RPC_NT_NO_MORE_ENTRIES"; + case RPC_NT_SS_CHAR_TRANS_OPEN_FAIL: + return (PUCHAR)"RPC_NT_SS_CHAR_TRANS_OPEN_FAIL"; + case RPC_NT_SS_CHAR_TRANS_SHORT_FILE: + return (PUCHAR)"RPC_NT_SS_CHAR_TRANS_SHORT_FILE"; + case RPC_NT_SS_IN_NULL_CONTEXT: + return (PUCHAR)"RPC_NT_SS_IN_NULL_CONTEXT"; + case RPC_NT_SS_CONTEXT_MISMATCH: + return (PUCHAR)"RPC_NT_SS_CONTEXT_MISMATCH"; + case RPC_NT_SS_CONTEXT_DAMAGED: + return (PUCHAR)"RPC_NT_SS_CONTEXT_DAMAGED"; + case RPC_NT_SS_HANDLES_MISMATCH: + return (PUCHAR)"RPC_NT_SS_HANDLES_MISMATCH"; + case RPC_NT_SS_CANNOT_GET_CALL_HANDLE: + return (PUCHAR)"RPC_NT_SS_CANNOT_GET_CALL_HANDLE"; + case RPC_NT_NULL_REF_POINTER: + return (PUCHAR)"RPC_NT_NULL_REF_POINTER"; + case RPC_NT_ENUM_VALUE_OUT_OF_RANGE: + return (PUCHAR)"RPC_NT_ENUM_VALUE_OUT_OF_RANGE"; + case RPC_NT_BYTE_COUNT_TOO_SMALL: + return (PUCHAR)"RPC_NT_BYTE_COUNT_TOO_SMALL"; + case RPC_NT_BAD_STUB_DATA: + return (PUCHAR)"RPC_NT_BAD_STUB_DATA"; + case RPC_NT_INVALID_ES_ACTION: + return (PUCHAR)"RPC_NT_INVALID_ES_ACTION"; + case RPC_NT_WRONG_ES_VERSION: + return (PUCHAR)"RPC_NT_WRONG_ES_VERSION"; + case RPC_NT_WRONG_STUB_VERSION: + return (PUCHAR)"RPC_NT_WRONG_STUB_VERSION"; + case RPC_NT_INVALID_PIPE_OBJECT: + return (PUCHAR)"RPC_NT_INVALID_PIPE_OBJECT"; + case RPC_NT_INVALID_PIPE_OPERATION: + return (PUCHAR)"RPC_NT_INVALID_PIPE_OPERATION"; + case RPC_NT_WRONG_PIPE_VERSION: + return (PUCHAR)"RPC_NT_WRONG_PIPE_VERSION"; + case RPC_NT_PIPE_CLOSED: + return (PUCHAR)"RPC_NT_PIPE_CLOSED"; + case RPC_NT_PIPE_DISCIPLINE_ERROR: + return (PUCHAR)"RPC_NT_PIPE_DISCIPLINE_ERROR"; + case RPC_NT_PIPE_EMPTY: + return (PUCHAR)"RPC_NT_PIPE_EMPTY"; + case STATUS_PNP_BAD_MPS_TABLE: + return (PUCHAR)"STATUS_PNP_BAD_MPS_TABLE"; + case STATUS_PNP_TRANSLATION_FAILED: + return (PUCHAR)"STATUS_PNP_TRANSLATION_FAILED"; + case STATUS_PNP_IRQ_TRANSLATION_FAILED: + return (PUCHAR)"STATUS_PNP_IRQ_TRANSLATION_FAILED"; +#if (VER_PRODUCT_BUILD > 2600) + case STATUS_PNP_INVALID_ID: + return (PUCHAR)"STATUS_PNP_INVALID_ID"; +#endif + case STATUS_CTX_WINSTATION_NAME_INVALID: + return (PUCHAR)"STATUS_CTX_WINSTATION_NAME_INVALID"; + case STATUS_CTX_INVALID_PD: + return (PUCHAR)"STATUS_CTX_INVALID_PD"; + case STATUS_CTX_PD_NOT_FOUND: + return (PUCHAR)"STATUS_CTX_PD_NOT_FOUND"; + case STATUS_CTX_CLOSE_PENDING: + return (PUCHAR)"STATUS_CTX_CLOSE_PENDING"; + case STATUS_CTX_NO_OUTBUF: + return (PUCHAR)"STATUS_CTX_NO_OUTBUF"; + case STATUS_CTX_MODEM_INF_NOT_FOUND: + return (PUCHAR)"STATUS_CTX_MODEM_INF_NOT_FOUND"; + case STATUS_CTX_INVALID_MODEMNAME: + return (PUCHAR)"STATUS_CTX_INVALID_MODEMNAME"; + case STATUS_CTX_RESPONSE_ERROR: + return (PUCHAR)"STATUS_CTX_RESPONSE_ERROR"; + case STATUS_CTX_MODEM_RESPONSE_TIMEOUT: + return (PUCHAR)"STATUS_CTX_MODEM_RESPONSE_TIMEOUT"; + case STATUS_CTX_MODEM_RESPONSE_NO_CARRIER: + return (PUCHAR)"STATUS_CTX_MODEM_RESPONSE_NO_CARRIER"; + case STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE: + return (PUCHAR)"STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE"; + case STATUS_CTX_MODEM_RESPONSE_BUSY: + return (PUCHAR)"STATUS_CTX_MODEM_RESPONSE_BUSY"; + case STATUS_CTX_MODEM_RESPONSE_VOICE: + return (PUCHAR)"STATUS_CTX_MODEM_RESPONSE_VOICE"; + case STATUS_CTX_TD_ERROR: + return (PUCHAR)"STATUS_CTX_TD_ERROR"; + case STATUS_CTX_LICENSE_CLIENT_INVALID: + return (PUCHAR)"STATUS_CTX_LICENSE_CLIENT_INVALID"; + case STATUS_CTX_LICENSE_NOT_AVAILABLE: + return (PUCHAR)"STATUS_CTX_LICENSE_NOT_AVAILABLE"; + case STATUS_CTX_LICENSE_EXPIRED: + return (PUCHAR)"STATUS_CTX_LICENSE_EXPIRED"; + case STATUS_CTX_WINSTATION_NOT_FOUND: + return (PUCHAR)"STATUS_CTX_WINSTATION_NOT_FOUND"; + case STATUS_CTX_WINSTATION_NAME_COLLISION: + return (PUCHAR)"STATUS_CTX_WINSTATION_NAME_COLLISION"; + case STATUS_CTX_WINSTATION_BUSY: + return (PUCHAR)"STATUS_CTX_WINSTATION_BUSY"; + case STATUS_CTX_BAD_VIDEO_MODE: + return (PUCHAR)"STATUS_CTX_BAD_VIDEO_MODE"; + case STATUS_CTX_GRAPHICS_INVALID: + return (PUCHAR)"STATUS_CTX_GRAPHICS_INVALID"; + case STATUS_CTX_NOT_CONSOLE: + return (PUCHAR)"STATUS_CTX_NOT_CONSOLE"; + case STATUS_CTX_CLIENT_QUERY_TIMEOUT: + return (PUCHAR)"STATUS_CTX_CLIENT_QUERY_TIMEOUT"; + case STATUS_CTX_CONSOLE_DISCONNECT: + return (PUCHAR)"STATUS_CTX_CONSOLE_DISCONNECT"; + case STATUS_CTX_CONSOLE_CONNECT: + return (PUCHAR)"STATUS_CTX_CONSOLE_CONNECT"; + case STATUS_CTX_SHADOW_DENIED: + return (PUCHAR)"STATUS_CTX_SHADOW_DENIED"; + case STATUS_CTX_WINSTATION_ACCESS_DENIED: + return (PUCHAR)"STATUS_CTX_WINSTATION_ACCESS_DENIED"; + case STATUS_CTX_INVALID_WD: + return (PUCHAR)"STATUS_CTX_INVALID_WD"; + case STATUS_CTX_WD_NOT_FOUND: + return (PUCHAR)"STATUS_CTX_WD_NOT_FOUND"; + case STATUS_CTX_SHADOW_INVALID: + return (PUCHAR)"STATUS_CTX_SHADOW_INVALID"; + case STATUS_CTX_SHADOW_DISABLED: + return (PUCHAR)"STATUS_CTX_SHADOW_DISABLED"; + case STATUS_RDP_PROTOCOL_ERROR: + return (PUCHAR)"STATUS_RDP_PROTOCOL_ERROR"; + case STATUS_CTX_CLIENT_LICENSE_NOT_SET: + return (PUCHAR)"STATUS_CTX_CLIENT_LICENSE_NOT_SET"; + case STATUS_CTX_CLIENT_LICENSE_IN_USE: + return (PUCHAR)"STATUS_CTX_CLIENT_LICENSE_IN_USE"; +#if (VER_PRODUCT_BUILD >= 2600) + case STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE: + return (PUCHAR)"STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE"; + case STATUS_CTX_SHADOW_NOT_RUNNING: + return (PUCHAR)"STATUS_CTX_SHADOW_NOT_RUNNING"; + case STATUS_CLUSTER_INVALID_NODE: + return (PUCHAR)"STATUS_CLUSTER_INVALID_NODE"; + case STATUS_CLUSTER_NODE_EXISTS: + return (PUCHAR)"STATUS_CLUSTER_NODE_EXISTS"; + case STATUS_CLUSTER_JOIN_IN_PROGRESS: + return (PUCHAR)"STATUS_CLUSTER_JOIN_IN_PROGRESS"; + case STATUS_CLUSTER_NODE_NOT_FOUND: + return (PUCHAR)"STATUS_CLUSTER_NODE_NOT_FOUND"; + case STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND: + return (PUCHAR)"STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND"; + case STATUS_CLUSTER_NETWORK_EXISTS: + return (PUCHAR)"STATUS_CLUSTER_NETWORK_EXISTS"; + case STATUS_CLUSTER_NETWORK_NOT_FOUND: + return (PUCHAR)"STATUS_CLUSTER_NETWORK_NOT_FOUND"; + case STATUS_CLUSTER_NETINTERFACE_EXISTS: + return (PUCHAR)"STATUS_CLUSTER_NETINTERFACE_EXISTS"; + case STATUS_CLUSTER_NETINTERFACE_NOT_FOUND: + return (PUCHAR)"STATUS_CLUSTER_NETINTERFACE_NOT_FOUND"; + case STATUS_CLUSTER_INVALID_REQUEST: + return (PUCHAR)"STATUS_CLUSTER_INVALID_REQUEST"; + case STATUS_CLUSTER_INVALID_NETWORK_PROVIDER: + return (PUCHAR)"STATUS_CLUSTER_INVALID_NETWORK_PROVIDER"; + case STATUS_CLUSTER_NODE_DOWN: + return (PUCHAR)"STATUS_CLUSTER_NODE_DOWN"; + case STATUS_CLUSTER_NODE_UNREACHABLE: + return (PUCHAR)"STATUS_CLUSTER_NODE_UNREACHABLE"; + case STATUS_CLUSTER_NODE_NOT_MEMBER: + return (PUCHAR)"STATUS_CLUSTER_NODE_NOT_MEMBER"; + case STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS: + return (PUCHAR)"STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS"; + case STATUS_CLUSTER_INVALID_NETWORK: + return (PUCHAR)"STATUS_CLUSTER_INVALID_NETWORK"; + case STATUS_CLUSTER_NO_NET_ADAPTERS: + return (PUCHAR)"STATUS_CLUSTER_NO_NET_ADAPTERS"; + case STATUS_CLUSTER_NODE_UP: + return (PUCHAR)"STATUS_CLUSTER_NODE_UP"; + case STATUS_CLUSTER_NODE_PAUSED: + return (PUCHAR)"STATUS_CLUSTER_NODE_PAUSED"; + case STATUS_CLUSTER_NODE_NOT_PAUSED: + return (PUCHAR)"STATUS_CLUSTER_NODE_NOT_PAUSED"; + case STATUS_CLUSTER_NO_SECURITY_CONTEXT: + return (PUCHAR)"STATUS_CLUSTER_NO_SECURITY_CONTEXT"; + case STATUS_CLUSTER_NETWORK_NOT_INTERNAL: + return (PUCHAR)"STATUS_CLUSTER_NETWORK_NOT_INTERNAL"; + case STATUS_CLUSTER_POISONED: + return (PUCHAR)"STATUS_CLUSTER_POISONED"; +#endif + case STATUS_ACPI_INVALID_OPCODE: + return (PUCHAR)"STATUS_ACPI_INVALID_OPCODE"; + case STATUS_ACPI_STACK_OVERFLOW: + return (PUCHAR)"STATUS_ACPI_STACK_OVERFLOW"; + case STATUS_ACPI_ASSERT_FAILED: + return (PUCHAR)"STATUS_ACPI_ASSERT_FAILED"; + case STATUS_ACPI_INVALID_INDEX: + return (PUCHAR)"STATUS_ACPI_INVALID_INDEX"; + case STATUS_ACPI_INVALID_ARGUMENT: + return (PUCHAR)"STATUS_ACPI_INVALID_ARGUMENT"; + case STATUS_ACPI_FATAL: + return (PUCHAR)"STATUS_ACPI_FATAL"; + case STATUS_ACPI_INVALID_SUPERNAME: + return (PUCHAR)"STATUS_ACPI_INVALID_SUPERNAME"; + case STATUS_ACPI_INVALID_ARGTYPE: + return (PUCHAR)"STATUS_ACPI_INVALID_ARGTYPE"; + case STATUS_ACPI_INVALID_OBJTYPE: + return (PUCHAR)"STATUS_ACPI_INVALID_OBJTYPE"; + case STATUS_ACPI_INVALID_TARGETTYPE: + return (PUCHAR)"STATUS_ACPI_INVALID_TARGETTYPE"; + case STATUS_ACPI_INCORRECT_ARGUMENT_COUNT: + return (PUCHAR)"STATUS_ACPI_INCORRECT_ARGUMENT_COUNT"; + case STATUS_ACPI_ADDRESS_NOT_MAPPED: + return (PUCHAR)"STATUS_ACPI_ADDRESS_NOT_MAPPED"; + case STATUS_ACPI_INVALID_EVENTTYPE: + return (PUCHAR)"STATUS_ACPI_INVALID_EVENTTYPE"; + case STATUS_ACPI_HANDLER_COLLISION: + return (PUCHAR)"STATUS_ACPI_HANDLER_COLLISION"; + case STATUS_ACPI_INVALID_DATA: + return (PUCHAR)"STATUS_ACPI_INVALID_DATA"; + case STATUS_ACPI_INVALID_REGION: + return (PUCHAR)"STATUS_ACPI_INVALID_REGION"; + case STATUS_ACPI_INVALID_ACCESS_SIZE: + return (PUCHAR)"STATUS_ACPI_INVALID_ACCESS_SIZE"; + case STATUS_ACPI_ACQUIRE_GLOBAL_LOCK: + return (PUCHAR)"STATUS_ACPI_ACQUIRE_GLOBAL_LOCK"; + case STATUS_ACPI_ALREADY_INITIALIZED: + return (PUCHAR)"STATUS_ACPI_ALREADY_INITIALIZED"; + case STATUS_ACPI_NOT_INITIALIZED: + return (PUCHAR)"STATUS_ACPI_NOT_INITIALIZED"; + case STATUS_ACPI_INVALID_MUTEX_LEVEL: + return (PUCHAR)"STATUS_ACPI_INVALID_MUTEX_LEVEL"; + case STATUS_ACPI_MUTEX_NOT_OWNED: + return (PUCHAR)"STATUS_ACPI_MUTEX_NOT_OWNED"; + case STATUS_ACPI_MUTEX_NOT_OWNER: + return (PUCHAR)"STATUS_ACPI_MUTEX_NOT_OWNER"; + case STATUS_ACPI_RS_ACCESS: + return (PUCHAR)"STATUS_ACPI_RS_ACCESS"; + case STATUS_ACPI_INVALID_TABLE: + return (PUCHAR)"STATUS_ACPI_INVALID_TABLE"; + case STATUS_ACPI_REG_HANDLER_FAILED: + return (PUCHAR)"STATUS_ACPI_REG_HANDLER_FAILED"; + case STATUS_ACPI_POWER_REQUEST_FAILED: + return (PUCHAR)"STATUS_ACPI_POWER_REQUEST_FAILED"; +#if (VER_PRODUCT_BUILD >= 2600) + case STATUS_SXS_SECTION_NOT_FOUND: + return (PUCHAR)"STATUS_SXS_SECTION_NOT_FOUND"; + case STATUS_SXS_CANT_GEN_ACTCTX: + return (PUCHAR)"STATUS_SXS_CANT_GEN_ACTCTX"; + case STATUS_SXS_INVALID_ACTCTXDATA_FORMAT: + return (PUCHAR)"STATUS_SXS_INVALID_ACTCTXDATA_FORMAT"; + case STATUS_SXS_ASSEMBLY_NOT_FOUND: + return (PUCHAR)"STATUS_SXS_ASSEMBLY_NOT_FOUND"; + case STATUS_SXS_MANIFEST_FORMAT_ERROR: + return (PUCHAR)"STATUS_SXS_MANIFEST_FORMAT_ERROR"; + case STATUS_SXS_MANIFEST_PARSE_ERROR: + return (PUCHAR)"STATUS_SXS_MANIFEST_PARSE_ERROR"; + case STATUS_SXS_ACTIVATION_CONTEXT_DISABLED: + return (PUCHAR)"STATUS_SXS_ACTIVATION_CONTEXT_DISABLED"; + case STATUS_SXS_KEY_NOT_FOUND: + return (PUCHAR)"STATUS_SXS_KEY_NOT_FOUND"; + case STATUS_SXS_VERSION_CONFLICT: + return (PUCHAR)"STATUS_SXS_VERSION_CONFLICT"; + case STATUS_SXS_WRONG_SECTION_TYPE: + return (PUCHAR)"STATUS_SXS_WRONG_SECTION_TYPE"; + case STATUS_SXS_THREAD_QUERIES_DISABLED: + return (PUCHAR)"STATUS_SXS_THREAD_QUERIES_DISABLED"; + case STATUS_SXS_ASSEMBLY_MISSING: + return (PUCHAR)"STATUS_SXS_ASSEMBLY_MISSING"; + case STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET: + return (PUCHAR)"STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET"; + case STATUS_SXS_EARLY_DEACTIVATION: + return (PUCHAR)"STATUS_SXS_EARLY_DEACTIVATION"; + case STATUS_SXS_INVALID_DEACTIVATION: + return (PUCHAR)"STATUS_SXS_INVALID_DEACTIVATION"; + case STATUS_SXS_MULTIPLE_DEACTIVATION: + return (PUCHAR)"STATUS_SXS_MULTIPLE_DEACTIVATION"; + case STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY: + return (PUCHAR)"STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY"; + case STATUS_SXS_PROCESS_TERMINATION_REQUESTED: + return (PUCHAR)"STATUS_SXS_PROCESS_TERMINATION_REQUESTED"; +#endif +#if (VER_PRODUCT_BUILD > 2600) + case STATUS_SXS_CORRUPT_ACTIVATION_STACK: + return (PUCHAR)"STATUS_SXS_CORRUPT_ACTIVATION_STACK"; +#endif + default: + return (PUCHAR)"NTSTATSTR_UNKNOWN_STATUS"; + } +} diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker2Installer.inf b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker2Installer.inf index 959b14d..c82a3c6 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker2Installer.inf +++ b/firmware-driver/SkyWalker1_Final_Release/Source/SkyWalker2Installer.inf @@ -1,142 +1,142 @@ -; SkyWalker2Installer.INF -- This file installs SkyWalker2 Driver -; -[Version] -signature="$CHICAGO$" -Class=Media -ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} -Provider=%SGI% -CatalogFile=SkyWalker2Installer.cat -DriverVer= 8/17/2009 - -; F i l e c o p y i n g s e c t i o n s (where the files go to). -; -[DestinationDirs] -DefaultDestDir=10,system32\drivers - -[Manufacturer] -%SGI%=SGI - -[ControlFlags] -;ExcludeFromSelect=* -;ExcludeFromSelect.NT=* - -; =================== Generic ================================== - -[SGI] -%SkyWalker2.DeviceDesc%=SkyWalker2.Device,USB\VID_09C0&PID_0206 ;SkyWalker2 - -[SkyWalker2.Device] -Include = ks.inf, kscaptur.inf, bda.inf -needs = KS.Registration, KSCAPTUR.Registration, BDA.Installation -AddReg = SkyWalker2.AddReg -CopyFiles = SkyWalker2.CopyDrivers - -[SkyWalker2.Device.NT] -Include = ks.inf, kscaptur.inf, bda.inf -needs = KS.Registration.NT, KSCAPTUR.Registration.NT, BDA.Installation.NT -;AddReg = SkyWalker2.AddReg -CopyFiles = SkyWalker2.CopyDrivers -; KnownFiles = SkyWalker2.KnownFiles - -[SkyWalker2.Device.NT.Services] -Addservice=SkyWalker2TVTuner, 0x00000002, SkyWalker2.AddService - -[SkyWalker2.AddService] -DisplayName=%SkyWalker2.FriendlyName% -ServiceType=1 ; SERVICE_KERNEL_DRIVER -StartType=3 ; SERVICE_DEMAND_START -ErrorControl=1 ; SERVICE_ERROR_NORMAL -ServiceBinary=%10%\System32\Drivers\SkyWalker1TVTuner.sys -LoadOrderGroup=ExtendedBase - -[SkyWalker2.CopyDrivers] -SkyWalker1TVTuner.sys - -[SkyWalker2.AddReg] -HKR,,DevLoader,,*NTKERN -HKR,,NTMPDriver,,SkyWalker1TVTuner.sys -HKR,,PageOutWhenUnopened,3,01 - -[SkyWalker2.Device.Interfaces] -AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,SkyWalker2.Receiver.Interfaces -AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,SkyWalker2.Tuner.Interfaces - -[SkyWalker2.Device.NT.Interfaces] -AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,SkyWalker2.Receiver.Interfaces -AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,SkyWalker2.Tuner.Interfaces - -[SkyWalker2.Tuner.Interfaces] -AddReg=SkyWalker2.Tuner.Interfaces.AddReg - -[SkyWalker2.Tuner.Interfaces.AddReg] -HKR,,CLSID,,%KSProxy.CLSID% -HKR,,FriendlyName,,%SkyWalker2.Tuner.FriendlyName% - -[SkyWalker2.Receiver.Interfaces] -AddReg=SkyWalker2.Receiver.Interfaces.AddReg - -[SkyWalker2.Receiver.Interfaces.AddReg] -HKR,,CLSID,,%KSProxy.CLSID% -HKR,,FriendlyName,,%SkyWalker2.Receiver.FriendlyName% - - -[Strings] -;non-localizable -SGI="Plethorasoft" -MfgName="SGI" -SkyWalker2.DeviceDesc="SkyWalker2 BDA TVTuner" -SkyWalker2.Tuner.FriendlyName="SkyWalker2 TV Tuner" -SkyWalker2.Receiver.FriendlyName="SkyWalker2 TV Receiver" -SkyWalker2.Tuner="SkyWalker2.Tuner" -KSProxy.CLSID="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" -KSCATEGORY_BDA_NETWORK_TUNER="{71985F48-1CA1-11d3-9CC8-00C04F7971E0}" -KSCATEGORY_BDA_RECEIVER_COMPONENT="{FD0A5AF4-B41D-11d2-9C95-00C04F7971E0}" -SKYWALKER_TUNER="{5C4E764F-AB43-46A9-B21E-8529C70F0A23}" -SKYWALKER_CAPTURE="{0F8F74D9-E524-4D05-BB60-F0C69ACB1756}" - -; -; ServiceType values -SERVICE_KERNEL_DRIVER = 0x00000001 -SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 -SERVICE_ADAPTER = 0x00000004 -SERVICE_RECOGNIZER_DRIVER = 0x00000008 -SERVICE_WIN32_OWN_PROCESS = 0x00000010 -SERVICE_WIN32_SHARE_PROCESS = 0x00000020 -SERVICE_INTERACTIVE_PROCESS = 0x00000100 -SERVICE_INTERACTIVE_SHARE_PROCESS = 0x00000120 - -; StartType values -SERVICE_BOOT_START = 0x00000000 -SERVICE_SYSTEM_START = 0x00000001 -SERVICE_AUTO_START = 0x00000002 -SERVICE_DEMAND_START = 0x00000003 -SERVICE_DISABLED = 0x00000004 - -; ErrorControl values -SERVICE_ERROR_IGNORE = 0x00000000 -SERVICE_ERROR_NORMAL = 0x00000001 -SERVICE_ERROR_SEVERE = 0x00000002 -SERVICE_ERROR_CRITICAL = 0x00000003 - -; Characteristic flags -NCF_VIRTUAL = 0x0001 -NCF_WRAPPER = 0x0002 -NCF_PHYSICAL = 0x0004 -NCF_HIDDEN = 0x0008 -NCF_NO_SERVICE = 0x0010 -NCF_NOT_USER_REMOVABLE = 0x0020 -NCF_HAS_UI = 0x0080 -NCF_MODEM = 0x0100 - -; Registry types -REG_MULTI_SZ = 0x10000 -REG_EXPAND_SZ = 0x20000 -REG_DWORD = 0x10001 - -; Win9x Compatible Types -REG_BINARY = 17 -REG_SZ = 0 - -; Service install flags -SPSVCINST_TAGTOFRONT = 0x1 +; SkyWalker2Installer.INF -- This file installs SkyWalker2 Driver +; +[Version] +signature="$CHICAGO$" +Class=Media +ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} +Provider=%SGI% +CatalogFile=SkyWalker2Installer.cat +DriverVer= 8/17/2009 + +; F i l e c o p y i n g s e c t i o n s (where the files go to). +; +[DestinationDirs] +DefaultDestDir=10,system32\drivers + +[Manufacturer] +%SGI%=SGI + +[ControlFlags] +;ExcludeFromSelect=* +;ExcludeFromSelect.NT=* + +; =================== Generic ================================== + +[SGI] +%SkyWalker2.DeviceDesc%=SkyWalker2.Device,USB\VID_09C0&PID_0206 ;SkyWalker2 + +[SkyWalker2.Device] +Include = ks.inf, kscaptur.inf, bda.inf +needs = KS.Registration, KSCAPTUR.Registration, BDA.Installation +AddReg = SkyWalker2.AddReg +CopyFiles = SkyWalker2.CopyDrivers + +[SkyWalker2.Device.NT] +Include = ks.inf, kscaptur.inf, bda.inf +needs = KS.Registration.NT, KSCAPTUR.Registration.NT, BDA.Installation.NT +;AddReg = SkyWalker2.AddReg +CopyFiles = SkyWalker2.CopyDrivers +; KnownFiles = SkyWalker2.KnownFiles + +[SkyWalker2.Device.NT.Services] +Addservice=SkyWalker2TVTuner, 0x00000002, SkyWalker2.AddService + +[SkyWalker2.AddService] +DisplayName=%SkyWalker2.FriendlyName% +ServiceType=1 ; SERVICE_KERNEL_DRIVER +StartType=3 ; SERVICE_DEMAND_START +ErrorControl=1 ; SERVICE_ERROR_NORMAL +ServiceBinary=%10%\System32\Drivers\SkyWalker1TVTuner.sys +LoadOrderGroup=ExtendedBase + +[SkyWalker2.CopyDrivers] +SkyWalker1TVTuner.sys + +[SkyWalker2.AddReg] +HKR,,DevLoader,,*NTKERN +HKR,,NTMPDriver,,SkyWalker1TVTuner.sys +HKR,,PageOutWhenUnopened,3,01 + +[SkyWalker2.Device.Interfaces] +AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,SkyWalker2.Receiver.Interfaces +AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,SkyWalker2.Tuner.Interfaces + +[SkyWalker2.Device.NT.Interfaces] +AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,SkyWalker2.Receiver.Interfaces +AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,SkyWalker2.Tuner.Interfaces + +[SkyWalker2.Tuner.Interfaces] +AddReg=SkyWalker2.Tuner.Interfaces.AddReg + +[SkyWalker2.Tuner.Interfaces.AddReg] +HKR,,CLSID,,%KSProxy.CLSID% +HKR,,FriendlyName,,%SkyWalker2.Tuner.FriendlyName% + +[SkyWalker2.Receiver.Interfaces] +AddReg=SkyWalker2.Receiver.Interfaces.AddReg + +[SkyWalker2.Receiver.Interfaces.AddReg] +HKR,,CLSID,,%KSProxy.CLSID% +HKR,,FriendlyName,,%SkyWalker2.Receiver.FriendlyName% + + +[Strings] +;non-localizable +SGI="Plethorasoft" +MfgName="SGI" +SkyWalker2.DeviceDesc="SkyWalker2 BDA TVTuner" +SkyWalker2.Tuner.FriendlyName="SkyWalker2 TV Tuner" +SkyWalker2.Receiver.FriendlyName="SkyWalker2 TV Receiver" +SkyWalker2.Tuner="SkyWalker2.Tuner" +KSProxy.CLSID="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" +KSCATEGORY_BDA_NETWORK_TUNER="{71985F48-1CA1-11d3-9CC8-00C04F7971E0}" +KSCATEGORY_BDA_RECEIVER_COMPONENT="{FD0A5AF4-B41D-11d2-9C95-00C04F7971E0}" +SKYWALKER_TUNER="{5C4E764F-AB43-46A9-B21E-8529C70F0A23}" +SKYWALKER_CAPTURE="{0F8F74D9-E524-4D05-BB60-F0C69ACB1756}" + +; +; ServiceType values +SERVICE_KERNEL_DRIVER = 0x00000001 +SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 +SERVICE_ADAPTER = 0x00000004 +SERVICE_RECOGNIZER_DRIVER = 0x00000008 +SERVICE_WIN32_OWN_PROCESS = 0x00000010 +SERVICE_WIN32_SHARE_PROCESS = 0x00000020 +SERVICE_INTERACTIVE_PROCESS = 0x00000100 +SERVICE_INTERACTIVE_SHARE_PROCESS = 0x00000120 + +; StartType values +SERVICE_BOOT_START = 0x00000000 +SERVICE_SYSTEM_START = 0x00000001 +SERVICE_AUTO_START = 0x00000002 +SERVICE_DEMAND_START = 0x00000003 +SERVICE_DISABLED = 0x00000004 + +; ErrorControl values +SERVICE_ERROR_IGNORE = 0x00000000 +SERVICE_ERROR_NORMAL = 0x00000001 +SERVICE_ERROR_SEVERE = 0x00000002 +SERVICE_ERROR_CRITICAL = 0x00000003 + +; Characteristic flags +NCF_VIRTUAL = 0x0001 +NCF_WRAPPER = 0x0002 +NCF_PHYSICAL = 0x0004 +NCF_HIDDEN = 0x0008 +NCF_NO_SERVICE = 0x0010 +NCF_NOT_USER_REMOVABLE = 0x0020 +NCF_HAS_UI = 0x0080 +NCF_MODEM = 0x0100 + +; Registry types +REG_MULTI_SZ = 0x10000 +REG_EXPAND_SZ = 0x20000 +REG_DWORD = 0x10001 + +; Win9x Compatible Types +REG_BINARY = 17 +REG_SZ = 0 + +; Service install flags +SPSVCINST_TAGTOFRONT = 0x1 SPSVCINST_ASSOCSERVICE = 0x2 \ No newline at end of file diff --git a/firmware-driver/SkyWalker1_Final_Release/Source/Sources b/firmware-driver/SkyWalker1_Final_Release/Source/Sources index 64686ce..01d4df6 100644 --- a/firmware-driver/SkyWalker1_Final_Release/Source/Sources +++ b/firmware-driver/SkyWalker1_Final_Release/Source/Sources @@ -1,62 +1,62 @@ -############################################################################# -# Shree Ganesha Inc. -# Sources File for the Skywalker1 TV Tuner -# Date : 29th September, 2009 -# Description : This file is a must for the Compilation of the -# Skywalker Driver. -# -########################################################################## - -TARGETNAME=SkyWalker1TVTuner # Set driver's name -TARGETTYPE=DRIVER # Set type of file built, for example, program, DLL, or driver - # For BDA minidriver, set to DRIVER. -TARGETPATH=obj$(BUILD_ALT_DIR) # Set destination directory for the built file - # Depending on whether your build environment is "free" or "checked", - # the BUILD_ALT_DIR variable appends "fre" or "chk" to the \obj subdirectory. -DRIVERTYPE=WDM # Set type of driver, can be set to either WDM or VXD. - # For BDA, set to WDM. - -# Generate .SYM and .PDB (map) files. These files map names to addresses. -# Required to debug on Win9x. -USE_MAPSYM=1 - -# Point to the header files that the sample source requires. -INCLUDES= \ - $(DDK_INC_PATH); \ - $(DDK_INC_PATH)\wdm; \ - $(SDK_INC_PATH); \ - $(SDK_PATH)\AMovie\Inc; \ - $(INCLUDES) - -# Point to the library files that the sample source requires. -TARGETLIBS= \ - $(DDK_LIB_PATH)\ks.lib \ - $(DDK_LIB_PATH)\ksguid.lib \ - $(DDK_LIB_PATH)\BdaSup.lib \ - $(DDK_LIB_PATH)\usbd.lib - -# The following macros are used with the Soft-ICE debugging tool. -!ifdef BUILD_SOFTICE_SYMBOLS -TARGETPATHEX=$(TARGETPATH)\$(TARGET_DIRECTORY) - -NTTARGETFILES=$(TARGETPATH)\$(TARGETNAME).dbg - -NTTARGETFILES=$(TARGETPATHEX)\$(TARGETNAME).nms $(NTTARGETFILES) -!endif - -# Source files that must be compiled. -SOURCES = SkyWalker1TunerPin.cpp \ - SkyWalker1AntennaPin.cpp \ - SkyWalker1CaptureFilter.cpp \ - SkyWalker1CaptureFilterDefinitions.cpp \ - SkyWalker1CapturePin.cpp \ - SkyWalker1Control.cpp \ - SkyWalker1Device.cpp \ - SkyWalker1Main.cpp \ - SkyWalker1PnP.cpp \ - SkyWalker1TransportPin.cpp \ - SkyWalker1TunerFilter.cpp \ - SkyWalker1TunerFilterDefinitions.cpp \ - SkyWalker1USB.cpp \ - SkyWalker1Utility.cpp +############################################################################# +# Shree Ganesha Inc. +# Sources File for the Skywalker1 TV Tuner +# Date : 29th September, 2009 +# Description : This file is a must for the Compilation of the +# Skywalker Driver. +# +########################################################################## + +TARGETNAME=SkyWalker1TVTuner # Set driver's name +TARGETTYPE=DRIVER # Set type of file built, for example, program, DLL, or driver + # For BDA minidriver, set to DRIVER. +TARGETPATH=obj$(BUILD_ALT_DIR) # Set destination directory for the built file + # Depending on whether your build environment is "free" or "checked", + # the BUILD_ALT_DIR variable appends "fre" or "chk" to the \obj subdirectory. +DRIVERTYPE=WDM # Set type of driver, can be set to either WDM or VXD. + # For BDA, set to WDM. + +# Generate .SYM and .PDB (map) files. These files map names to addresses. +# Required to debug on Win9x. +USE_MAPSYM=1 + +# Point to the header files that the sample source requires. +INCLUDES= \ + $(DDK_INC_PATH); \ + $(DDK_INC_PATH)\wdm; \ + $(SDK_INC_PATH); \ + $(SDK_PATH)\AMovie\Inc; \ + $(INCLUDES) + +# Point to the library files that the sample source requires. +TARGETLIBS= \ + $(DDK_LIB_PATH)\ks.lib \ + $(DDK_LIB_PATH)\ksguid.lib \ + $(DDK_LIB_PATH)\BdaSup.lib \ + $(DDK_LIB_PATH)\usbd.lib + +# The following macros are used with the Soft-ICE debugging tool. +!ifdef BUILD_SOFTICE_SYMBOLS +TARGETPATHEX=$(TARGETPATH)\$(TARGET_DIRECTORY) + +NTTARGETFILES=$(TARGETPATH)\$(TARGETNAME).dbg + +NTTARGETFILES=$(TARGETPATHEX)\$(TARGETNAME).nms $(NTTARGETFILES) +!endif + +# Source files that must be compiled. +SOURCES = SkyWalker1TunerPin.cpp \ + SkyWalker1AntennaPin.cpp \ + SkyWalker1CaptureFilter.cpp \ + SkyWalker1CaptureFilterDefinitions.cpp \ + SkyWalker1CapturePin.cpp \ + SkyWalker1Control.cpp \ + SkyWalker1Device.cpp \ + SkyWalker1Main.cpp \ + SkyWalker1PnP.cpp \ + SkyWalker1TransportPin.cpp \ + SkyWalker1TunerFilter.cpp \ + SkyWalker1TunerFilterDefinitions.cpp \ + SkyWalker1USB.cpp \ + SkyWalker1Utility.cpp \ No newline at end of file diff --git a/firmware-driver/Skywalker-1 BDA Driver 08172009/SkyWalker1Installer.inf b/firmware-driver/Skywalker-1 BDA Driver 08172009/SkyWalker1Installer.inf index 1a42430..ea3744c 100644 --- a/firmware-driver/Skywalker-1 BDA Driver 08172009/SkyWalker1Installer.inf +++ b/firmware-driver/Skywalker-1 BDA Driver 08172009/SkyWalker1Installer.inf @@ -1,142 +1,142 @@ -; SkyWalker1Installer.INF -- This file installs SkyWalker1 Driver -; -[Version] -signature="$CHICAGO$" -Class=Media -ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} -Provider=%SGI% -CatalogFile=SkyWalker1Installer.cat -DriverVer= 8/17/2009 - -; F i l e c o p y i n g s e c t i o n s (where the files go to). -; -[DestinationDirs] -DefaultDestDir=10,system32\drivers - -[Manufacturer] -%SGI%=SGI - -[ControlFlags] -;ExcludeFromSelect=* -;ExcludeFromSelect.NT=* - -; =================== Generic ================================== - -[SGI] -%SkyWalker1.DeviceDesc%=Skywalker1.Device,USB\VID_09C0&PID_0203 ;SkyWalker1 - -[Skywalker1.Device] -Include = ks.inf, kscaptur.inf, bda.inf -needs = KS.Registration, KSCAPTUR.Registration, BDA.Installation -AddReg = Skywalker1.AddReg -CopyFiles = Skywalker1.CopyDrivers - -[Skywalker1.Device.NT] -Include = ks.inf, kscaptur.inf, bda.inf -needs = KS.Registration.NT, KSCAPTUR.Registration.NT, BDA.Installation.NT -;AddReg = Skywalker1.AddReg -CopyFiles = Skywalker1.CopyDrivers -; KnownFiles = Skywalker1.KnownFiles - -[Skywalker1.Device.NT.Services] -Addservice=SkyWalker1TVTuner, 0x00000002, Skywalker1.AddService - -[Skywalker1.AddService] -DisplayName=%SkyWalker1.FriendlyName% -ServiceType=1 ; SERVICE_KERNEL_DRIVER -StartType=3 ; SERVICE_DEMAND_START -ErrorControl=1 ; SERVICE_ERROR_NORMAL -ServiceBinary=%10%\System32\Drivers\SkyWalker1TVTuner.sys -LoadOrderGroup=ExtendedBase - -[Skywalker1.CopyDrivers] -SkyWalker1TVTuner.sys - -[Skywalker1.AddReg] -HKR,,DevLoader,,*NTKERN -HKR,,NTMPDriver,,SkyWalker1TVTuner.sys -HKR,,PageOutWhenUnopened,3,01 - -[Skywalker1.Device.Interfaces] -AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,Skywalker1.Receiver.Interfaces -AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,Skywalker1.Tuner.Interfaces - -[Skywalker1.Device.NT.Interfaces] -AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,Skywalker1.Receiver.Interfaces -AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,Skywalker1.Tuner.Interfaces - -[Skywalker1.Tuner.Interfaces] -AddReg=Skywalker1.Tuner.Interfaces.AddReg - -[Skywalker1.Tuner.Interfaces.AddReg] -HKR,,CLSID,,%KSProxy.CLSID% -HKR,,FriendlyName,,%SkyWalker1.Tuner.FriendlyName% - -[Skywalker1.Receiver.Interfaces] -AddReg=Skywalker1.Receiver.Interfaces.AddReg - -[Skywalker1.Receiver.Interfaces.AddReg] -HKR,,CLSID,,%KSProxy.CLSID% -HKR,,FriendlyName,,%SkyWalker1.Receiver.FriendlyName% - - -[Strings] -;non-localizable -SGI="Plethorasoft" -MfgName="SGI" -SkyWalker1.DeviceDesc="SkyWalker1 BDA TVTuner" -SkyWalker1.Tuner.FriendlyName="SkyWalker1 TV Tuner" -SkyWalker1.Receiver.FriendlyName="SkyWalker1 TV Receiver" -SkyWalker1.Tuner="SkyWalker1.Tuner" -KSProxy.CLSID="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" -KSCATEGORY_BDA_NETWORK_TUNER="{71985F48-1CA1-11d3-9CC8-00C04F7971E0}" -KSCATEGORY_BDA_RECEIVER_COMPONENT="{FD0A5AF4-B41D-11d2-9C95-00C04F7971E0}" -SKYWALKER_TUNER="{5C4E764F-AB43-46A9-B21E-8529C70F0A23}" -SKYWALKER_CAPTURE="{0F8F74D9-E524-4D05-BB60-F0C69ACB1756}" - -; -; ServiceType values -SERVICE_KERNEL_DRIVER = 0x00000001 -SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 -SERVICE_ADAPTER = 0x00000004 -SERVICE_RECOGNIZER_DRIVER = 0x00000008 -SERVICE_WIN32_OWN_PROCESS = 0x00000010 -SERVICE_WIN32_SHARE_PROCESS = 0x00000020 -SERVICE_INTERACTIVE_PROCESS = 0x00000100 -SERVICE_INTERACTIVE_SHARE_PROCESS = 0x00000120 - -; StartType values -SERVICE_BOOT_START = 0x00000000 -SERVICE_SYSTEM_START = 0x00000001 -SERVICE_AUTO_START = 0x00000002 -SERVICE_DEMAND_START = 0x00000003 -SERVICE_DISABLED = 0x00000004 - -; ErrorControl values -SERVICE_ERROR_IGNORE = 0x00000000 -SERVICE_ERROR_NORMAL = 0x00000001 -SERVICE_ERROR_SEVERE = 0x00000002 -SERVICE_ERROR_CRITICAL = 0x00000003 - -; Characteristic flags -NCF_VIRTUAL = 0x0001 -NCF_WRAPPER = 0x0002 -NCF_PHYSICAL = 0x0004 -NCF_HIDDEN = 0x0008 -NCF_NO_SERVICE = 0x0010 -NCF_NOT_USER_REMOVABLE = 0x0020 -NCF_HAS_UI = 0x0080 -NCF_MODEM = 0x0100 - -; Registry types -REG_MULTI_SZ = 0x10000 -REG_EXPAND_SZ = 0x20000 -REG_DWORD = 0x10001 - -; Win9x Compatible Types -REG_BINARY = 17 -REG_SZ = 0 - -; Service install flags -SPSVCINST_TAGTOFRONT = 0x1 +; SkyWalker1Installer.INF -- This file installs SkyWalker1 Driver +; +[Version] +signature="$CHICAGO$" +Class=Media +ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} +Provider=%SGI% +CatalogFile=SkyWalker1Installer.cat +DriverVer= 8/17/2009 + +; F i l e c o p y i n g s e c t i o n s (where the files go to). +; +[DestinationDirs] +DefaultDestDir=10,system32\drivers + +[Manufacturer] +%SGI%=SGI + +[ControlFlags] +;ExcludeFromSelect=* +;ExcludeFromSelect.NT=* + +; =================== Generic ================================== + +[SGI] +%SkyWalker1.DeviceDesc%=Skywalker1.Device,USB\VID_09C0&PID_0203 ;SkyWalker1 + +[Skywalker1.Device] +Include = ks.inf, kscaptur.inf, bda.inf +needs = KS.Registration, KSCAPTUR.Registration, BDA.Installation +AddReg = Skywalker1.AddReg +CopyFiles = Skywalker1.CopyDrivers + +[Skywalker1.Device.NT] +Include = ks.inf, kscaptur.inf, bda.inf +needs = KS.Registration.NT, KSCAPTUR.Registration.NT, BDA.Installation.NT +;AddReg = Skywalker1.AddReg +CopyFiles = Skywalker1.CopyDrivers +; KnownFiles = Skywalker1.KnownFiles + +[Skywalker1.Device.NT.Services] +Addservice=SkyWalker1TVTuner, 0x00000002, Skywalker1.AddService + +[Skywalker1.AddService] +DisplayName=%SkyWalker1.FriendlyName% +ServiceType=1 ; SERVICE_KERNEL_DRIVER +StartType=3 ; SERVICE_DEMAND_START +ErrorControl=1 ; SERVICE_ERROR_NORMAL +ServiceBinary=%10%\System32\Drivers\SkyWalker1TVTuner.sys +LoadOrderGroup=ExtendedBase + +[Skywalker1.CopyDrivers] +SkyWalker1TVTuner.sys + +[Skywalker1.AddReg] +HKR,,DevLoader,,*NTKERN +HKR,,NTMPDriver,,SkyWalker1TVTuner.sys +HKR,,PageOutWhenUnopened,3,01 + +[Skywalker1.Device.Interfaces] +AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,Skywalker1.Receiver.Interfaces +AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,Skywalker1.Tuner.Interfaces + +[Skywalker1.Device.NT.Interfaces] +AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,Skywalker1.Receiver.Interfaces +AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,Skywalker1.Tuner.Interfaces + +[Skywalker1.Tuner.Interfaces] +AddReg=Skywalker1.Tuner.Interfaces.AddReg + +[Skywalker1.Tuner.Interfaces.AddReg] +HKR,,CLSID,,%KSProxy.CLSID% +HKR,,FriendlyName,,%SkyWalker1.Tuner.FriendlyName% + +[Skywalker1.Receiver.Interfaces] +AddReg=Skywalker1.Receiver.Interfaces.AddReg + +[Skywalker1.Receiver.Interfaces.AddReg] +HKR,,CLSID,,%KSProxy.CLSID% +HKR,,FriendlyName,,%SkyWalker1.Receiver.FriendlyName% + + +[Strings] +;non-localizable +SGI="Plethorasoft" +MfgName="SGI" +SkyWalker1.DeviceDesc="SkyWalker1 BDA TVTuner" +SkyWalker1.Tuner.FriendlyName="SkyWalker1 TV Tuner" +SkyWalker1.Receiver.FriendlyName="SkyWalker1 TV Receiver" +SkyWalker1.Tuner="SkyWalker1.Tuner" +KSProxy.CLSID="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" +KSCATEGORY_BDA_NETWORK_TUNER="{71985F48-1CA1-11d3-9CC8-00C04F7971E0}" +KSCATEGORY_BDA_RECEIVER_COMPONENT="{FD0A5AF4-B41D-11d2-9C95-00C04F7971E0}" +SKYWALKER_TUNER="{5C4E764F-AB43-46A9-B21E-8529C70F0A23}" +SKYWALKER_CAPTURE="{0F8F74D9-E524-4D05-BB60-F0C69ACB1756}" + +; +; ServiceType values +SERVICE_KERNEL_DRIVER = 0x00000001 +SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 +SERVICE_ADAPTER = 0x00000004 +SERVICE_RECOGNIZER_DRIVER = 0x00000008 +SERVICE_WIN32_OWN_PROCESS = 0x00000010 +SERVICE_WIN32_SHARE_PROCESS = 0x00000020 +SERVICE_INTERACTIVE_PROCESS = 0x00000100 +SERVICE_INTERACTIVE_SHARE_PROCESS = 0x00000120 + +; StartType values +SERVICE_BOOT_START = 0x00000000 +SERVICE_SYSTEM_START = 0x00000001 +SERVICE_AUTO_START = 0x00000002 +SERVICE_DEMAND_START = 0x00000003 +SERVICE_DISABLED = 0x00000004 + +; ErrorControl values +SERVICE_ERROR_IGNORE = 0x00000000 +SERVICE_ERROR_NORMAL = 0x00000001 +SERVICE_ERROR_SEVERE = 0x00000002 +SERVICE_ERROR_CRITICAL = 0x00000003 + +; Characteristic flags +NCF_VIRTUAL = 0x0001 +NCF_WRAPPER = 0x0002 +NCF_PHYSICAL = 0x0004 +NCF_HIDDEN = 0x0008 +NCF_NO_SERVICE = 0x0010 +NCF_NOT_USER_REMOVABLE = 0x0020 +NCF_HAS_UI = 0x0080 +NCF_MODEM = 0x0100 + +; Registry types +REG_MULTI_SZ = 0x10000 +REG_EXPAND_SZ = 0x20000 +REG_DWORD = 0x10001 + +; Win9x Compatible Types +REG_BINARY = 17 +REG_SZ = 0 + +; Service install flags +SPSVCINST_TAGTOFRONT = 0x1 SPSVCINST_ASSOCSERVICE = 0x2 \ No newline at end of file diff --git a/firmware-driver/Skywalker-1 BDA Driver 08172009/SkyWalker2Installer.inf b/firmware-driver/Skywalker-1 BDA Driver 08172009/SkyWalker2Installer.inf index 959b14d..c82a3c6 100644 --- a/firmware-driver/Skywalker-1 BDA Driver 08172009/SkyWalker2Installer.inf +++ b/firmware-driver/Skywalker-1 BDA Driver 08172009/SkyWalker2Installer.inf @@ -1,142 +1,142 @@ -; SkyWalker2Installer.INF -- This file installs SkyWalker2 Driver -; -[Version] -signature="$CHICAGO$" -Class=Media -ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} -Provider=%SGI% -CatalogFile=SkyWalker2Installer.cat -DriverVer= 8/17/2009 - -; F i l e c o p y i n g s e c t i o n s (where the files go to). -; -[DestinationDirs] -DefaultDestDir=10,system32\drivers - -[Manufacturer] -%SGI%=SGI - -[ControlFlags] -;ExcludeFromSelect=* -;ExcludeFromSelect.NT=* - -; =================== Generic ================================== - -[SGI] -%SkyWalker2.DeviceDesc%=SkyWalker2.Device,USB\VID_09C0&PID_0206 ;SkyWalker2 - -[SkyWalker2.Device] -Include = ks.inf, kscaptur.inf, bda.inf -needs = KS.Registration, KSCAPTUR.Registration, BDA.Installation -AddReg = SkyWalker2.AddReg -CopyFiles = SkyWalker2.CopyDrivers - -[SkyWalker2.Device.NT] -Include = ks.inf, kscaptur.inf, bda.inf -needs = KS.Registration.NT, KSCAPTUR.Registration.NT, BDA.Installation.NT -;AddReg = SkyWalker2.AddReg -CopyFiles = SkyWalker2.CopyDrivers -; KnownFiles = SkyWalker2.KnownFiles - -[SkyWalker2.Device.NT.Services] -Addservice=SkyWalker2TVTuner, 0x00000002, SkyWalker2.AddService - -[SkyWalker2.AddService] -DisplayName=%SkyWalker2.FriendlyName% -ServiceType=1 ; SERVICE_KERNEL_DRIVER -StartType=3 ; SERVICE_DEMAND_START -ErrorControl=1 ; SERVICE_ERROR_NORMAL -ServiceBinary=%10%\System32\Drivers\SkyWalker1TVTuner.sys -LoadOrderGroup=ExtendedBase - -[SkyWalker2.CopyDrivers] -SkyWalker1TVTuner.sys - -[SkyWalker2.AddReg] -HKR,,DevLoader,,*NTKERN -HKR,,NTMPDriver,,SkyWalker1TVTuner.sys -HKR,,PageOutWhenUnopened,3,01 - -[SkyWalker2.Device.Interfaces] -AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,SkyWalker2.Receiver.Interfaces -AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,SkyWalker2.Tuner.Interfaces - -[SkyWalker2.Device.NT.Interfaces] -AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,SkyWalker2.Receiver.Interfaces -AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,SkyWalker2.Tuner.Interfaces - -[SkyWalker2.Tuner.Interfaces] -AddReg=SkyWalker2.Tuner.Interfaces.AddReg - -[SkyWalker2.Tuner.Interfaces.AddReg] -HKR,,CLSID,,%KSProxy.CLSID% -HKR,,FriendlyName,,%SkyWalker2.Tuner.FriendlyName% - -[SkyWalker2.Receiver.Interfaces] -AddReg=SkyWalker2.Receiver.Interfaces.AddReg - -[SkyWalker2.Receiver.Interfaces.AddReg] -HKR,,CLSID,,%KSProxy.CLSID% -HKR,,FriendlyName,,%SkyWalker2.Receiver.FriendlyName% - - -[Strings] -;non-localizable -SGI="Plethorasoft" -MfgName="SGI" -SkyWalker2.DeviceDesc="SkyWalker2 BDA TVTuner" -SkyWalker2.Tuner.FriendlyName="SkyWalker2 TV Tuner" -SkyWalker2.Receiver.FriendlyName="SkyWalker2 TV Receiver" -SkyWalker2.Tuner="SkyWalker2.Tuner" -KSProxy.CLSID="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" -KSCATEGORY_BDA_NETWORK_TUNER="{71985F48-1CA1-11d3-9CC8-00C04F7971E0}" -KSCATEGORY_BDA_RECEIVER_COMPONENT="{FD0A5AF4-B41D-11d2-9C95-00C04F7971E0}" -SKYWALKER_TUNER="{5C4E764F-AB43-46A9-B21E-8529C70F0A23}" -SKYWALKER_CAPTURE="{0F8F74D9-E524-4D05-BB60-F0C69ACB1756}" - -; -; ServiceType values -SERVICE_KERNEL_DRIVER = 0x00000001 -SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 -SERVICE_ADAPTER = 0x00000004 -SERVICE_RECOGNIZER_DRIVER = 0x00000008 -SERVICE_WIN32_OWN_PROCESS = 0x00000010 -SERVICE_WIN32_SHARE_PROCESS = 0x00000020 -SERVICE_INTERACTIVE_PROCESS = 0x00000100 -SERVICE_INTERACTIVE_SHARE_PROCESS = 0x00000120 - -; StartType values -SERVICE_BOOT_START = 0x00000000 -SERVICE_SYSTEM_START = 0x00000001 -SERVICE_AUTO_START = 0x00000002 -SERVICE_DEMAND_START = 0x00000003 -SERVICE_DISABLED = 0x00000004 - -; ErrorControl values -SERVICE_ERROR_IGNORE = 0x00000000 -SERVICE_ERROR_NORMAL = 0x00000001 -SERVICE_ERROR_SEVERE = 0x00000002 -SERVICE_ERROR_CRITICAL = 0x00000003 - -; Characteristic flags -NCF_VIRTUAL = 0x0001 -NCF_WRAPPER = 0x0002 -NCF_PHYSICAL = 0x0004 -NCF_HIDDEN = 0x0008 -NCF_NO_SERVICE = 0x0010 -NCF_NOT_USER_REMOVABLE = 0x0020 -NCF_HAS_UI = 0x0080 -NCF_MODEM = 0x0100 - -; Registry types -REG_MULTI_SZ = 0x10000 -REG_EXPAND_SZ = 0x20000 -REG_DWORD = 0x10001 - -; Win9x Compatible Types -REG_BINARY = 17 -REG_SZ = 0 - -; Service install flags -SPSVCINST_TAGTOFRONT = 0x1 +; SkyWalker2Installer.INF -- This file installs SkyWalker2 Driver +; +[Version] +signature="$CHICAGO$" +Class=Media +ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} +Provider=%SGI% +CatalogFile=SkyWalker2Installer.cat +DriverVer= 8/17/2009 + +; F i l e c o p y i n g s e c t i o n s (where the files go to). +; +[DestinationDirs] +DefaultDestDir=10,system32\drivers + +[Manufacturer] +%SGI%=SGI + +[ControlFlags] +;ExcludeFromSelect=* +;ExcludeFromSelect.NT=* + +; =================== Generic ================================== + +[SGI] +%SkyWalker2.DeviceDesc%=SkyWalker2.Device,USB\VID_09C0&PID_0206 ;SkyWalker2 + +[SkyWalker2.Device] +Include = ks.inf, kscaptur.inf, bda.inf +needs = KS.Registration, KSCAPTUR.Registration, BDA.Installation +AddReg = SkyWalker2.AddReg +CopyFiles = SkyWalker2.CopyDrivers + +[SkyWalker2.Device.NT] +Include = ks.inf, kscaptur.inf, bda.inf +needs = KS.Registration.NT, KSCAPTUR.Registration.NT, BDA.Installation.NT +;AddReg = SkyWalker2.AddReg +CopyFiles = SkyWalker2.CopyDrivers +; KnownFiles = SkyWalker2.KnownFiles + +[SkyWalker2.Device.NT.Services] +Addservice=SkyWalker2TVTuner, 0x00000002, SkyWalker2.AddService + +[SkyWalker2.AddService] +DisplayName=%SkyWalker2.FriendlyName% +ServiceType=1 ; SERVICE_KERNEL_DRIVER +StartType=3 ; SERVICE_DEMAND_START +ErrorControl=1 ; SERVICE_ERROR_NORMAL +ServiceBinary=%10%\System32\Drivers\SkyWalker1TVTuner.sys +LoadOrderGroup=ExtendedBase + +[SkyWalker2.CopyDrivers] +SkyWalker1TVTuner.sys + +[SkyWalker2.AddReg] +HKR,,DevLoader,,*NTKERN +HKR,,NTMPDriver,,SkyWalker1TVTuner.sys +HKR,,PageOutWhenUnopened,3,01 + +[SkyWalker2.Device.Interfaces] +AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,SkyWalker2.Receiver.Interfaces +AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,SkyWalker2.Tuner.Interfaces + +[SkyWalker2.Device.NT.Interfaces] +AddInterface=%KSCATEGORY_BDA_RECEIVER_COMPONENT%,%SKYWALKER_CAPTURE%,SkyWalker2.Receiver.Interfaces +AddInterface=%KSCATEGORY_BDA_NETWORK_TUNER%,%SKYWALKER_TUNER%,SkyWalker2.Tuner.Interfaces + +[SkyWalker2.Tuner.Interfaces] +AddReg=SkyWalker2.Tuner.Interfaces.AddReg + +[SkyWalker2.Tuner.Interfaces.AddReg] +HKR,,CLSID,,%KSProxy.CLSID% +HKR,,FriendlyName,,%SkyWalker2.Tuner.FriendlyName% + +[SkyWalker2.Receiver.Interfaces] +AddReg=SkyWalker2.Receiver.Interfaces.AddReg + +[SkyWalker2.Receiver.Interfaces.AddReg] +HKR,,CLSID,,%KSProxy.CLSID% +HKR,,FriendlyName,,%SkyWalker2.Receiver.FriendlyName% + + +[Strings] +;non-localizable +SGI="Plethorasoft" +MfgName="SGI" +SkyWalker2.DeviceDesc="SkyWalker2 BDA TVTuner" +SkyWalker2.Tuner.FriendlyName="SkyWalker2 TV Tuner" +SkyWalker2.Receiver.FriendlyName="SkyWalker2 TV Receiver" +SkyWalker2.Tuner="SkyWalker2.Tuner" +KSProxy.CLSID="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" +KSCATEGORY_BDA_NETWORK_TUNER="{71985F48-1CA1-11d3-9CC8-00C04F7971E0}" +KSCATEGORY_BDA_RECEIVER_COMPONENT="{FD0A5AF4-B41D-11d2-9C95-00C04F7971E0}" +SKYWALKER_TUNER="{5C4E764F-AB43-46A9-B21E-8529C70F0A23}" +SKYWALKER_CAPTURE="{0F8F74D9-E524-4D05-BB60-F0C69ACB1756}" + +; +; ServiceType values +SERVICE_KERNEL_DRIVER = 0x00000001 +SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 +SERVICE_ADAPTER = 0x00000004 +SERVICE_RECOGNIZER_DRIVER = 0x00000008 +SERVICE_WIN32_OWN_PROCESS = 0x00000010 +SERVICE_WIN32_SHARE_PROCESS = 0x00000020 +SERVICE_INTERACTIVE_PROCESS = 0x00000100 +SERVICE_INTERACTIVE_SHARE_PROCESS = 0x00000120 + +; StartType values +SERVICE_BOOT_START = 0x00000000 +SERVICE_SYSTEM_START = 0x00000001 +SERVICE_AUTO_START = 0x00000002 +SERVICE_DEMAND_START = 0x00000003 +SERVICE_DISABLED = 0x00000004 + +; ErrorControl values +SERVICE_ERROR_IGNORE = 0x00000000 +SERVICE_ERROR_NORMAL = 0x00000001 +SERVICE_ERROR_SEVERE = 0x00000002 +SERVICE_ERROR_CRITICAL = 0x00000003 + +; Characteristic flags +NCF_VIRTUAL = 0x0001 +NCF_WRAPPER = 0x0002 +NCF_PHYSICAL = 0x0004 +NCF_HIDDEN = 0x0008 +NCF_NO_SERVICE = 0x0010 +NCF_NOT_USER_REMOVABLE = 0x0020 +NCF_HAS_UI = 0x0080 +NCF_MODEM = 0x0100 + +; Registry types +REG_MULTI_SZ = 0x10000 +REG_EXPAND_SZ = 0x20000 +REG_DWORD = 0x10001 + +; Win9x Compatible Types +REG_BINARY = 17 +REG_SZ = 0 + +; Service install flags +SPSVCINST_TAGTOFRONT = 0x1 SPSVCINST_ASSOCSERVICE = 0x2 \ No newline at end of file diff --git a/firmware-driver/updater/ReadMe.txt b/firmware-driver/updater/ReadMe.txt index 20d8454..e47d013 100644 --- a/firmware-driver/updater/ReadMe.txt +++ b/firmware-driver/updater/ReadMe.txt @@ -1,18 +1,18 @@ -This firmware update utility will upgrade 8pSK-toUSB2 Rev.2 adapter firmware to ver.2.10.4 - -############################################################################################################## -# -# Make sure that genpix 8pSK-toUSB2 Rev.2 adapter is the ONLY genpix device connected to PC during firmware upgrade. -# Disconnect all other genpix devices. -# -# 8PSK module has to be connected to adapter during firmware upgrade procedure. -# -# Do NOT switch the PC power off during firmware upgrade procedure. -# Do NOT plug/unplug any USB devices during firmware upgrade procedure. -# -############################################################################################################## - -8pSK-toUSB2 Rev.2 adapter firmware updater runs in Windows x32/x64 environment only (Windows XP and higher). - -To start firmware upgrade: -double-click on Rev2_update_2_10_4.exe file, and follow the instruction on the screen. +This firmware update utility will upgrade 8pSK-toUSB2 Rev.2 adapter firmware to ver.2.10.4 + +############################################################################################################## +# +# Make sure that genpix 8pSK-toUSB2 Rev.2 adapter is the ONLY genpix device connected to PC during firmware upgrade. +# Disconnect all other genpix devices. +# +# 8PSK module has to be connected to adapter during firmware upgrade procedure. +# +# Do NOT switch the PC power off during firmware upgrade procedure. +# Do NOT plug/unplug any USB devices during firmware upgrade procedure. +# +############################################################################################################## + +8pSK-toUSB2 Rev.2 adapter firmware updater runs in Windows x32/x64 environment only (Windows XP and higher). + +To start firmware upgrade: +double-click on Rev2_update_2_10_4.exe file, and follow the instruction on the screen. diff --git a/firmware-dump/fw_v213_comparison_report.md b/firmware-dump/fw_v213_comparison_report.md index ad042cf..12db42d 100644 --- a/firmware-dump/fw_v213_comparison_report.md +++ b/firmware-dump/fw_v213_comparison_report.md @@ -1,379 +1,379 @@ -# Genpix SkyWalker-1 Firmware v2.13.x Sub-Variant Comparison Report - -## Executive Summary - -Three firmware sub-variants were extracted from `SW1_update_2_13_x.exe` and analyzed as 8051 (Cypress FX2) binaries via Ghidra. The analysis reveals that **FW1 (v2.13.1) targets fundamentally different hardware** than FW2/FW3, while **FW2 (v2.13.2) and FW3 (v2.13.3) target different revisions of a newer hardware platform** with an external demodulator connected via a parallel data bus. - ---- - -## 1. Function List Comparison - -### Function Counts -| Variant | Port | Functions | Extra Function | -|---------|------|-----------|----------------| -| FW1 (v2.13.1) | 8194 | 82 | FUN_CODE_0fc7, FUN_CODE_1405, FUN_CODE_14b9 (unique) | -| FW2 (v2.13.2) | 8195 | 83 | FUN_CODE_1288 (unique), FUN_CODE_0ffc | -| FW3 (v2.13.3) | 8196 | 83 | FUN_CODE_1288, FUN_CODE_0706 (unique) | - -### Shared Core Functions (Same Address Across All Three) -The following functions exist at identical addresses in all variants: - -``` -CODE:0000 RESET_vector CODE:0003 INT0_vector -CODE:0033 INT2_USB_GPIF_vector CODE:0036 FUN_CODE_0036 -CODE:0043 INT4_FX2_vector CODE:004b INT5_FX2_vector -CODE:0050 FUN_CODE_0050 CODE:0053 INT6_FX2_vector -CODE:0056 FUN_CODE_0056 CODE:034e FUN_CODE_034e (vendor cmd handler) -CODE:06d9 FUN_CODE_06d9 CODE:0718 FUN_CODE_0718 -CODE:072a FUN_CODE_072a CODE:0779 FUN_CODE_0779 -CODE:079d FUN_CODE_079d CODE:0800 FUN_CODE_0800 (main loop) -CODE:0ca4 FUN_CODE_0ca4 CODE:0eea FUN_CODE_0eea -CODE:1000 FUN_CODE_1000 CODE:1500 thunk (target differs!) -CODE:15b8 FUN_CODE_15b8 CODE:170d main_entry -CODE:1799 FUN_CODE_1799 CODE:1800 FUN_CODE_1800 -CODE:19ed FUN_CODE_19ed CODE:1a5d FUN_CODE_1a5d -CODE:1ac6 FUN_CODE_1ac6 CODE:1b2a FUN_CODE_1b2a -``` - -### Functions That Shift Addresses (Same Logic, Different Location) -Many functions exist in all three variants but at shifted addresses: - -| Purpose | FW1 Address | FW2 Address | FW3 Address | -|---------|-------------|-------------|-------------| -| Hardware init | 0x11ab | 0x1288 | 0x1288 | -| Demod setup | 0x10d9 | 0x10dd | 0x10dd | -| I2C/parallel data transfer | 0x0eea (I2C) | 0x0eea (parallel) | 0x0eea (parallel) | -| Tuner detect | 0x1405 | 0x0eea | 0x0eea | -| Delay loop | 0x14b9 | 0x1e92 (FW2) | 0x1e88 (FW3) | -| EEPROM checksum | 0x1ca0 | 0x1cff | 0x1ca1 | -| USB descriptor setup | 0x2031 | 0x206c | 0x206c | -| Thunk target | 0x2252 | 0x228d | 0x228d | -| 2nd init call | 0x1be6 | 0x1c45 | 0x1cf7 | - -### Functions Unique to Each Variant - -**FW1 only:** -- `FUN_CODE_0fc7` -- An I2C write-with-retry function (tries 0x14 = 20 times via I2C bus using FUN_CODE_23ae/23ee) -- `FUN_CODE_1405` -- Tuner/demodulator identification via **I2C bus + P1 port reads** with signature matching -- `FUN_CODE_14b9` -- Calibrated delay function using CPUCS clock divider awareness - -**FW2 only:** -- `FUN_CODE_0ffc` -- Stores a parameter into BANK3_R1 (register save) -- `FUN_CODE_1288` -- New hardware initialization (loads from external memory at e080-e08e) - -**FW3 only:** -- `FUN_CODE_0706` -- Memory write dispatcher that handles 3 addressing modes (XDATA, IDATA, direct) -- `FUN_CODE_1288` -- Same as FW2 -- Uses `FUN_CODE_1ffc` (at a different address from FW2's 0x1ffd) - ---- - -## 2. Main Entry Comparison (CODE:170D) - -### Identical Across All Three: -- IRAM clear loop (0x7F down to 0x00) -- Init table parsing from CODE:0B88 -- Bit config table reference at CODE:1740 -- Final call to FUN_CODE_0800 - -### One Key Difference -- Stack Pointer: - -| Variant | SP Value | -|---------|----------| -| FW1 | SP = 0x50 | -| FW2 | SP = 0x50 | -| FW3 | SP = **0x52** | - -FW3 sets `SP = 0x52`, requiring 2 more bytes of IRAM for stack usage. This indicates FW3 uses additional internal RAM locations (0x51-0x52) for state variables that FW1/FW2 don't need, pushing the stack higher. - -**Confirmed**: FW3 uses `DAT_INTMEM_51` as a hardware status register throughout its code, while FW1/FW2 use `DAT_INTMEM_4f` for the same purpose. The 2-byte difference in SP exactly accounts for this. - -### Memory at CODE:170D: -``` -FW1: 787f e4f6 d8fd 7581 50 02 1754 020800 e4 -FW2: 787f e4f6 d8fd 7581 50 02 1754 020800 e4 (identical to FW1) -FW3: 787f e4f6 d8fd 7581 52 02 1754 020800 e4 (byte at 0x1714 = 0x52 vs 0x50) -``` - ---- - -## 3. Key Function Decompilation Comparison - -### 3.1 FUN_CODE_0800 (Main Loop) -- All at Same Address - -**Structure identical across all three:** -1. Clear INTMEM locations 0x22-0x2D, 0x32-0x35 -2. Clear bit flags _1_0 and _0_6 -3. Call hardware init (address differs) -4. Set up BANK register pairs (XDATA pointers): all use the same values (0x0E00, 0x0E12, 0x0E1C, 0x0E54, 0x0E8C, 0x0EE8) -5. Call FUN_CODE_072a with init params -6. Memory copy loop (0x80 bytes at offset 0x0E00) -7. Retry loop with FUN_CODE_1799 (20 attempts) -8. Retry loop with EEPROM checksum function (20 attempts) -9. Check demod type byte at offset +10 from BANK1_R4/R5 (== 0x03 -> set flag) -10. Enable interrupts, enter main event loop - -**Differences in called function addresses (relocated, not functionally different):** - -| Call Purpose | FW1 | FW2 | FW3 | -|-------------|------|------|------| -| Hardware init | FUN_CODE_11ab | FUN_CODE_1288 | FUN_CODE_1288 | -| EEPROM checksum | FUN_CODE_1ca0 | FUN_CODE_1cff | FUN_CODE_1ca1 | -| USB setup | FUN_CODE_2031 | FUN_CODE_206c | FUN_CODE_206c | -| Main loop poll | FUN_CODE_21ec | FUN_CODE_2227 | FUN_CODE_2227 | -| Interrupt check | FUN_CODE_2445 | FUN_CODE_247c | FUN_CODE_2473 | -| Status check | FUN_CODE_2189 | FUN_CODE_21c4 | FUN_CODE_21c4 | -| Buffer flush | FUN_CODE_20b9 | FUN_CODE_20f4 | FUN_CODE_20f4 | -| EP complete | FUN_CODE_2447 | FUN_CODE_247e | FUN_CODE_2475 | - -### 3.2 Hardware Init Function (FW1: 0x11ab, FW2/FW3: 0x1288) - -**Functionally identical** across all three except: - -| Parameter | FW1 | FW2 | FW3 | -|-----------|------|------|------| -| P0 init value | **0xa4** | **0xa4** | **0xa0** | -| Status register | DAT_INTMEM_4f | DAT_INTMEM_4f | **DAT_INTMEM_51** | -| Sub-init call 1 | FUN_CODE_1c44 | FUN_CODE_1ca3 | FUN_CODE_1c45 | -| Sub-init call 2 | FUN_CODE_1000 | FUN_CODE_10dd | FUN_CODE_10dd | -| I2C/bus init | FUN_CODE_213b | FUN_CODE_2176 | FUN_CODE_2176 | -| Tuner init | FUN_CODE_1be6 | FUN_CODE_1c45 | FUN_CODE_1cf7 | - -**P0 = 0xa4 vs 0xa0**: P0 on the FX2 controls GPIO port 0. The difference is bit 2: -- FW1/FW2: P0 bit 2 = 1 (0xa4 = 1010 0100) -- FW3: P0 bit 2 = 0 (0xa0 = 1010 0000) - -This suggests different default GPIO state for a control signal, likely related to the demodulator interface mode or reset polarity. - -### 3.3 FUN_CODE_0eea -- The Most Revealing Difference - -**FW1**: This is a standard **I2C master transfer** function: -- Uses `FUN_CODE_23ae` (I2C START), `FUN_CODE_23ee` (I2C byte write), `FUN_CODE_23d0` (I2C address) -- Reads back via `FUN_CODE_2164` -- Standard I2C retry with NACK detection - -**FW2**: This is a **parallel bus read with demodulator handshake**: -- Reads demod type from address table (BANK1_R4/R5 + offset) -- Uses `FUN_CODE_11b6` for demod selection -- Toggles P0 bits 6/7 for bus control (P0.6 = chip select, P0.7 = read strobe) -- Reads data from P1 port (parallel data bus) -- Checks P1 ^ 0x1D (signature) then reads P1 for device ID -- Matches device IDs: 0xC5/0xD5 (for type 3), 0x5A (type 4), 0x5B (type 5), 0x5C (type 6) -- Controls P3 bits for demod power/reset - -**FW3**: Similar parallel bus read as FW2 but with **different timing and bus protocol**: -- Sets P0 | 0x80 once at start (not per-iteration like FW2) -- Uses `DAT_INTMEM_3f` and `DAT_INTMEM_40` as OR-accumulators for P1 reads -- Two separate P1 reads per cycle: one with P0.6 high, one with P0.6 low -- Calls `FUN_CODE_1b2a` with 3 parameters (accumulated OR values) vs FW2's 2 parameters -- Uses `P0 | 0x44` and `P0 & 0xBF` toggle pattern (vs FW2's different bit dance) - -### 3.4 Vendor Command Handler (FUN_CODE_034e) - -**Structurally identical** across all three -- same switch/case table structure at CODE:035e. - -Key differences: - -| Feature | FW1 | FW2 | FW3 | -|---------|------|------|------| -| Case 0x35f/0x427 call | FUN_CODE_0ffe (nop) | FUN_CODE_1ffd | FUN_CODE_0ffe (nop) | -| Case 0x361 call | FUN_CODE_2441 | FUN_CODE_2478 | FUN_CODE_246f | -| Case 0x365 call | FUN_CODE_2443 | FUN_CODE_247a | FUN_CODE_2471 | -| Case 0x36f call | FUN_CODE_2357 | FUN_CODE_2392 | FUN_CODE_2392 | -| Case 0x371 call | FUN_CODE_243d | FUN_CODE_0ffc | FUN_CODE_1ffc | -| Case 0x373/0x3ff call | FUN_CODE_2309 | FUN_CODE_2344 | FUN_CODE_2344 | -| Case 0xf0 indirect call | func_0x231e | func_0x2359 | func_0x2359 | -| Case 0x39d return | func_0x06e4 | DAT=0x0 | DAT=0x0 | -| Case 0x3d1 call | FUN_CODE_2110 | FUN_CODE_214b | FUN_CODE_214b | -| Case 0x3d3 behavior | TR2 timer check | OR operation | OR operation | -| Case 0x405 behavior | Goto LAB_05db | Conditional branch | Conditional branch | -| Case 0x421 behavior | Simple check | Extra P2_1, RL A logic | Extra P2_1, RL A logic | - -**FW1's unique case 0x3d3**: Checks Timer 2 Run flag (TR2) -- this is used for I2C bus timeout recovery, consistent with FW1 being I2C-based. - -**FW2/FW3's unique case 0x421-0x423**: Includes a rotate-left and P2.1 write -- this is a parallel bus data direction control, consistent with the external demodulator interface. - ---- - -## 4. Memory Comparison at Key Offsets - -### CODE:0000-0x000F (Reset Vector) -``` -FW1: 02170d 753728 e53760 1b7ffc 7e7f12 22 -FW2: 02170d 753728 e53760 1b7ffc 7e7f12 22 -FW3: 02170d 753728 e53760 1b7ffc 7e7f12 22 -ALL IDENTICAL -- LJMP 0x170D, then INT0 vector handler -``` - -### CODE:0B88-0x0B9F (Init Table Start) -``` -FW1: 41e0b6 626033 e0c609 070939 4f0000 000000 000000 000000 -FW2: 41e0b6 626033 e0c609 070939 4f0000 000000 000000 000000 -FW3: 41e0b6 626033 e0c609 070939 4f0000 000000 000000 000000 -ALL IDENTICAL -- Same register/SFR initialization table -``` - -### CODE:1500 (Thunk/INT Vector Target) -``` -FW1: 02 2252 00 02 22dd 00 02 22c7 00 02 226a 00 -FW2: 02 228d 00 02 2318 00 02 2302 00 02 22a5 00 -FW3: 02 228d 00 02 2318 00 02 2302 00 02 22a5 00 -``` -FW1 jumps to different addresses than FW2/FW3. **FW2 and FW3 are IDENTICAL here** -- their interrupt handlers are at the same addresses. - -### CODE:1740-0x174F (Bit Config Table) -``` -FW1: 4004 f456 8001 46f6 dfe4 800b 0102 0408 -FW2: 4004 f456 8001 46f6 dfe4 800b 0102 0408 -FW3: 4004 f456 8001 46f6 dfe4 800b 0102 0408 -ALL IDENTICAL -- Same bit manipulation lookup table -``` - -### CODE:0800 (Main Loop Start) -``` -FW1: e4f52d...c208c206 12 11ab 750c0e... -FW2: e4f52d...c208c206 12 1288 750c0e... -FW3: e4f52d...c208c206 12 1288 750c0e... -``` -Only difference: the LCALL target (FW1: 0x11ab, FW2/FW3: 0x1288). - -### CODE:06D9 (Utility Functions) -``` -FW1: bb010c e58229 f582e5 833af5 83e022 5006e9 2582f8 e622 -FW2: bb010c e58229 f582e5 833af5 83e022 5006e9 2582f8 e622 -FW3: bb010c e58229 f582e5 833af5 83e022 5006e9 2582f8 e622 -ALL IDENTICAL -- Generic memory access utilities shared by all -``` - -### CODE:0EEA (Critical Divergence Point) -``` -FW1: 8f44 8c45 8d46 8b47 754a14 e544 b451... (I2C transfer params in registers) -FW2: 753e14 e50d 240a f582 e435 0cf5 83e0... (reads from DPTR+offset table) -FW3: 753e14 e4f5 3ff5 40 e50d 240a f582... (similar to FW2 + accumulator init) -``` -This confirms: FW1's FUN_CODE_0eea is a completely different function (I2C master) than FW2/FW3's (parallel bus demod interface). - ---- - -## 5. FW2 vs FW3 Specific Differences - -FW2 and FW3 are the most similar pair (1,525 bytes different). Key differences: - -| Feature | FW2 | FW3 | -|---------|------|------| -| Stack Pointer | SP = 0x50 | SP = 0x52 | -| Status register | DAT_INTMEM_4f | DAT_INTMEM_51 | -| P0 init | 0xa4 | 0xa0 (bit 2 different) | -| FUN_CODE_0eea bus protocol | Single-phase P1 read | Dual-phase P1 read with OR accumulation | -| I2C buffer addresses | DAT_INTMEM_48/49 | DAT_INTMEM_4a/4b | -| Unique function | FUN_CODE_0ffc (register store) | FUN_CODE_0706 (multi-mode write) | -| P0 bus timing | P0 &= ~0x40; P0 |= 0x80 per iteration | P0 |= 0x80 once; P0 |= 0x44 / P0 &= ~0x40 per phase | -| Delay function address | FUN_CODE_1e92 | FUN_CODE_1e88 | - -### The P1 Read Difference is Critical - -**FW2** reads P1 once per bus cycle: -```c -uVar1 = P1; // Read with one bus state -P0 |= 0x40; // Then change control line -uVar2 = P1; // Read again -FUN_CODE_1b2a(uVar2, uVar1); // Process both samples -``` - -**FW3** reads P1 in two phases and OR-accumulates: -```c -DAT_INTMEM_3f = 0; DAT_INTMEM_40 = 0; // Clear accumulators -// Phase 1: P0.6 high -P0 |= 0x44; -bVar2 = P1; -DAT_INTMEM_3f |= bVar2; // OR-accumulate -// Phase 2: P0.6 low -P0 &= ~0x40; -bVar2 = P1; -DAT_INTMEM_40 |= bVar2; // OR-accumulate -FUN_CODE_1b2a(0, DAT_INTMEM_3f, DAT_INTMEM_40); // Process accumulated -``` - -This OR-accumulation pattern in FW3 suggests dealing with a bus that may have metastable signals or requires multiple samples, characteristic of **a different demodulator chip with different bus timing**. - ---- - -## 6. Hypothesis: What Distinguishes Each Variant - -### FW1 (v2.13.1) -- Original I2C-Connected Demodulator Hardware - -**Target**: First-generation SkyWalker-1 PCB with an **I2C-connected demodulator** (likely a Conexant/Zarlink integrated tuner+demod). - -Evidence: -- Uses standard I2C protocol functions (START, STOP, ACK/NACK, byte read/write) -- FUN_CODE_0fc7: I2C write retry loop -- FUN_CODE_1405: Reads demodulator identification via I2C + P1 GPIO, checks device signatures: - - Type 3: P1 == 0xA5 or 0xB5 - - Type 4: P1 == 0x5A - - Type 5: P1 == 0x5B - - Type 6: P1 == 0x5C -- Has timer-based I2C timeout (TR2 check in vendor handler) -- SP=0x50, fewer IRAM state variables needed -- `func_0x06e4` called for unknown vendor commands (older error path) - -**Likely demodulator**: An I2C-bus demodulator supporting DVB-S/DCII/DSS, with the FX2 as USB bridge. The type codes 3-6 likely correspond to different supported modulation modes or demod silicon revisions. - -### FW2 (v2.13.2) -- Second-Generation Parallel-Bus Demodulator - -**Target**: Revised SkyWalker-1 PCB with a **parallel-bus connected demodulator** (likely a different demod chip or a custom FPGA/ASIC). - -Evidence: -- FUN_CODE_0eea: Parallel bus read using P0 GPIO for control (CS, RD strobe) and P1 for data -- FUN_CODE_10dd: Copies configuration from external memory (e080-e08e) into demod registers (e6c0-e6cd) -- reads 15 configuration bytes from what appears to be EEPROM/flash config area -- Reads same device signatures but via parallel bus (P1 ^ 0x1D check, then P1 reads for 0xC5/0xD5/0x5A/0x5B/0x5C) -- P0 = 0xa4 (bit 2 set = specific bus mode select) -- SP = 0x50 -- Extra vendor command paths for parallel data direction (P2.1 control in case 0x421/0x423) -- Uses FUN_CODE_14e2: Busy-wait on e678 bit 6 (demod ready flag) with 0xFFFF timeout counter - -**Likely demodulator**: A parallel-bus demodulator with 8-bit data port on P1, active-low chip select and read strobe on P0. The external config block (e080-e08e) stores per-unit calibration/tuning data. - -### FW3 (v2.13.3) -- Third-Generation with Enhanced Bus Protocol - -**Target**: Further revised PCB with the **same parallel-bus demodulator as FW2** but with a **different bus interface revision** or a variant chip that requires modified timing. - -Evidence: -- Same demod configuration loading as FW2 (FUN_CODE_10dd identical) -- Same parallel bus architecture but with dual-phase reading and OR-accumulation -- P0 = 0xa0 (bit 2 clear = different bus mode or reset polarity) -- SP = 0x52 (2 more IRAM bytes: status register moved from 0x4F to 0x51) -- FUN_CODE_0706 (unique): Multi-mode memory write supporting XDATA, IDATA, and direct addressing -- suggests the demod communicates through multiple address spaces -- The OR-accumulation of P1 reads suggests either: - - A demodulator with open-drain outputs requiring multiple read cycles - - Bus settling time issues on the newer PCB layout - - A chip variant that serializes data across multiple bus phases - ---- - -## 7. Summary Table - -| Aspect | FW1 (v2.13.1) | FW2 (v2.13.2) | FW3 (v2.13.3) | -|--------|---------------|---------------|---------------| -| **Demod interface** | I2C bus | Parallel bus (P0/P1) | Parallel bus (enhanced) | -| **Bus protocol** | I2C START/STOP/ACK | Single-phase P1 read | Dual-phase P1 read + OR accumulate | -| **Stack pointer** | 0x50 | 0x50 | 0x52 | -| **P0 init** | 0xa4 | 0xa4 | 0xa0 | -| **Status register** | INTMEM 0x4F | INTMEM 0x4F | INTMEM 0x51 | -| **Config source** | Hardcoded | External (e080-e08e) | External (e080-e08e) | -| **Demod types supported** | 3-6 (via I2C) | 3-6 (via parallel) | 3-6 (via parallel) | -| **Binary distance from FW1** | -- | 3,993 bytes | 3,789 bytes | -| **Binary distance from FW2** | 3,993 bytes | -- | 1,525 bytes | - -## 8. Conclusion - -The three v2.13 firmware sub-variants represent an evolutionary progression of the SkyWalker-1 hardware: - -1. **v2.13.1 (FW1)**: Original design with I2C-connected demodulator. The FX2 communicates with the demod entirely through I2C, using standard master-mode transactions. This is the simplest interface but limited in bandwidth. - -2. **v2.13.2 (FW2)**: Redesigned with a parallel-bus demodulator. The demod data port is connected directly to FX2's P1, with P0 bits used for bus control signals (chip select, read/write strobes). Configuration data is loaded from an external EEPROM area. This provides higher throughput for TS data transfer. - -3. **v2.13.3 (FW3)**: Refinement of the FW2 design, likely for a newer demod silicon revision or PCB layout. Uses dual-phase bus reads with signal accumulation, different GPIO defaults, and additional IRAM for state tracking. The OR-accumulation pattern suggests dealing with bus signal integrity improvements. - -The updater program's format string `"FW 2.13.%i"` and its selection logic presumably check the hardware revision (likely via a GPIO strap or I2C ID read) to determine which of the three firmware images to flash. - -All three variants support the same modulation types (DVB-S/QPSK, Turbo QPSK/8PSK, DCII, DSS) -- the demod type codes 3-6 appear in all variants. The differences are purely about the hardware interface, not the feature set. +# Genpix SkyWalker-1 Firmware v2.13.x Sub-Variant Comparison Report + +## Executive Summary + +Three firmware sub-variants were extracted from `SW1_update_2_13_x.exe` and analyzed as 8051 (Cypress FX2) binaries via Ghidra. The analysis reveals that **FW1 (v2.13.1) targets fundamentally different hardware** than FW2/FW3, while **FW2 (v2.13.2) and FW3 (v2.13.3) target different revisions of a newer hardware platform** with an external demodulator connected via a parallel data bus. + +--- + +## 1. Function List Comparison + +### Function Counts +| Variant | Port | Functions | Extra Function | +|---------|------|-----------|----------------| +| FW1 (v2.13.1) | 8194 | 82 | FUN_CODE_0fc7, FUN_CODE_1405, FUN_CODE_14b9 (unique) | +| FW2 (v2.13.2) | 8195 | 83 | FUN_CODE_1288 (unique), FUN_CODE_0ffc | +| FW3 (v2.13.3) | 8196 | 83 | FUN_CODE_1288, FUN_CODE_0706 (unique) | + +### Shared Core Functions (Same Address Across All Three) +The following functions exist at identical addresses in all variants: + +``` +CODE:0000 RESET_vector CODE:0003 INT0_vector +CODE:0033 INT2_USB_GPIF_vector CODE:0036 FUN_CODE_0036 +CODE:0043 INT4_FX2_vector CODE:004b INT5_FX2_vector +CODE:0050 FUN_CODE_0050 CODE:0053 INT6_FX2_vector +CODE:0056 FUN_CODE_0056 CODE:034e FUN_CODE_034e (vendor cmd handler) +CODE:06d9 FUN_CODE_06d9 CODE:0718 FUN_CODE_0718 +CODE:072a FUN_CODE_072a CODE:0779 FUN_CODE_0779 +CODE:079d FUN_CODE_079d CODE:0800 FUN_CODE_0800 (main loop) +CODE:0ca4 FUN_CODE_0ca4 CODE:0eea FUN_CODE_0eea +CODE:1000 FUN_CODE_1000 CODE:1500 thunk (target differs!) +CODE:15b8 FUN_CODE_15b8 CODE:170d main_entry +CODE:1799 FUN_CODE_1799 CODE:1800 FUN_CODE_1800 +CODE:19ed FUN_CODE_19ed CODE:1a5d FUN_CODE_1a5d +CODE:1ac6 FUN_CODE_1ac6 CODE:1b2a FUN_CODE_1b2a +``` + +### Functions That Shift Addresses (Same Logic, Different Location) +Many functions exist in all three variants but at shifted addresses: + +| Purpose | FW1 Address | FW2 Address | FW3 Address | +|---------|-------------|-------------|-------------| +| Hardware init | 0x11ab | 0x1288 | 0x1288 | +| Demod setup | 0x10d9 | 0x10dd | 0x10dd | +| I2C/parallel data transfer | 0x0eea (I2C) | 0x0eea (parallel) | 0x0eea (parallel) | +| Tuner detect | 0x1405 | 0x0eea | 0x0eea | +| Delay loop | 0x14b9 | 0x1e92 (FW2) | 0x1e88 (FW3) | +| EEPROM checksum | 0x1ca0 | 0x1cff | 0x1ca1 | +| USB descriptor setup | 0x2031 | 0x206c | 0x206c | +| Thunk target | 0x2252 | 0x228d | 0x228d | +| 2nd init call | 0x1be6 | 0x1c45 | 0x1cf7 | + +### Functions Unique to Each Variant + +**FW1 only:** +- `FUN_CODE_0fc7` -- An I2C write-with-retry function (tries 0x14 = 20 times via I2C bus using FUN_CODE_23ae/23ee) +- `FUN_CODE_1405` -- Tuner/demodulator identification via **I2C bus + P1 port reads** with signature matching +- `FUN_CODE_14b9` -- Calibrated delay function using CPUCS clock divider awareness + +**FW2 only:** +- `FUN_CODE_0ffc` -- Stores a parameter into BANK3_R1 (register save) +- `FUN_CODE_1288` -- New hardware initialization (loads from external memory at e080-e08e) + +**FW3 only:** +- `FUN_CODE_0706` -- Memory write dispatcher that handles 3 addressing modes (XDATA, IDATA, direct) +- `FUN_CODE_1288` -- Same as FW2 +- Uses `FUN_CODE_1ffc` (at a different address from FW2's 0x1ffd) + +--- + +## 2. Main Entry Comparison (CODE:170D) + +### Identical Across All Three: +- IRAM clear loop (0x7F down to 0x00) +- Init table parsing from CODE:0B88 +- Bit config table reference at CODE:1740 +- Final call to FUN_CODE_0800 + +### One Key Difference -- Stack Pointer: + +| Variant | SP Value | +|---------|----------| +| FW1 | SP = 0x50 | +| FW2 | SP = 0x50 | +| FW3 | SP = **0x52** | + +FW3 sets `SP = 0x52`, requiring 2 more bytes of IRAM for stack usage. This indicates FW3 uses additional internal RAM locations (0x51-0x52) for state variables that FW1/FW2 don't need, pushing the stack higher. + +**Confirmed**: FW3 uses `DAT_INTMEM_51` as a hardware status register throughout its code, while FW1/FW2 use `DAT_INTMEM_4f` for the same purpose. The 2-byte difference in SP exactly accounts for this. + +### Memory at CODE:170D: +``` +FW1: 787f e4f6 d8fd 7581 50 02 1754 020800 e4 +FW2: 787f e4f6 d8fd 7581 50 02 1754 020800 e4 (identical to FW1) +FW3: 787f e4f6 d8fd 7581 52 02 1754 020800 e4 (byte at 0x1714 = 0x52 vs 0x50) +``` + +--- + +## 3. Key Function Decompilation Comparison + +### 3.1 FUN_CODE_0800 (Main Loop) -- All at Same Address + +**Structure identical across all three:** +1. Clear INTMEM locations 0x22-0x2D, 0x32-0x35 +2. Clear bit flags _1_0 and _0_6 +3. Call hardware init (address differs) +4. Set up BANK register pairs (XDATA pointers): all use the same values (0x0E00, 0x0E12, 0x0E1C, 0x0E54, 0x0E8C, 0x0EE8) +5. Call FUN_CODE_072a with init params +6. Memory copy loop (0x80 bytes at offset 0x0E00) +7. Retry loop with FUN_CODE_1799 (20 attempts) +8. Retry loop with EEPROM checksum function (20 attempts) +9. Check demod type byte at offset +10 from BANK1_R4/R5 (== 0x03 -> set flag) +10. Enable interrupts, enter main event loop + +**Differences in called function addresses (relocated, not functionally different):** + +| Call Purpose | FW1 | FW2 | FW3 | +|-------------|------|------|------| +| Hardware init | FUN_CODE_11ab | FUN_CODE_1288 | FUN_CODE_1288 | +| EEPROM checksum | FUN_CODE_1ca0 | FUN_CODE_1cff | FUN_CODE_1ca1 | +| USB setup | FUN_CODE_2031 | FUN_CODE_206c | FUN_CODE_206c | +| Main loop poll | FUN_CODE_21ec | FUN_CODE_2227 | FUN_CODE_2227 | +| Interrupt check | FUN_CODE_2445 | FUN_CODE_247c | FUN_CODE_2473 | +| Status check | FUN_CODE_2189 | FUN_CODE_21c4 | FUN_CODE_21c4 | +| Buffer flush | FUN_CODE_20b9 | FUN_CODE_20f4 | FUN_CODE_20f4 | +| EP complete | FUN_CODE_2447 | FUN_CODE_247e | FUN_CODE_2475 | + +### 3.2 Hardware Init Function (FW1: 0x11ab, FW2/FW3: 0x1288) + +**Functionally identical** across all three except: + +| Parameter | FW1 | FW2 | FW3 | +|-----------|------|------|------| +| P0 init value | **0xa4** | **0xa4** | **0xa0** | +| Status register | DAT_INTMEM_4f | DAT_INTMEM_4f | **DAT_INTMEM_51** | +| Sub-init call 1 | FUN_CODE_1c44 | FUN_CODE_1ca3 | FUN_CODE_1c45 | +| Sub-init call 2 | FUN_CODE_1000 | FUN_CODE_10dd | FUN_CODE_10dd | +| I2C/bus init | FUN_CODE_213b | FUN_CODE_2176 | FUN_CODE_2176 | +| Tuner init | FUN_CODE_1be6 | FUN_CODE_1c45 | FUN_CODE_1cf7 | + +**P0 = 0xa4 vs 0xa0**: P0 on the FX2 controls GPIO port 0. The difference is bit 2: +- FW1/FW2: P0 bit 2 = 1 (0xa4 = 1010 0100) +- FW3: P0 bit 2 = 0 (0xa0 = 1010 0000) + +This suggests different default GPIO state for a control signal, likely related to the demodulator interface mode or reset polarity. + +### 3.3 FUN_CODE_0eea -- The Most Revealing Difference + +**FW1**: This is a standard **I2C master transfer** function: +- Uses `FUN_CODE_23ae` (I2C START), `FUN_CODE_23ee` (I2C byte write), `FUN_CODE_23d0` (I2C address) +- Reads back via `FUN_CODE_2164` +- Standard I2C retry with NACK detection + +**FW2**: This is a **parallel bus read with demodulator handshake**: +- Reads demod type from address table (BANK1_R4/R5 + offset) +- Uses `FUN_CODE_11b6` for demod selection +- Toggles P0 bits 6/7 for bus control (P0.6 = chip select, P0.7 = read strobe) +- Reads data from P1 port (parallel data bus) +- Checks P1 ^ 0x1D (signature) then reads P1 for device ID +- Matches device IDs: 0xC5/0xD5 (for type 3), 0x5A (type 4), 0x5B (type 5), 0x5C (type 6) +- Controls P3 bits for demod power/reset + +**FW3**: Similar parallel bus read as FW2 but with **different timing and bus protocol**: +- Sets P0 | 0x80 once at start (not per-iteration like FW2) +- Uses `DAT_INTMEM_3f` and `DAT_INTMEM_40` as OR-accumulators for P1 reads +- Two separate P1 reads per cycle: one with P0.6 high, one with P0.6 low +- Calls `FUN_CODE_1b2a` with 3 parameters (accumulated OR values) vs FW2's 2 parameters +- Uses `P0 | 0x44` and `P0 & 0xBF` toggle pattern (vs FW2's different bit dance) + +### 3.4 Vendor Command Handler (FUN_CODE_034e) + +**Structurally identical** across all three -- same switch/case table structure at CODE:035e. + +Key differences: + +| Feature | FW1 | FW2 | FW3 | +|---------|------|------|------| +| Case 0x35f/0x427 call | FUN_CODE_0ffe (nop) | FUN_CODE_1ffd | FUN_CODE_0ffe (nop) | +| Case 0x361 call | FUN_CODE_2441 | FUN_CODE_2478 | FUN_CODE_246f | +| Case 0x365 call | FUN_CODE_2443 | FUN_CODE_247a | FUN_CODE_2471 | +| Case 0x36f call | FUN_CODE_2357 | FUN_CODE_2392 | FUN_CODE_2392 | +| Case 0x371 call | FUN_CODE_243d | FUN_CODE_0ffc | FUN_CODE_1ffc | +| Case 0x373/0x3ff call | FUN_CODE_2309 | FUN_CODE_2344 | FUN_CODE_2344 | +| Case 0xf0 indirect call | func_0x231e | func_0x2359 | func_0x2359 | +| Case 0x39d return | func_0x06e4 | DAT=0x0 | DAT=0x0 | +| Case 0x3d1 call | FUN_CODE_2110 | FUN_CODE_214b | FUN_CODE_214b | +| Case 0x3d3 behavior | TR2 timer check | OR operation | OR operation | +| Case 0x405 behavior | Goto LAB_05db | Conditional branch | Conditional branch | +| Case 0x421 behavior | Simple check | Extra P2_1, RL A logic | Extra P2_1, RL A logic | + +**FW1's unique case 0x3d3**: Checks Timer 2 Run flag (TR2) -- this is used for I2C bus timeout recovery, consistent with FW1 being I2C-based. + +**FW2/FW3's unique case 0x421-0x423**: Includes a rotate-left and P2.1 write -- this is a parallel bus data direction control, consistent with the external demodulator interface. + +--- + +## 4. Memory Comparison at Key Offsets + +### CODE:0000-0x000F (Reset Vector) +``` +FW1: 02170d 753728 e53760 1b7ffc 7e7f12 22 +FW2: 02170d 753728 e53760 1b7ffc 7e7f12 22 +FW3: 02170d 753728 e53760 1b7ffc 7e7f12 22 +ALL IDENTICAL -- LJMP 0x170D, then INT0 vector handler +``` + +### CODE:0B88-0x0B9F (Init Table Start) +``` +FW1: 41e0b6 626033 e0c609 070939 4f0000 000000 000000 000000 +FW2: 41e0b6 626033 e0c609 070939 4f0000 000000 000000 000000 +FW3: 41e0b6 626033 e0c609 070939 4f0000 000000 000000 000000 +ALL IDENTICAL -- Same register/SFR initialization table +``` + +### CODE:1500 (Thunk/INT Vector Target) +``` +FW1: 02 2252 00 02 22dd 00 02 22c7 00 02 226a 00 +FW2: 02 228d 00 02 2318 00 02 2302 00 02 22a5 00 +FW3: 02 228d 00 02 2318 00 02 2302 00 02 22a5 00 +``` +FW1 jumps to different addresses than FW2/FW3. **FW2 and FW3 are IDENTICAL here** -- their interrupt handlers are at the same addresses. + +### CODE:1740-0x174F (Bit Config Table) +``` +FW1: 4004 f456 8001 46f6 dfe4 800b 0102 0408 +FW2: 4004 f456 8001 46f6 dfe4 800b 0102 0408 +FW3: 4004 f456 8001 46f6 dfe4 800b 0102 0408 +ALL IDENTICAL -- Same bit manipulation lookup table +``` + +### CODE:0800 (Main Loop Start) +``` +FW1: e4f52d...c208c206 12 11ab 750c0e... +FW2: e4f52d...c208c206 12 1288 750c0e... +FW3: e4f52d...c208c206 12 1288 750c0e... +``` +Only difference: the LCALL target (FW1: 0x11ab, FW2/FW3: 0x1288). + +### CODE:06D9 (Utility Functions) +``` +FW1: bb010c e58229 f582e5 833af5 83e022 5006e9 2582f8 e622 +FW2: bb010c e58229 f582e5 833af5 83e022 5006e9 2582f8 e622 +FW3: bb010c e58229 f582e5 833af5 83e022 5006e9 2582f8 e622 +ALL IDENTICAL -- Generic memory access utilities shared by all +``` + +### CODE:0EEA (Critical Divergence Point) +``` +FW1: 8f44 8c45 8d46 8b47 754a14 e544 b451... (I2C transfer params in registers) +FW2: 753e14 e50d 240a f582 e435 0cf5 83e0... (reads from DPTR+offset table) +FW3: 753e14 e4f5 3ff5 40 e50d 240a f582... (similar to FW2 + accumulator init) +``` +This confirms: FW1's FUN_CODE_0eea is a completely different function (I2C master) than FW2/FW3's (parallel bus demod interface). + +--- + +## 5. FW2 vs FW3 Specific Differences + +FW2 and FW3 are the most similar pair (1,525 bytes different). Key differences: + +| Feature | FW2 | FW3 | +|---------|------|------| +| Stack Pointer | SP = 0x50 | SP = 0x52 | +| Status register | DAT_INTMEM_4f | DAT_INTMEM_51 | +| P0 init | 0xa4 | 0xa0 (bit 2 different) | +| FUN_CODE_0eea bus protocol | Single-phase P1 read | Dual-phase P1 read with OR accumulation | +| I2C buffer addresses | DAT_INTMEM_48/49 | DAT_INTMEM_4a/4b | +| Unique function | FUN_CODE_0ffc (register store) | FUN_CODE_0706 (multi-mode write) | +| P0 bus timing | P0 &= ~0x40; P0 |= 0x80 per iteration | P0 |= 0x80 once; P0 |= 0x44 / P0 &= ~0x40 per phase | +| Delay function address | FUN_CODE_1e92 | FUN_CODE_1e88 | + +### The P1 Read Difference is Critical + +**FW2** reads P1 once per bus cycle: +```c +uVar1 = P1; // Read with one bus state +P0 |= 0x40; // Then change control line +uVar2 = P1; // Read again +FUN_CODE_1b2a(uVar2, uVar1); // Process both samples +``` + +**FW3** reads P1 in two phases and OR-accumulates: +```c +DAT_INTMEM_3f = 0; DAT_INTMEM_40 = 0; // Clear accumulators +// Phase 1: P0.6 high +P0 |= 0x44; +bVar2 = P1; +DAT_INTMEM_3f |= bVar2; // OR-accumulate +// Phase 2: P0.6 low +P0 &= ~0x40; +bVar2 = P1; +DAT_INTMEM_40 |= bVar2; // OR-accumulate +FUN_CODE_1b2a(0, DAT_INTMEM_3f, DAT_INTMEM_40); // Process accumulated +``` + +This OR-accumulation pattern in FW3 suggests dealing with a bus that may have metastable signals or requires multiple samples, characteristic of **a different demodulator chip with different bus timing**. + +--- + +## 6. Hypothesis: What Distinguishes Each Variant + +### FW1 (v2.13.1) -- Original I2C-Connected Demodulator Hardware + +**Target**: First-generation SkyWalker-1 PCB with an **I2C-connected demodulator** (likely a Conexant/Zarlink integrated tuner+demod). + +Evidence: +- Uses standard I2C protocol functions (START, STOP, ACK/NACK, byte read/write) +- FUN_CODE_0fc7: I2C write retry loop +- FUN_CODE_1405: Reads demodulator identification via I2C + P1 GPIO, checks device signatures: + - Type 3: P1 == 0xA5 or 0xB5 + - Type 4: P1 == 0x5A + - Type 5: P1 == 0x5B + - Type 6: P1 == 0x5C +- Has timer-based I2C timeout (TR2 check in vendor handler) +- SP=0x50, fewer IRAM state variables needed +- `func_0x06e4` called for unknown vendor commands (older error path) + +**Likely demodulator**: An I2C-bus demodulator supporting DVB-S/DCII/DSS, with the FX2 as USB bridge. The type codes 3-6 likely correspond to different supported modulation modes or demod silicon revisions. + +### FW2 (v2.13.2) -- Second-Generation Parallel-Bus Demodulator + +**Target**: Revised SkyWalker-1 PCB with a **parallel-bus connected demodulator** (likely a different demod chip or a custom FPGA/ASIC). + +Evidence: +- FUN_CODE_0eea: Parallel bus read using P0 GPIO for control (CS, RD strobe) and P1 for data +- FUN_CODE_10dd: Copies configuration from external memory (e080-e08e) into demod registers (e6c0-e6cd) -- reads 15 configuration bytes from what appears to be EEPROM/flash config area +- Reads same device signatures but via parallel bus (P1 ^ 0x1D check, then P1 reads for 0xC5/0xD5/0x5A/0x5B/0x5C) +- P0 = 0xa4 (bit 2 set = specific bus mode select) +- SP = 0x50 +- Extra vendor command paths for parallel data direction (P2.1 control in case 0x421/0x423) +- Uses FUN_CODE_14e2: Busy-wait on e678 bit 6 (demod ready flag) with 0xFFFF timeout counter + +**Likely demodulator**: A parallel-bus demodulator with 8-bit data port on P1, active-low chip select and read strobe on P0. The external config block (e080-e08e) stores per-unit calibration/tuning data. + +### FW3 (v2.13.3) -- Third-Generation with Enhanced Bus Protocol + +**Target**: Further revised PCB with the **same parallel-bus demodulator as FW2** but with a **different bus interface revision** or a variant chip that requires modified timing. + +Evidence: +- Same demod configuration loading as FW2 (FUN_CODE_10dd identical) +- Same parallel bus architecture but with dual-phase reading and OR-accumulation +- P0 = 0xa0 (bit 2 clear = different bus mode or reset polarity) +- SP = 0x52 (2 more IRAM bytes: status register moved from 0x4F to 0x51) +- FUN_CODE_0706 (unique): Multi-mode memory write supporting XDATA, IDATA, and direct addressing -- suggests the demod communicates through multiple address spaces +- The OR-accumulation of P1 reads suggests either: + - A demodulator with open-drain outputs requiring multiple read cycles + - Bus settling time issues on the newer PCB layout + - A chip variant that serializes data across multiple bus phases + +--- + +## 7. Summary Table + +| Aspect | FW1 (v2.13.1) | FW2 (v2.13.2) | FW3 (v2.13.3) | +|--------|---------------|---------------|---------------| +| **Demod interface** | I2C bus | Parallel bus (P0/P1) | Parallel bus (enhanced) | +| **Bus protocol** | I2C START/STOP/ACK | Single-phase P1 read | Dual-phase P1 read + OR accumulate | +| **Stack pointer** | 0x50 | 0x50 | 0x52 | +| **P0 init** | 0xa4 | 0xa4 | 0xa0 | +| **Status register** | INTMEM 0x4F | INTMEM 0x4F | INTMEM 0x51 | +| **Config source** | Hardcoded | External (e080-e08e) | External (e080-e08e) | +| **Demod types supported** | 3-6 (via I2C) | 3-6 (via parallel) | 3-6 (via parallel) | +| **Binary distance from FW1** | -- | 3,993 bytes | 3,789 bytes | +| **Binary distance from FW2** | 3,993 bytes | -- | 1,525 bytes | + +## 8. Conclusion + +The three v2.13 firmware sub-variants represent an evolutionary progression of the SkyWalker-1 hardware: + +1. **v2.13.1 (FW1)**: Original design with I2C-connected demodulator. The FX2 communicates with the demod entirely through I2C, using standard master-mode transactions. This is the simplest interface but limited in bandwidth. + +2. **v2.13.2 (FW2)**: Redesigned with a parallel-bus demodulator. The demod data port is connected directly to FX2's P1, with P0 bits used for bus control signals (chip select, read/write strobes). Configuration data is loaded from an external EEPROM area. This provides higher throughput for TS data transfer. + +3. **v2.13.3 (FW3)**: Refinement of the FW2 design, likely for a newer demod silicon revision or PCB layout. Uses dual-phase bus reads with signal accumulation, different GPIO defaults, and additional IRAM for state tracking. The OR-accumulation pattern suggests dealing with bus signal integrity improvements. + +The updater program's format string `"FW 2.13.%i"` and its selection logic presumably check the hardware revision (likely via a GPIO strap or I2C ID read) to determine which of the three firmware images to flash. + +All three variants support the same modulation types (DVB-S/QPSK, Turbo QPSK/8PSK, DCII, DSS) -- the demod type codes 3-6 appear in all variants. The differences are purely about the hardware interface, not the feature set. diff --git a/firmware/dscr.a51 b/firmware/dscr.a51 index d629fe6..95f25ac 100644 --- a/firmware/dscr.a51 +++ b/firmware/dscr.a51 @@ -1,230 +1,230 @@ -; USB Descriptors for Genpix SkyWalker-1 Custom Firmware -; -; VID=0x09C0, PID=0x0203 -; Single interface, EP2 IN bulk 512-byte for TS data - -.module DEV_DSCR - -; descriptor types -DSCR_DEVICE_TYPE=1 -DSCR_CONFIG_TYPE=2 -DSCR_STRING_TYPE=3 -DSCR_INTERFACE_TYPE=4 -DSCR_ENDPOINT_TYPE=5 -DSCR_DEVQUAL_TYPE=6 - -; for the repeating interfaces -DSCR_INTERFACE_LEN=9 -DSCR_ENDPOINT_LEN=7 - -; endpoint types -ENDPOINT_TYPE_CONTROL=0 -ENDPOINT_TYPE_ISO=1 -ENDPOINT_TYPE_BULK=2 -ENDPOINT_TYPE_INT=3 - - .globl _dev_dscr, _dev_qual_dscr, _highspd_dscr, _fullspd_dscr, _dev_strings, _dev_strings_end - .area DSCR_AREA (CODE) - -_dev_dscr: - .db dev_dscr_end-_dev_dscr ; len - .db DSCR_DEVICE_TYPE ; type - .dw 0x0002 ; usb 2.0 - .db 0xff ; class (vendor specific) - .db 0xff ; subclass (vendor specific) - .db 0xff ; protocol (vendor specific) - .db 64 ; packet size (ep0) - .dw 0xC009 ; vendor id 0x09C0 (byte-swapped) - .dw 0x0302 ; product id 0x0203 (byte-swapped) - .dw 0x0100 ; version id - .db 1 ; manufacturer str idx - .db 2 ; product str idx - .db 3 ; serial str idx - .db 1 ; n configurations -dev_dscr_end: - -_dev_qual_dscr: - .db dev_qualdscr_end-_dev_qual_dscr - .db DSCR_DEVQUAL_TYPE - .dw 0x0002 ; usb 2.0 - .db 0xff - .db 0xff - .db 0xff - .db 64 ; max packet - .db 1 ; n configs - .db 0 ; extra reserved byte -dev_qualdscr_end: - -; --- High speed configuration descriptor --- -_highspd_dscr: - .db highspd_dscr_end-_highspd_dscr - .db DSCR_CONFIG_TYPE - .db (highspd_dscr_realend-_highspd_dscr) % 256 ; total length lsb - .db (highspd_dscr_realend-_highspd_dscr) / 256 ; total length msb - .db 1 ; n interfaces - .db 1 ; config number - .db 0 ; config string - .db 0x80 ; attrs = bus powered, no wakeup - .db 0xFA ; max power = 500mA (0xFA * 2 = 500) -highspd_dscr_end: - -; interface 0 - .db DSCR_INTERFACE_LEN - .db DSCR_INTERFACE_TYPE - .db 0 ; index - .db 0 ; alt setting idx - .db 1 ; n endpoints (EP2 IN only) - .db 0xff ; class (vendor specific) - .db 0xff - .db 0xff - .db 4 ; string index - -; endpoint 2 IN (0x82) -- MPEG-2 transport stream bulk - .db DSCR_ENDPOINT_LEN - .db DSCR_ENDPOINT_TYPE - .db 0x82 ; ep2 dir=IN and address - .db ENDPOINT_TYPE_BULK ; type - .db 0x00 ; max packet LSB - .db 0x02 ; max packet size=512 bytes - .db 0x00 ; polling interval - -highspd_dscr_realend: - - .even -; --- Full speed configuration descriptor --- -_fullspd_dscr: - .db fullspd_dscr_end-_fullspd_dscr - .db DSCR_CONFIG_TYPE - .db (fullspd_dscr_realend-_fullspd_dscr) % 256 ; total length lsb - .db (fullspd_dscr_realend-_fullspd_dscr) / 256 ; total length msb - .db 1 ; n interfaces - .db 1 ; config number - .db 0 ; config string - .db 0x80 ; attrs = bus powered, no wakeup - .db 0xFA ; max power = 500mA -fullspd_dscr_end: - -; interface 0 - .db DSCR_INTERFACE_LEN - .db DSCR_INTERFACE_TYPE - .db 0 ; index - .db 0 ; alt setting idx - .db 1 ; n endpoints - .db 0xff ; class (vendor specific) - .db 0xff - .db 0xff - .db 4 ; string index - -; endpoint 2 IN (0x82) -- bulk 64 byte at full speed - .db DSCR_ENDPOINT_LEN - .db DSCR_ENDPOINT_TYPE - .db 0x82 ; ep2 dir=IN and address - .db ENDPOINT_TYPE_BULK ; type - .db 0x40 ; max packet LSB = 64 - .db 0x00 ; max packet size MSB - .db 0x00 ; polling interval - -fullspd_dscr_realend: - -.even -_dev_strings: - -; string 0 -- language descriptor -_string0: - .db string0end-_string0 - .db DSCR_STRING_TYPE - .db 0x09, 0x04 ; English (US) -string0end: - -; string 1 -- manufacturer -_string1: - .db string1end-_string1 - .db DSCR_STRING_TYPE - .ascii 'G' - .db 0 - .ascii 'e' - .db 0 - .ascii 'n' - .db 0 - .ascii 'p' - .db 0 - .ascii 'i' - .db 0 - .ascii 'x' - .db 0 -string1end: - -; string 2 -- product -_string2: - .db string2end-_string2 - .db DSCR_STRING_TYPE - .ascii 'S' - .db 0 - .ascii 'k' - .db 0 - .ascii 'y' - .db 0 - .ascii 'W' - .db 0 - .ascii 'a' - .db 0 - .ascii 'l' - .db 0 - .ascii 'k' - .db 0 - .ascii 'e' - .db 0 - .ascii 'r' - .db 0 - .ascii '-' - .db 0 - .ascii '1' - .db 0 - .ascii ' ' - .db 0 - .ascii 'C' - .db 0 - .ascii 'u' - .db 0 - .ascii 's' - .db 0 - .ascii 't' - .db 0 - .ascii 'o' - .db 0 - .ascii 'm' - .db 0 -string2end: - -; string 3 -- serial number -_string3: - .db string3end-_string3 - .db DSCR_STRING_TYPE - .ascii '0' - .db 0 - .ascii '0' - .db 0 - .ascii '0' - .db 0 - .ascii '1' - .db 0 -string3end: - -; string 4 -- interface -_string4: - .db string4end-_string4 - .db DSCR_STRING_TYPE - .ascii 'D' - .db 0 - .ascii 'V' - .db 0 - .ascii 'B' - .db 0 - .ascii '-' - .db 0 - .ascii 'S' - .db 0 -string4end: - -_dev_strings_end: - .dw 0x0000 +; USB Descriptors for Genpix SkyWalker-1 Custom Firmware +; +; VID=0x09C0, PID=0x0203 +; Single interface, EP2 IN bulk 512-byte for TS data + +.module DEV_DSCR + +; descriptor types +DSCR_DEVICE_TYPE=1 +DSCR_CONFIG_TYPE=2 +DSCR_STRING_TYPE=3 +DSCR_INTERFACE_TYPE=4 +DSCR_ENDPOINT_TYPE=5 +DSCR_DEVQUAL_TYPE=6 + +; for the repeating interfaces +DSCR_INTERFACE_LEN=9 +DSCR_ENDPOINT_LEN=7 + +; endpoint types +ENDPOINT_TYPE_CONTROL=0 +ENDPOINT_TYPE_ISO=1 +ENDPOINT_TYPE_BULK=2 +ENDPOINT_TYPE_INT=3 + + .globl _dev_dscr, _dev_qual_dscr, _highspd_dscr, _fullspd_dscr, _dev_strings, _dev_strings_end + .area DSCR_AREA (CODE) + +_dev_dscr: + .db dev_dscr_end-_dev_dscr ; len + .db DSCR_DEVICE_TYPE ; type + .dw 0x0002 ; usb 2.0 + .db 0xff ; class (vendor specific) + .db 0xff ; subclass (vendor specific) + .db 0xff ; protocol (vendor specific) + .db 64 ; packet size (ep0) + .dw 0xC009 ; vendor id 0x09C0 (byte-swapped) + .dw 0x0302 ; product id 0x0203 (byte-swapped) + .dw 0x0100 ; version id + .db 1 ; manufacturer str idx + .db 2 ; product str idx + .db 3 ; serial str idx + .db 1 ; n configurations +dev_dscr_end: + +_dev_qual_dscr: + .db dev_qualdscr_end-_dev_qual_dscr + .db DSCR_DEVQUAL_TYPE + .dw 0x0002 ; usb 2.0 + .db 0xff + .db 0xff + .db 0xff + .db 64 ; max packet + .db 1 ; n configs + .db 0 ; extra reserved byte +dev_qualdscr_end: + +; --- High speed configuration descriptor --- +_highspd_dscr: + .db highspd_dscr_end-_highspd_dscr + .db DSCR_CONFIG_TYPE + .db (highspd_dscr_realend-_highspd_dscr) % 256 ; total length lsb + .db (highspd_dscr_realend-_highspd_dscr) / 256 ; total length msb + .db 1 ; n interfaces + .db 1 ; config number + .db 0 ; config string + .db 0x80 ; attrs = bus powered, no wakeup + .db 0xFA ; max power = 500mA (0xFA * 2 = 500) +highspd_dscr_end: + +; interface 0 + .db DSCR_INTERFACE_LEN + .db DSCR_INTERFACE_TYPE + .db 0 ; index + .db 0 ; alt setting idx + .db 1 ; n endpoints (EP2 IN only) + .db 0xff ; class (vendor specific) + .db 0xff + .db 0xff + .db 4 ; string index + +; endpoint 2 IN (0x82) -- MPEG-2 transport stream bulk + .db DSCR_ENDPOINT_LEN + .db DSCR_ENDPOINT_TYPE + .db 0x82 ; ep2 dir=IN and address + .db ENDPOINT_TYPE_BULK ; type + .db 0x00 ; max packet LSB + .db 0x02 ; max packet size=512 bytes + .db 0x00 ; polling interval + +highspd_dscr_realend: + + .even +; --- Full speed configuration descriptor --- +_fullspd_dscr: + .db fullspd_dscr_end-_fullspd_dscr + .db DSCR_CONFIG_TYPE + .db (fullspd_dscr_realend-_fullspd_dscr) % 256 ; total length lsb + .db (fullspd_dscr_realend-_fullspd_dscr) / 256 ; total length msb + .db 1 ; n interfaces + .db 1 ; config number + .db 0 ; config string + .db 0x80 ; attrs = bus powered, no wakeup + .db 0xFA ; max power = 500mA +fullspd_dscr_end: + +; interface 0 + .db DSCR_INTERFACE_LEN + .db DSCR_INTERFACE_TYPE + .db 0 ; index + .db 0 ; alt setting idx + .db 1 ; n endpoints + .db 0xff ; class (vendor specific) + .db 0xff + .db 0xff + .db 4 ; string index + +; endpoint 2 IN (0x82) -- bulk 64 byte at full speed + .db DSCR_ENDPOINT_LEN + .db DSCR_ENDPOINT_TYPE + .db 0x82 ; ep2 dir=IN and address + .db ENDPOINT_TYPE_BULK ; type + .db 0x40 ; max packet LSB = 64 + .db 0x00 ; max packet size MSB + .db 0x00 ; polling interval + +fullspd_dscr_realend: + +.even +_dev_strings: + +; string 0 -- language descriptor +_string0: + .db string0end-_string0 + .db DSCR_STRING_TYPE + .db 0x09, 0x04 ; English (US) +string0end: + +; string 1 -- manufacturer +_string1: + .db string1end-_string1 + .db DSCR_STRING_TYPE + .ascii 'G' + .db 0 + .ascii 'e' + .db 0 + .ascii 'n' + .db 0 + .ascii 'p' + .db 0 + .ascii 'i' + .db 0 + .ascii 'x' + .db 0 +string1end: + +; string 2 -- product +_string2: + .db string2end-_string2 + .db DSCR_STRING_TYPE + .ascii 'S' + .db 0 + .ascii 'k' + .db 0 + .ascii 'y' + .db 0 + .ascii 'W' + .db 0 + .ascii 'a' + .db 0 + .ascii 'l' + .db 0 + .ascii 'k' + .db 0 + .ascii 'e' + .db 0 + .ascii 'r' + .db 0 + .ascii '-' + .db 0 + .ascii '1' + .db 0 + .ascii ' ' + .db 0 + .ascii 'C' + .db 0 + .ascii 'u' + .db 0 + .ascii 's' + .db 0 + .ascii 't' + .db 0 + .ascii 'o' + .db 0 + .ascii 'm' + .db 0 +string2end: + +; string 3 -- serial number +_string3: + .db string3end-_string3 + .db DSCR_STRING_TYPE + .ascii '0' + .db 0 + .ascii '0' + .db 0 + .ascii '0' + .db 0 + .ascii '1' + .db 0 +string3end: + +; string 4 -- interface +_string4: + .db string4end-_string4 + .db DSCR_STRING_TYPE + .ascii 'D' + .db 0 + .ascii 'V' + .db 0 + .ascii 'B' + .db 0 + .ascii '-' + .db 0 + .ascii 'S' + .db 0 +string4end: + +_dev_strings_end: + .dw 0x0000 diff --git a/gp8psk-driver-analysis.md b/gp8psk-driver-analysis.md index acb10f2..660128e 100644 --- a/gp8psk-driver-analysis.md +++ b/gp8psk-driver-analysis.md @@ -1,280 +1,280 @@ -# Genpix SkyWalker-1 DVB-S Linux Kernel Driver Analysis - -## Overview - -The Genpix GP8PSK driver is a Linux kernel DVB-USB compliant driver (found at `drivers/media/usb/dvb-usb/gp8psk.c`) that supports multiple Genpix satellite receiver models. The driver communicates with a Cypress FX2 microcontroller via USB vendor control requests to manage DVB-S satellite reception including demodulation, LNB power control, DiSEqC switching, and signal monitoring. - ---- - -## 1. VENDOR USB CONTROL COMMANDS (0x80-0x9D) - -All vendor commands use USB control transfers with the following pattern: -- **USB Type**: `USB_TYPE_VENDOR` (vendor-specific request) -- **Direction**: `USB_DIR_IN` (device-to-host) or `USB_DIR_OUT` (host-to-device) -- **Timeout**: 2000ms -- **Retry Logic**: Up to 3 attempts for IN operations if partial data received -- **Data Buffer**: 80 bytes maximum (kernel driver state structure) - -### Complete Vendor Command Map - -| Cmd | Name | Direction | wValue | wIndex | wLength | Purpose | -|-----|------|-----------|--------|--------|---------|---------| -| 0x80 | **GET_8PSK_CONFIG** | IN | 0x0000 | 0x0000 | 1 byte | Read device configuration status byte | -| 0x81 | SET_8PSK_CONFIG | OUT | varies | 0x0000 | 0 | Set config (STALL - not implemented) | -| 0x82 | (reserved) | -- | -- | -- | -- | STALL - not used | -| 0x83 | **I2C_WRITE** | OUT | dev_addr | reg_addr | N bytes | Write to I2C device (BCM4500 demod) | -| 0x84 | **I2C_READ** | IN | dev_addr | reg_addr | N bytes | Read from I2C device (BCM4500 demod) | -| 0x85 | **ARM_TRANSFER** | OUT | onoff (0/1) | 0x0000 | 0 | Start/stop MPEG-2 stream transfer | -| 0x86 | **TUNE_8PSK** | OUT | 0x0000 | 0x0000 | 10 bytes | Set tuning parameters | -| 0x87 | **GET_SIGNAL_STRENGTH** | IN | 0x0000 | 0x0000 | 6 bytes | Read SNR (signal quality) | -| 0x88 | **LOAD_BCM4500** | OUT | 1 (start) | 0x0000 | 0 | Initiate BCM4500 firmware download | -| 0x89 | **BOOT_8PSK** | IN | onoff (0/1) | 0x0000 | 1 byte | Power on/off 8PSK demodulator | -| 0x8A | **START_INTERSIL** | IN | onoff (0/1) | 0x0000 | 1 byte | Enable/disable LNB power supply | -| 0x8B | **SET_LNB_VOLTAGE** | OUT | voltage (0/1) | 0x0000 | 0 | Set LNB to 13V (0) or 18V (1) | -| 0x8C | **SET_22KHZ_TONE** | OUT | onoff (0/1) | 0x0000 | 0 | Enable/disable 22 kHz DiSEqC tone | -| 0x8D | **SEND_DISEQC_COMMAND** | OUT | msg[0] | 0x0000 | len | Send DiSEqC message to dish switch | -| 0x8E | SET_DVB_MODE | OUT | 1 | 0x0000 | 0 | Enable DVB-S mode (STALL on some revisions) | -| 0x8F | (unknown) | -- | -- | -- | -- | Unknown/internal use | -| 0x90 | **GET_SIGNAL_LOCK** | IN | 0x0000 | 0x0000 | 1 byte | Read signal lock status bit | -| 0x91-0x98 | (internal use) | -- | -- | -- | -- | I2C/diagnostic reads | -| 0x99 | **GET_DEMOD_STATUS** (v2.13+) | IN | 0x0000 | 0x0000 | 1 byte | Read BCM4500 register 0xF9 via I2C | -| 0x9A | **INIT_DEMOD** (v2.13+) | OUT | 0x0000 | 0x0000 | 0 | Trigger demodulator re-init (up to 3 attempts) | -| 0x9B | (reserved) | -- | -- | -- | -- | STALL - not used | -| 0x9C | **DELAY_COMMAND** (v2.13+) | OUT | delay_param | 0x0000 | 0 | Perform tuning/acquisition delay with polling | -| 0x9D | **CW3K_INIT** | OUT | onoff (0/1) | 0x0000 | 0 | Initialize SkyWalker CW3K model | - -### Configuration Status Byte (GET_8PSK_CONFIG - 0x80) - -The returned byte is a bit-mapped status register: - -``` -Bit 0 (0x01): bm8pskStarted - Device booted and running -Bit 1 (0x02): bm8pskFW_Loaded - BCM4500 firmware loaded -Bit 2 (0x04): bmIntersilOn - LNB power supply enabled -Bit 3 (0x08): bmDVBmode - DVB mode enabled -Bit 4 (0x10): bm22kHz - 22 kHz tone active -Bit 5 (0x20): bmSEL18V - 18V LNB voltage selected (else 13V) -Bit 6 (0x40): bmDCtuned - DC offset tuning complete -Bit 7 (0x80): bmArmed - MPEG-2 stream transfer armed -``` - ---- - -## 2. FIRMWARE LOADING SEQUENCE - -### Two-Stage Firmware Loading - -The device requires **two separate firmware files**: - -#### Stage 1: FX2 RAM Code (Bootloader Phase) -- **File**: `dvb-usb-gp8psk-01.fw` -- **Purpose**: Cypress FX2 microcontroller firmware -- **When**: Loaded automatically by DVB-USB framework during device enumeration (cold-to-warm boot transition) -- **How**: Standard USB firmware upload via control endpoint (0xA0 vendor request to CPUCS) -- **Device Mode**: Cold boot (VID 0x09C0, PID 0x0200) - -#### Stage 2: BCM4500 Demodulator Firmware (Tuner Phase) -- **File**: `dvb-usb-gp8psk-02.fw` -- **Purpose**: Broadcom BCM4500 DVB-S demodulator chip firmware -- **When**: Loaded after device power-on via LOAD_BCM4500 command -- **How**: Custom binary protocol via USB control transfers -- **Conditions**: Only for Rev.1 Warm devices; Rev.2 and SkyWalker have firmware burned in ROM - -### BCM4500 Firmware Loading Protocol - -```c -// Step 1: Initiate load -gp8psk_usb_out_op(device, LOAD_BCM4500, 1, 0, NULL, 0); // 0x88 cmd, wValue=1 - -// Step 2: Download firmware chunks -// Format: [chunk_length] [data...] repeated, terminated by 0xFF -ptr = fw_data; -while (ptr[0] != 0xFF) { - chunk_size = ptr[0] + 4; // length byte + 3 extra bytes - if (chunk_size > 64) ERROR("chunk too large"); - buf = copy chunk_size bytes from ptr; - usb_control_msg(device, USB_SNDCTRLPIPE, 0, ..., buf, chunk_size, 2000ms); - ptr += chunk_size; -} -``` - -### Boot Sequence Flow (Power-On) - -``` -1. Read device config (GET_8PSK_CONFIG - 0x80) - +-- Check bit 0: Device started? - -2. If not started: - +-- Boot device (BOOT_8PSK - 0x89, wValue=1) - +-- Get firmware version (via 0x0B: FW_VERSION_READ) - -3. If firmware not loaded (check bit 1): - +-- Load BCM4500 firmware (LOAD_BCM4500 - 0x88) - -4. If LNB not powered (check bit 2): - +-- Enable LNB supply (START_INTERSIL - 0x8A, wValue=1) - -5. Set DVB mode (on some revisions): - +-- SET_DVB_MODE - 0x8E, wValue=1 - -6. Abort any pending MPEG transfer: - +-- ARM_TRANSFER - 0x85, wValue=0 (cancel) - -7. Ready for tuning/reception -``` - ---- - -## 3. TUNING AND DEMODULATION FLOW - -### Frequency Tuning (TUNE_8PSK - 0x86) - -The tuning command transmits a 10-byte parameter structure: - -``` -Bytes 0-3: Symbol Rate (Little-Endian 32-bit, in sps) -Bytes 4-7: Tuner Frequency (Little-Endian 32-bit, in kHz) -Byte 8: Modulation Type (see table below) -Byte 9: Inner FEC Rate / reserved -``` - -### Modulation Types - -``` -ADV_MOD_DVB_QPSK = 0 // DVB-S standard QPSK -ADV_MOD_TURBO_QPSK = 1 // Turbo QPSK -ADV_MOD_TURBO_8PSK = 2 // Turbo 8PSK (Trellis) -ADV_MOD_TURBO_16QAM = 3 // Turbo 16QAM -ADV_MOD_DCII_C_QPSK = 4 // Digicipher II Combo -ADV_MOD_DCII_I_QPSK = 5 // Digicipher II I-stream (split) -ADV_MOD_DCII_Q_QPSK = 6 // Digicipher II Q-stream (split) -ADV_MOD_DCII_C_OQPSK = 7 // Digicipher II offset QPSK -ADV_MOD_DSS_QPSK = 8 // DSS/DIRECTV QPSK -ADV_MOD_DVB_BPSK = 9 // DVB-S BPSK -``` - -### Complete Tuning Sequence - -``` -1. Configure LNB voltage based on polarization: - H/CIRCULAR_L -> 18V (SET_LNB_VOLTAGE - 0x8B, wValue=1) - V/CIRCULAR_R -> 13V (SET_LNB_VOLTAGE - 0x8B, wValue=0) - -2. Set 22 kHz tone: - SET_22KHZ_TONE - 0x8C, wValue=0/1 - -3. Send DiSEqC switch command if needed (see section 4) - -4. Send tuning command: - TUNE_8PSK - 0x86, 10-byte parameter buffer - -5. Poll for lock: - GET_SIGNAL_LOCK - 0x90 (returns 1 byte, non-zero = locked) -``` - -### Signal Quality Monitoring - -**GET_SIGNAL_STRENGTH (0x87)** returns 6-byte buffer: -``` -Bytes 0-1: SNR value (LE 16-bit, dBu*256 units) -Bytes 2-5: Reserved/diagnostics -``` - -SNR scaling: `snr * 17` maps to 0-65535 (100% at SNR >= 0x0F00) - ---- - -## 4. DiSEqC COMMAND HANDLING (0x8D) - -### Tone Burst (Mini DiSEqC) -```c -// For legacy 2-way switches -gp8psk_usb_out_op(device, SEND_DISEQC_COMMAND, - burst_value, // SEC_MINI_A=0x00 or SEC_MINI_B=0x01 - 0, NULL, 0); -``` - -### Full DiSEqC Message (3-6 bytes) -```c -// Standard DiSEqC 1.0/1.1/1.2 messages -gp8psk_usb_out_op(device, SEND_DISEQC_COMMAND, - msg[0], // Framing byte as wValue - 0, - msg, // Full message buffer - msg_len); // 3-6 bytes -``` - -Common framing bytes: 0xE0 (broadcast, no reply), 0xE1 (addressed, no reply) - ---- - -## 5. DEVICE TABLE AND USB IDS - -### Kernel Module Aliases (Linux 6.16.5) - -``` -usb:v09C0p0200 - Rev.1 Cold boot (requires FX2 firmware upload) -usb:v09C0p0201 - Rev.1 Warm (requires BCM4500 FW via 0x88) -usb:v09C0p0202 - Rev.2 (BCM4500 in ROM) -usb:v09C0p0203 - SkyWalker-1 (PID confirmed on actual hardware) -usb:v09C0p0204 - SkyWalker-1 (alternate revision) -usb:v09C0p0206 - SkyWalker CW3K (requires CW3K_INIT 0x9D) -``` - -Note: PID 0x0205 (SkyWalker-2) is absent from the 6.16.5 kernel build. -Note: PID 0x0203 was not in earlier kernel versions (e.g., v6.6.1). - -### Device Properties - -```c -gp8psk_properties { - usb_ctrl = CYPRESS_FX2; - firmware = "dvb-usb-gp8psk-01.fw"; - num_adapters = 1; - endpoint = 0x82 (IN bulk); - stream = USB_BULK; - count = 7 URBs; - buffersize = 8192 bytes per URB; - generic_bulk_ctrl_endpoint = 0x01; -} -``` - ---- - -## 6. CORRELATION: KERNEL DRIVER vs FIRMWARE ANALYSIS - -### Commands Used by Kernel Driver - -| Command | Driver Uses | v2.06 FW | v2.13 FW | Rev.2 FW | -|---------|-------------|----------|----------|----------| -| 0x80 GET_8PSK_CONFIG | Boot check | OK | OK | OK | -| 0x83 I2C_WRITE | BCM4500 reg writes | OK | OK | OK | -| 0x84 I2C_READ | BCM4500 reg reads | OK | OK | OK | -| 0x85 ARM_TRANSFER | Stream start/stop | OK | OK | OK | -| 0x86 TUNE_8PSK | Frequency tuning | OK | OK | OK | -| 0x87 GET_SIGNAL_STRENGTH | SNR readback | OK | Changed | OK | -| 0x88 LOAD_BCM4500 | BCM4500 FW load | **STALL** | **STALL** | **STALL** | -| 0x89 BOOT_8PSK | Power on/off | OK | OK | OK | -| 0x8A START_INTERSIL | LNB power | OK | OK | OK | -| 0x8B SET_LNB_VOLTAGE | 13V/18V | OK | OK | OK | -| 0x8C SET_22KHZ_TONE | Tone control | OK | OK | OK | -| 0x8D SEND_DISEQC | DiSEqC messages | OK (GPIO) | OK (I2C) | OK | -| 0x90 GET_SIGNAL_LOCK | Lock status | OK | OK | OK | -| 0x9D CW3K_INIT | CW3K only | N/A | v2.13 only | N/A | - -### Key Finding: LOAD_BCM4500 (0x88) STALLs - -Command 0x88 routes to the STALL handler in all extracted firmware versions. -The kernel driver only sends this command for Rev.1 Warm (PID 0x0201) devices, -after checking that `bm8pskFW_Loaded` (bit 1) is NOT set. On Rev.2/SkyWalker -hardware, this bit is already set at boot (BCM4500 firmware in ROM), so the -driver never attempts the load. The STALL is a safety net. - ---- - -## Sources - -- Linux kernel 6.16.5 `dvb-usb-gp8psk` module (installed on analysis system) -- Linux kernel source: `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h`, `gp8psk-fe.c` -- Windows BDA driver source: `SkyWalker1_Final_Release/Include/SkyWalker1Control.h` -- Firmware reverse engineering via Ghidra (ports 8193-8197) +# Genpix SkyWalker-1 DVB-S Linux Kernel Driver Analysis + +## Overview + +The Genpix GP8PSK driver is a Linux kernel DVB-USB compliant driver (found at `drivers/media/usb/dvb-usb/gp8psk.c`) that supports multiple Genpix satellite receiver models. The driver communicates with a Cypress FX2 microcontroller via USB vendor control requests to manage DVB-S satellite reception including demodulation, LNB power control, DiSEqC switching, and signal monitoring. + +--- + +## 1. VENDOR USB CONTROL COMMANDS (0x80-0x9D) + +All vendor commands use USB control transfers with the following pattern: +- **USB Type**: `USB_TYPE_VENDOR` (vendor-specific request) +- **Direction**: `USB_DIR_IN` (device-to-host) or `USB_DIR_OUT` (host-to-device) +- **Timeout**: 2000ms +- **Retry Logic**: Up to 3 attempts for IN operations if partial data received +- **Data Buffer**: 80 bytes maximum (kernel driver state structure) + +### Complete Vendor Command Map + +| Cmd | Name | Direction | wValue | wIndex | wLength | Purpose | +|-----|------|-----------|--------|--------|---------|---------| +| 0x80 | **GET_8PSK_CONFIG** | IN | 0x0000 | 0x0000 | 1 byte | Read device configuration status byte | +| 0x81 | SET_8PSK_CONFIG | OUT | varies | 0x0000 | 0 | Set config (STALL - not implemented) | +| 0x82 | (reserved) | -- | -- | -- | -- | STALL - not used | +| 0x83 | **I2C_WRITE** | OUT | dev_addr | reg_addr | N bytes | Write to I2C device (BCM4500 demod) | +| 0x84 | **I2C_READ** | IN | dev_addr | reg_addr | N bytes | Read from I2C device (BCM4500 demod) | +| 0x85 | **ARM_TRANSFER** | OUT | onoff (0/1) | 0x0000 | 0 | Start/stop MPEG-2 stream transfer | +| 0x86 | **TUNE_8PSK** | OUT | 0x0000 | 0x0000 | 10 bytes | Set tuning parameters | +| 0x87 | **GET_SIGNAL_STRENGTH** | IN | 0x0000 | 0x0000 | 6 bytes | Read SNR (signal quality) | +| 0x88 | **LOAD_BCM4500** | OUT | 1 (start) | 0x0000 | 0 | Initiate BCM4500 firmware download | +| 0x89 | **BOOT_8PSK** | IN | onoff (0/1) | 0x0000 | 1 byte | Power on/off 8PSK demodulator | +| 0x8A | **START_INTERSIL** | IN | onoff (0/1) | 0x0000 | 1 byte | Enable/disable LNB power supply | +| 0x8B | **SET_LNB_VOLTAGE** | OUT | voltage (0/1) | 0x0000 | 0 | Set LNB to 13V (0) or 18V (1) | +| 0x8C | **SET_22KHZ_TONE** | OUT | onoff (0/1) | 0x0000 | 0 | Enable/disable 22 kHz DiSEqC tone | +| 0x8D | **SEND_DISEQC_COMMAND** | OUT | msg[0] | 0x0000 | len | Send DiSEqC message to dish switch | +| 0x8E | SET_DVB_MODE | OUT | 1 | 0x0000 | 0 | Enable DVB-S mode (STALL on some revisions) | +| 0x8F | (unknown) | -- | -- | -- | -- | Unknown/internal use | +| 0x90 | **GET_SIGNAL_LOCK** | IN | 0x0000 | 0x0000 | 1 byte | Read signal lock status bit | +| 0x91-0x98 | (internal use) | -- | -- | -- | -- | I2C/diagnostic reads | +| 0x99 | **GET_DEMOD_STATUS** (v2.13+) | IN | 0x0000 | 0x0000 | 1 byte | Read BCM4500 register 0xF9 via I2C | +| 0x9A | **INIT_DEMOD** (v2.13+) | OUT | 0x0000 | 0x0000 | 0 | Trigger demodulator re-init (up to 3 attempts) | +| 0x9B | (reserved) | -- | -- | -- | -- | STALL - not used | +| 0x9C | **DELAY_COMMAND** (v2.13+) | OUT | delay_param | 0x0000 | 0 | Perform tuning/acquisition delay with polling | +| 0x9D | **CW3K_INIT** | OUT | onoff (0/1) | 0x0000 | 0 | Initialize SkyWalker CW3K model | + +### Configuration Status Byte (GET_8PSK_CONFIG - 0x80) + +The returned byte is a bit-mapped status register: + +``` +Bit 0 (0x01): bm8pskStarted - Device booted and running +Bit 1 (0x02): bm8pskFW_Loaded - BCM4500 firmware loaded +Bit 2 (0x04): bmIntersilOn - LNB power supply enabled +Bit 3 (0x08): bmDVBmode - DVB mode enabled +Bit 4 (0x10): bm22kHz - 22 kHz tone active +Bit 5 (0x20): bmSEL18V - 18V LNB voltage selected (else 13V) +Bit 6 (0x40): bmDCtuned - DC offset tuning complete +Bit 7 (0x80): bmArmed - MPEG-2 stream transfer armed +``` + +--- + +## 2. FIRMWARE LOADING SEQUENCE + +### Two-Stage Firmware Loading + +The device requires **two separate firmware files**: + +#### Stage 1: FX2 RAM Code (Bootloader Phase) +- **File**: `dvb-usb-gp8psk-01.fw` +- **Purpose**: Cypress FX2 microcontroller firmware +- **When**: Loaded automatically by DVB-USB framework during device enumeration (cold-to-warm boot transition) +- **How**: Standard USB firmware upload via control endpoint (0xA0 vendor request to CPUCS) +- **Device Mode**: Cold boot (VID 0x09C0, PID 0x0200) + +#### Stage 2: BCM4500 Demodulator Firmware (Tuner Phase) +- **File**: `dvb-usb-gp8psk-02.fw` +- **Purpose**: Broadcom BCM4500 DVB-S demodulator chip firmware +- **When**: Loaded after device power-on via LOAD_BCM4500 command +- **How**: Custom binary protocol via USB control transfers +- **Conditions**: Only for Rev.1 Warm devices; Rev.2 and SkyWalker have firmware burned in ROM + +### BCM4500 Firmware Loading Protocol + +```c +// Step 1: Initiate load +gp8psk_usb_out_op(device, LOAD_BCM4500, 1, 0, NULL, 0); // 0x88 cmd, wValue=1 + +// Step 2: Download firmware chunks +// Format: [chunk_length] [data...] repeated, terminated by 0xFF +ptr = fw_data; +while (ptr[0] != 0xFF) { + chunk_size = ptr[0] + 4; // length byte + 3 extra bytes + if (chunk_size > 64) ERROR("chunk too large"); + buf = copy chunk_size bytes from ptr; + usb_control_msg(device, USB_SNDCTRLPIPE, 0, ..., buf, chunk_size, 2000ms); + ptr += chunk_size; +} +``` + +### Boot Sequence Flow (Power-On) + +``` +1. Read device config (GET_8PSK_CONFIG - 0x80) + +-- Check bit 0: Device started? + +2. If not started: + +-- Boot device (BOOT_8PSK - 0x89, wValue=1) + +-- Get firmware version (via 0x0B: FW_VERSION_READ) + +3. If firmware not loaded (check bit 1): + +-- Load BCM4500 firmware (LOAD_BCM4500 - 0x88) + +4. If LNB not powered (check bit 2): + +-- Enable LNB supply (START_INTERSIL - 0x8A, wValue=1) + +5. Set DVB mode (on some revisions): + +-- SET_DVB_MODE - 0x8E, wValue=1 + +6. Abort any pending MPEG transfer: + +-- ARM_TRANSFER - 0x85, wValue=0 (cancel) + +7. Ready for tuning/reception +``` + +--- + +## 3. TUNING AND DEMODULATION FLOW + +### Frequency Tuning (TUNE_8PSK - 0x86) + +The tuning command transmits a 10-byte parameter structure: + +``` +Bytes 0-3: Symbol Rate (Little-Endian 32-bit, in sps) +Bytes 4-7: Tuner Frequency (Little-Endian 32-bit, in kHz) +Byte 8: Modulation Type (see table below) +Byte 9: Inner FEC Rate / reserved +``` + +### Modulation Types + +``` +ADV_MOD_DVB_QPSK = 0 // DVB-S standard QPSK +ADV_MOD_TURBO_QPSK = 1 // Turbo QPSK +ADV_MOD_TURBO_8PSK = 2 // Turbo 8PSK (Trellis) +ADV_MOD_TURBO_16QAM = 3 // Turbo 16QAM +ADV_MOD_DCII_C_QPSK = 4 // Digicipher II Combo +ADV_MOD_DCII_I_QPSK = 5 // Digicipher II I-stream (split) +ADV_MOD_DCII_Q_QPSK = 6 // Digicipher II Q-stream (split) +ADV_MOD_DCII_C_OQPSK = 7 // Digicipher II offset QPSK +ADV_MOD_DSS_QPSK = 8 // DSS/DIRECTV QPSK +ADV_MOD_DVB_BPSK = 9 // DVB-S BPSK +``` + +### Complete Tuning Sequence + +``` +1. Configure LNB voltage based on polarization: + H/CIRCULAR_L -> 18V (SET_LNB_VOLTAGE - 0x8B, wValue=1) + V/CIRCULAR_R -> 13V (SET_LNB_VOLTAGE - 0x8B, wValue=0) + +2. Set 22 kHz tone: + SET_22KHZ_TONE - 0x8C, wValue=0/1 + +3. Send DiSEqC switch command if needed (see section 4) + +4. Send tuning command: + TUNE_8PSK - 0x86, 10-byte parameter buffer + +5. Poll for lock: + GET_SIGNAL_LOCK - 0x90 (returns 1 byte, non-zero = locked) +``` + +### Signal Quality Monitoring + +**GET_SIGNAL_STRENGTH (0x87)** returns 6-byte buffer: +``` +Bytes 0-1: SNR value (LE 16-bit, dBu*256 units) +Bytes 2-5: Reserved/diagnostics +``` + +SNR scaling: `snr * 17` maps to 0-65535 (100% at SNR >= 0x0F00) + +--- + +## 4. DiSEqC COMMAND HANDLING (0x8D) + +### Tone Burst (Mini DiSEqC) +```c +// For legacy 2-way switches +gp8psk_usb_out_op(device, SEND_DISEQC_COMMAND, + burst_value, // SEC_MINI_A=0x00 or SEC_MINI_B=0x01 + 0, NULL, 0); +``` + +### Full DiSEqC Message (3-6 bytes) +```c +// Standard DiSEqC 1.0/1.1/1.2 messages +gp8psk_usb_out_op(device, SEND_DISEQC_COMMAND, + msg[0], // Framing byte as wValue + 0, + msg, // Full message buffer + msg_len); // 3-6 bytes +``` + +Common framing bytes: 0xE0 (broadcast, no reply), 0xE1 (addressed, no reply) + +--- + +## 5. DEVICE TABLE AND USB IDS + +### Kernel Module Aliases (Linux 6.16.5) + +``` +usb:v09C0p0200 - Rev.1 Cold boot (requires FX2 firmware upload) +usb:v09C0p0201 - Rev.1 Warm (requires BCM4500 FW via 0x88) +usb:v09C0p0202 - Rev.2 (BCM4500 in ROM) +usb:v09C0p0203 - SkyWalker-1 (PID confirmed on actual hardware) +usb:v09C0p0204 - SkyWalker-1 (alternate revision) +usb:v09C0p0206 - SkyWalker CW3K (requires CW3K_INIT 0x9D) +``` + +Note: PID 0x0205 (SkyWalker-2) is absent from the 6.16.5 kernel build. +Note: PID 0x0203 was not in earlier kernel versions (e.g., v6.6.1). + +### Device Properties + +```c +gp8psk_properties { + usb_ctrl = CYPRESS_FX2; + firmware = "dvb-usb-gp8psk-01.fw"; + num_adapters = 1; + endpoint = 0x82 (IN bulk); + stream = USB_BULK; + count = 7 URBs; + buffersize = 8192 bytes per URB; + generic_bulk_ctrl_endpoint = 0x01; +} +``` + +--- + +## 6. CORRELATION: KERNEL DRIVER vs FIRMWARE ANALYSIS + +### Commands Used by Kernel Driver + +| Command | Driver Uses | v2.06 FW | v2.13 FW | Rev.2 FW | +|---------|-------------|----------|----------|----------| +| 0x80 GET_8PSK_CONFIG | Boot check | OK | OK | OK | +| 0x83 I2C_WRITE | BCM4500 reg writes | OK | OK | OK | +| 0x84 I2C_READ | BCM4500 reg reads | OK | OK | OK | +| 0x85 ARM_TRANSFER | Stream start/stop | OK | OK | OK | +| 0x86 TUNE_8PSK | Frequency tuning | OK | OK | OK | +| 0x87 GET_SIGNAL_STRENGTH | SNR readback | OK | Changed | OK | +| 0x88 LOAD_BCM4500 | BCM4500 FW load | **STALL** | **STALL** | **STALL** | +| 0x89 BOOT_8PSK | Power on/off | OK | OK | OK | +| 0x8A START_INTERSIL | LNB power | OK | OK | OK | +| 0x8B SET_LNB_VOLTAGE | 13V/18V | OK | OK | OK | +| 0x8C SET_22KHZ_TONE | Tone control | OK | OK | OK | +| 0x8D SEND_DISEQC | DiSEqC messages | OK (GPIO) | OK (I2C) | OK | +| 0x90 GET_SIGNAL_LOCK | Lock status | OK | OK | OK | +| 0x9D CW3K_INIT | CW3K only | N/A | v2.13 only | N/A | + +### Key Finding: LOAD_BCM4500 (0x88) STALLs + +Command 0x88 routes to the STALL handler in all extracted firmware versions. +The kernel driver only sends this command for Rev.1 Warm (PID 0x0201) devices, +after checking that `bm8pskFW_Loaded` (bit 1) is NOT set. On Rev.2/SkyWalker +hardware, this bit is already set at boot (BCM4500 firmware in ROM), so the +driver never attempts the load. The STALL is a safety net. + +--- + +## Sources + +- Linux kernel 6.16.5 `dvb-usb-gp8psk` module (installed on analysis system) +- Linux kernel source: `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h`, `gp8psk-fe.c` +- Windows BDA driver source: `SkyWalker1_Final_Release/Include/SkyWalker1Control.h` +- Firmware reverse engineering via Ghidra (ports 8193-8197) diff --git a/gpif-streaming-analysis.md b/gpif-streaming-analysis.md index d7b9b47..a12b132 100644 --- a/gpif-streaming-analysis.md +++ b/gpif-streaming-analysis.md @@ -1,772 +1,772 @@ -# Genpix SkyWalker-1: GPIF MPEG-2 Streaming Path Analysis - -## Overview - -This document traces the MPEG-2 transport stream data path from the Broadcom BCM4500 satellite demodulator through the Cypress FX2's General Programmable Interface (GPIF) engine to USB bulk endpoint EP2 (0x82). Analysis covers all three firmware versions: - -| Instance | Port | Firmware | Functions | Config Byte | GPIF Ctrl Fn | -|----------|------|----------|-----------|-------------|-------------| -| v2.06 | 8193 | Original | 61 | IRAM 0x6D | 0x1919 | -| v2.13 FW1 | 8194 | Revised | 82 | IRAM 0x4F | 0x1800 | -| Rev.2 v2.10.4 | 8197 | HW Rev.2 | 107 | IRAM 0x4E | 0x0D7C | - ---- - -## Data Flow Architecture - -``` - Cypress FX2 (CY7C68013A) - +-----------------------------+ - | | - BCM4500 P3.5 TS_EN | GPIF Engine EP2 FIFO | USB 2.0 HS - Demodulator <-----------------+ (Master Read) (AUTOIN) +------------> Host - (I2C:0x3F) GPIF Data Bus | 0xE4xx wfm 4x buf | EP2 (0x82) - -------------------> CTL/RDY pins 8-bit | Bulk IN - 8-bit parallel TS | | 7 URBs x 8KB - +-----------------------------+ -``` - -**The path is: BCM4500 -> GPIF master read -> EP2 FIFO (auto-commit) -> USB bulk IN.** - -There is no software intermediary in the data path. The GPIF engine reads data directly into the EP2 FIFO buffer. The EP2FIFOCFG AUTOIN bit causes the hardware to automatically commit full packets to the USB controller for transfer to the host. The firmware only needs to start/stop the GPIF engine and handle completion interrupts. - ---- - -## 1. IFCONFIG: Interface Configuration - -**Register: IFCONFIG (0xE601) = 0xEE** - -Written in FUN_CODE_1000 (Rev.2 at CODE:1000), called during hardware initialization: - -```asm -; Rev.2 FUN_CODE_1000 at CODE:1000 -CODE:1000: 90e601 MOV DPTR,#0xe601 ; IFCONFIG register -CODE:1003: 74ee MOV A,#0xee ; 0xEE -CODE:1005: f0 MOVX @DPTR,A -``` - -**Bit decode (0xEE = 1110_1110):** - -| Bit | Name | Value | Meaning | -|-----|------|-------|---------| -| 7 | IFCLKSRC | 1 | Internal clock source | -| 6 | 3048MHZ | 1 | 48 MHz IFCLK frequency | -| 5 | IFCLKOE | 1 | IFCLK pin drives output (available to BCM4500) | -| 4 | IFCLKPOL | 0 | Non-inverted clock polarity | -| 3 | ASYNC | 1 | Asynchronous GPIF (no IFCLK-synchronized handshake) | -| 2 | GSTATE | 1 | GPIF state machine outputs visible on PORTE (debug) | -| 1:0 | IFCFG | 10 | **GPIF internal master mode** | - -The FX2 operates as a GPIF master, reading data from the BCM4500's parallel transport stream output. Asynchronous mode means the GPIF uses RDY pin handshaking rather than clock-edge sampling. The 48 MHz IFCLK output provides a reference clock to the BCM4500, though actual data capture is handshake-controlled. - -This value is identical across all three firmware versions. - -### Prior IFCONFIG Value - -During early init (FUN_CODE_10d9), IFCONFIG is temporarily set to 0xCA before FUN_CODE_1000 overwrites it: - -```asm -; Early init - temporary IFCONFIG -CODE:10f0: 90e601 MOV DPTR,#0xe601 -CODE:10f3: 74ca MOV A,#0xca ; 0xCA = internal 48MHz, IFCLKOE, no GSTATE, GPIF -CODE:10f5: f0 MOVX @DPTR,A -``` - -0xCA (1100_1010): Same as 0xEE but GSTATE=0 and ASYNC=0. The final value (0xEE) enables async mode and debug state output. - ---- - -## 2. FIFO Reset Sequence - -All endpoint FIFOs are reset during initialization using the Cypress-prescribed procedure. - -**Register: FIFORESET (0xE604)** - -```asm -; Rev.2 FUN_CODE_10d9 at CODE:1130-1152 -CODE:1130: 90e604 MOV DPTR,#0xe604 ; FIFORESET -CODE:1133: 7480 MOV A,#0x80 ; Bit 7 = NAKALL: NAK all host transfers -CODE:1135: f0 MOVX @DPTR,A -CODE:1136: 00 00 00 ; mandatory 3-NOP sync delay -CODE:1139: 7402 MOV A,#0x02 ; Reset EP2 FIFO -CODE:113b: f0 MOVX @DPTR,A -CODE:113c: 00 00 00 -CODE:113f: 7404 MOV A,#0x04 ; Reset EP4 FIFO -CODE:1141: f0 MOVX @DPTR,A -CODE:1142: 00 00 00 -CODE:1145: 7406 MOV A,#0x06 ; Reset EP6 FIFO -CODE:1147: f0 MOVX @DPTR,A -CODE:1148: 00 00 00 -CODE:114b: 7408 MOV A,#0x08 ; Reset EP8 FIFO -CODE:114d: f0 MOVX @DPTR,A -CODE:114e: 00 00 00 -CODE:1151: e4 CLR A ; NAKALL = 0 (resume) -CODE:1152: f0 MOVX @DPTR,A -``` - -The sequence: NAKALL on -> reset EP2 -> EP4 -> EP6 -> EP8 -> NAKALL off. Triple-NOP delays between writes are required by the FX2 architecture: XRAM register writes take 2 cycles to propagate, and back-to-back writes to the same register need at least 3 instruction cycles between them. - ---- - -## 3. Endpoint FIFO Configuration - -### EP2FIFOCFG (0xE618) = 0x0C - -```asm -; Rev.2 FUN_CODE_10d9 at CODE:1156 -CODE:1156: 90e618 MOV DPTR,#0xe618 ; EP2FIFOCFG -CODE:1159: 740c MOV A,#0x0c ; 0x0C -CODE:115b: f0 MOVX @DPTR,A -``` - -**Bit decode (0x0C = 0000_1100):** - -| Bit | Name | Value | Meaning | -|-----|------|-------|---------| -| 4 | INFM1 | 0 | IN endpoint: packet count not decremented | -| 3 | AUTOIN | 1 | **Auto-commit IN packets when FIFO buffer full** | -| 2 | ZEROLENIN | 1 | Allow zero-length IN packets | -| 1 | (reserved) | 0 | -- | -| 0 | WORDWIDE | 0 | **8-bit data path** (not 16-bit) | - -The AUTOIN bit is critical: when the GPIF engine fills an EP2 FIFO buffer to the configured packet size, the FX2 hardware automatically arms the buffer for USB transfer. No firmware intervention is needed in the data path. The 8-bit WORDWIDE setting matches the BCM4500's 8-bit parallel transport stream output. - -### Other Endpoint FIFOs (All Disabled) - -```asm -CODE:115f: e4 CLR A ; A = 0 -CODE:1160: 90e619 MOV DPTR,#0xe619 ; EP4FIFOCFG = 0x00 -CODE:1163: f0 MOVX @DPTR,A -CODE:1167: 90e61a MOV DPTR,#0xe61a ; EP6FIFOCFG = 0x00 -CODE:116a: f0 MOVX @DPTR,A -CODE:116e: 90e61b MOV DPTR,#0xe61b ; EP8FIFOCFG = 0x00 -CODE:1171: f0 MOVX @DPTR,A -``` - -Only EP2 is configured for streaming. EP4/EP6/EP8 FIFOs are left in default (manual/disabled) state. - ---- - -## 4. GPIF Waveform and Control Configuration - -### Init Table-Driven Configuration - -The GPIF waveform descriptors (0xE400-0xE47F), GPIFIDLECTL (0xE660), GPIFCTLCFG (0xE661), GPIFWFSELECT (0xE662), and related registers are programmed via a compressed init table processed during the main() entry point, before the main loop begins. - -| Version | Init Table Address | Parser Function | -|---------|-------------------|-----------------| -| v2.06 | CODE:0B46 | main (0x188D) | -| v2.13 FW1 | CODE:0B88 | main_entry (0x170D) | -| Rev.2 | CODE:0B48 | main_init (0x15A6) | - -The init table uses a custom encoding: -- **Control byte** bits [7:6] select the write mode (IRAM, XRAM 2-byte addr, bit manipulation, XRAM 1-byte addr) -- **Control byte** bits [5:0] encode the byte count (extended to 2 bytes if bit 5 is set) -- Data bytes follow, written sequentially to the target address range - -The table writes configuration data to the 0xE0xx scratch RAM area (device descriptors, I2C addresses, modulation parameters) and to the 0xE4xx/0xE6xx GPIF/USB register space. The GPIF waveform descriptors occupy 128 bytes at 0xE400-0xE47F and define the state machine transitions for reading data from the BCM4500. - -### GPIF Waveform Structure - -The GPIF waveform data embedded in the init table contains 4 waveform slots (32 bytes each): - -The waveform data visible in the init table (from 0x0BAD region in Rev.2) includes the pattern: -``` -01 01 01 01 01 01 07 00 01 00 00 00 00 00 00 -F0 F0 F0 F0 F0 F0 F0 F0 00 3F 00 00 00 00 3F -``` - -This is characteristic of a simple single-state GPIF read waveform: -- **States 0-5**: CTL outputs = 0x01 (single control line asserted), length = 1 IFCLK -- **State 6**: CTL = 0x07, length = 0 (idle/terminate state) -- **Opcode/branch**: 0x00 (SDP = sample data point, branch idle) -- **Output/logic**: 0xF0 pattern (FIFO write flags) - -The waveform programs a straightforward "assert read strobe, capture data, de-assert" cycle that reads one byte per GPIF transaction from the BCM4500's parallel port into the EP2 FIFO. - -### REVCTL (0xE60B) = 0x03 - -```asm -CODE:1127: 90e60b MOV DPTR,#0xe60b ; REVCTL -CODE:112a: 7403 MOV A,#0x3 -CODE:112c: f0 MOVX @DPTR,A -``` - -REVCTL = 0x03: Both NOAUTOARM and SKIPCOMMIT bits set. This disables the automatic arming of endpoint buffers on access and skips the auto-commit behavior. Combined with EP2FIFOCFG.AUTOIN=1, this means the GPIF engine explicitly controls when data is committed -- the AUTOIN triggers on FIFO fullness, not on CPU access. - -### GPIFSGLDATH (SFR 0xBB) - GPIF Trigger Register - -The SFR at address 0xBB is used as the GPIF trigger/status register: - -- **Read**: Bit 7 (DONE) = 1 when GPIF is idle -- **Write**: Bits [1:0] = endpoint select (00=EP2), Bit 2 = read direction - -```asm -; Poll for GPIF completion -CODE:0dca: e5bb MOV A,0xbb ; Read GPIFTRIG -CODE:0dcc: 30e7fb JNB 0xe7,0x0dca ; Loop until bit 7 (DONE) = 1 - -; Trigger next GPIF read into EP2 -CODE:0dd2: 75bb04 MOV 0xbb,#0x4 ; GPIFTRIG = 0x04 (read, EP2) -``` - ---- - -## 5. ARM_TRANSFER (Vendor Command 0x85) - -### Dispatch - -The host issues USB vendor command 0x85 to start or stop the MPEG-2 transport stream. The command dispatches via the vendor command jump table at CODE:0x0076: - -| Version | Jump Table Entry | Handler Address | GPIF Control Fn | -|---------|-----------------|-----------------|-----------------| -| v2.06 | `21 10` (AJMP) | CODE:0110 | LCALL 0x1919 | -| v2.13 FW1 | `21 10` (AJMP) | CODE:0110 | LCALL 0x1800 | -| Rev.2 | `01 fa` (AJMP) | CODE:00FA | LCALL 0x0D7C | - -The handler logic is identical across versions despite the address differences: - -```asm -; Rev.2 ARM_TRANSFER handler at CODE:00FA (v2.06/v2.13 at CODE:0110) -CODE:00fa: e54e MOV A,0x4e ; Read config byte -CODE:00fc: 30e00d JNB 0xe0,0x010c ; Test bit 0: demodulator active? -CODE:00ff: 90e6ba MOV DPTR,#0xe6ba ; Read USB SETUP wValue (low byte) -CODE:0102: e0 MOVX A,@DPTR -CODE:0103: 24ff ADD A,#0xff ; Set CY if wValue != 0 -CODE:0105: 9206 MOV 0x06,CY ; arm_flag = (wValue != 0) -CODE:0107: 120d7c LCALL 0x0d7c ; Call GPIF control function -CODE:010a: 8005 SJMP 0x0111 ; Skip to done -CODE:010c: c206 CLR 0x06 ; arm_flag = 0 (force stop if no demod) -CODE:010e: 120d7c LCALL 0x0d7c ; Call GPIF control function -CODE:0111: e4 CLR A -CODE:0112: 90e68b MOV DPTR,#0xe68b ; EP0BCL = 0 (acknowledge control transfer) -CODE:0115: f0 MOVX @DPTR,A -``` - -The handler checks bit 0 of the configuration byte (demodulator present/active). If active, it reads wValue from the USB SETUP packet: wValue=1 means start streaming, wValue=0 means stop. If the demodulator is not active, it forces a stop. - -### GPIF Control Function - START Path - -When arm_flag=1 (start streaming), the GPIF control function at 0x0D7C (Rev.2) / 0x1800 (v2.13) / 0x1919 (v2.06) executes: - -```c -// Decompiled from Rev.2 FUN_CODE_0d7c -- START STREAMING -if (arm_flag != 0 && config_byte.7 == 0) { - // Not already streaming -- initialize GPIF transfer - - config_byte |= 0x80; // Mark streaming active - - // 1. Set GPIF transaction count (0xE630:0xE631) - GPIFTCB3 = 0x80; // 0xE630 = 0x80 - GPIFTCB2 = 0x00; // 0xE631 = 0x00 - // Transaction count = 0x8000_0000 (2 GB) -- effectively infinite - - // 2. Clear GPIF address registers - *(0xE6D2) = 0x00; // Address high = 0 - *(0xE6D3) = 0x01; // Address low = 1 (initial) - EP2FIFOBCL = 0x00; // 0xE6F1: Reset byte count - *(0xE6D3) = 0x00; // Address low = 0 - - // 3. Initial GPIF trigger via XRAM registers - *(0xE6D0) = 0x02; // Trigger/config = EP2 read - *(0xE6D1) = 0x00; // Trigger latch - - // 4. Assert BCM4500 transport stream enable - P3 &= ~0x20; // P3.5 LOW = TS_EN asserted - - // 5. Wait for GPIF completion - while (!(GPIFTRIG & 0x80)) {} // Poll SFR 0xBB bit 7 (DONE) - - // 6. Re-enable P3.5 - P3 |= 0x20; // P3.5 HIGH - - // 7. Trigger continuous GPIF read into EP2 - GPIFTRIG = 0x04; // SFR 0xBB = read EP2 - - // 8. Signal data path active - P0 &= ~0x80; // P0.7 LOW = streaming active indicator -} -``` - -**Disassembly (Rev.2 at CODE:0D7C):** -```asm -; START path (arm_flag = 1, config.7 = 0) -CODE:0d84: 434e80 ORL 0x4e,#0x80 ; config_byte |= 0x80 (streaming flag) - -CODE:0d87: 90e630 MOV DPTR,#0xe630 ; GPIFTCB3 -CODE:0d8a: 7480 MOV A,#0x80 -CODE:0d8c: f0 MOVX @DPTR,A ; GPIFTCB3 = 0x80 - -CODE:0d90: e4 CLR A -CODE:0d91: 90e631 MOV DPTR,#0xe631 ; GPIFTCB2 -CODE:0d94: f0 MOVX @DPTR,A ; GPIFTCB2 = 0x00 - -CODE:0d98: 90e6d2 MOV DPTR,#0xe6d2 -CODE:0d9b: f0 MOVX @DPTR,A ; [0xE6D2] = 0x00 - -CODE:0d9f: 90e6d3 MOV DPTR,#0xe6d3 -CODE:0da2: 04 INC A ; A = 1 -CODE:0da3: f0 MOVX @DPTR,A ; [0xE6D3] = 0x01 - -CODE:0da7: e4 CLR A -CODE:0da8: 90e6f1 MOV DPTR,#0xe6f1 ; EP2FIFOBCL (byte count low) -CODE:0dab: f0 MOVX @DPTR,A ; EP2FIFOBCL = 0x00 - -CODE:0daf: 90e6d3 MOV DPTR,#0xe6d3 -CODE:0db2: f0 MOVX @DPTR,A ; [0xE6D3] = 0x00 - -CODE:0db6: 90e6d0 MOV DPTR,#0xe6d0 -CODE:0db9: 7402 MOV A,#0x2 -CODE:0dbb: f0 MOVX @DPTR,A ; [0xE6D0] = 0x02 - -CODE:0dbf: e4 CLR A -CODE:0dc0: 90e6d1 MOV DPTR,#0xe6d1 -CODE:0dc3: f0 MOVX @DPTR,A ; [0xE6D1] = 0x00 - -CODE:0dc7: 53b0df ANL 0xb0,#0xdf ; P3 &= 0xDF -> P3.5 = 0 (TS_EN assert) - -CODE:0dca: e5bb MOV A,0xbb ; Read GPIFTRIG status -CODE:0dcc: 30e7fb JNB 0xe7,0x0dca ; Wait for DONE bit - -CODE:0dcf: 43b020 ORL 0xb0,#0x20 ; P3 |= 0x20 -> P3.5 = 1 -CODE:0dd2: 75bb04 MOV 0xbb,#0x4 ; GPIFTRIG = 0x04 (read EP2) - -CODE:0dd8: 53807f ANL 0x80,#0x7f ; P0 &= 0x7F -> P0.7 = 0 -CODE:0ddb: 22 RET -``` - -### GPIF Control Function - STOP Path - -When arm_flag=0 (stop streaming) and config_byte.7=1 (currently streaming): - -```c -// Decompiled from Rev.2 FUN_CODE_0d7c -- STOP STREAMING -if (arm_flag == 0 && config_byte.7 == 1) { - // Currently streaming -- abort GPIF and flush - - P0 |= 0x80; // P0.7 HIGH = streaming stopped - EP2FIFOBCH = 0xFF; // 0xE6F5: Force byte count high = 0xFF - // (skip/flush current FIFO packet) - - while (!(GPIFTRIG & 0x80)) {} // Wait for GPIF to go idle - - OUTPKTEND = 0x82; // 0xE648 = 0x82: Force-commit EP2 packet - // Bit 7 = skip, bits[3:0] = EP2 - config_byte &= ~0x80; // Clear streaming flag - - P3 |= 0xE0; // P3.7:5 = 1 (de-assert all control lines) -} -``` - -**Disassembly (Rev.2 at CODE:0DDC):** -```asm -; STOP path (arm_flag = 0, config.7 = 1) -CODE:0de1: 438080 ORL 0x80,#0x80 ; P0 |= 0x80 -> P0.7 = 1 (stopped) - -CODE:0de4: 90e6f5 MOV DPTR,#0xe6f5 ; EP2FIFOBCH (byte count high) -CODE:0de7: 74ff MOV A,#0xff -CODE:0de9: f0 MOVX @DPTR,A ; = 0xFF (force flush) - -CODE:0dea: e5bb MOV A,0xbb ; Read GPIFTRIG -CODE:0dec: 30e7fb JNB 0xe7,0x0dea ; Wait for DONE - -CODE:0def: 90e648 MOV DPTR,#0xe648 ; OUTPKTEND -CODE:0df2: 7482 MOV A,#0x82 ; Skip=1, EP2 -CODE:0df4: f0 MOVX @DPTR,A ; Force-commit/skip EP2 packet - -CODE:0df5: 534e7f ANL 0x4e,#0x7f ; config_byte &= 0x7F (clear streaming) -CODE:0df8: 43b0e0 ORL 0xb0,#0xe0 ; P3 |= 0xE0 (de-assert controls) -CODE:0dfb: 22 RET -``` - -**OUTPKTEND (0xE648) = 0x82** is notable: bit 7 set means "skip" (discard partially filled packet), bits [3:0] = 2 = EP2. This ensures any partial FIFO buffer is flushed when stopping the stream. - ---- - -## 6. INT4/INT6 Interrupt Handlers (GPIF/FIFO Events) - -### Rev.2 v2.10.4: Shared Handler via Trampoline - -Both INT4 and INT6 vectors jump to the same handler: - -```asm -CODE:0043: 021200 LJMP 0x1200 ; INT4_FX2_vector -> trampoline -CODE:0053: 021200 LJMP 0x1200 ; INT6_FX2_vector -> trampoline - -; Trampoline at 0x1200 -CODE:1200: 022084 LJMP 0x2084 ; -> GPIF_INT4_INT6_handler -``` - -**GPIF_INT4_INT6_handler (CODE:2084):** -```asm -CODE:2084: c0e0 PUSH A ; Save accumulator -CODE:2086: c083 PUSH DPH ; Save DPTR high -CODE:2088: c082 PUSH DPL ; Save DPTR low - -CODE:208a: d201 SETB 0x01 ; Set bit flag _0_1 (GPIF event pending) - -CODE:208c: 5391ef ANL 0x91,#0xef ; Clear EXIF.4 (INT4/INT6 IRQ flag) - -CODE:208f: 90e65d MOV DPTR,#0xe65d ; GPIFIRQ (GPIF Interrupt Request) -CODE:2092: 7401 MOV A,#0x1 -CODE:2094: f0 MOVX @DPTR,A ; Clear GPIFIRQ bit 0 - -CODE:2095: d082 POP DPL ; Restore context -CODE:2097: d083 POP DPH -CODE:2099: d0e0 POP A -CODE:209b: 32 RETI ; Return from interrupt -``` - -The handler is minimal: it sets a software flag (_0_1) and clears the hardware interrupt. The actual GPIF processing happens in the main loop when this flag is polled. - -### v2.13 FW1: Same Pattern - -```asm -CODE:0043: 021500 LJMP 0x1500 ; INT4 -> thunk_FUN_CODE_2252 -CODE:1500: 022252 LJMP 0x2252 ; -> FUN_CODE_2252 -``` - -**FUN_CODE_2252 (CODE:2252):** identical logic, sets _0_6 flag, clears EXIF.4 and GPIFIRQ.0. - -### v2.06: Different Vector Dispatch - -In v2.06, INT4 and INT6 both jump to 0x1600 which is a jump table indexed by the interrupt source register. The GPIF-related entry jumps to the same pattern (set flag, clear IRQ, return). - -### INT2 (USB/GPIF Combined Vector) - -The INT2 vector at CODE:0033 handles USB/GPIF combined interrupts: - -```asm -; Rev.2 -CODE:0033: 02223f LJMP 0x223f ; USB_GPIF_handler - -; USB_GPIF_handler at CODE:223F -CODE:223f: c0e0 PUSH A -CODE:2241: 53d8ef ANL 0xd8,#0xef ; Clear CCON.4 (PCA counter flag) -CODE:2244: d0e0 POP A -CODE:2246: 32 RETI -``` - -This handler simply clears the PCA (Programmable Counter Array) interrupt flag. The actual USB processing is handled by polling in the main loop. - ---- - -## 7. FLOWSTATE Configuration - -During initialization (FUN_CODE_09a9 at CODE:0AEF), the GPIF flow state machine is configured: - -```asm -CODE:0aef: 90e668 MOV DPTR,#0xe668 ; FLOWSTATEA -CODE:0af2: e0 MOVX A,@DPTR -CODE:0af3: 4409 ORL A,#0x09 ; Set bits 0 and 3 -CODE:0af5: f0 MOVX @DPTR,A -``` - -**FLOWSTATEA (0xE668) |= 0x09:** - -| Bit | Value | Meaning | -|-----|-------|---------| -| 0 | 1 | FSEN: Flow State enable -- GPIF uses flow state logic | -| 3 | 1 | FS[3]: Flow state flag -- specific to waveform design | - -The flow state machine controls automated GPIF-to-FIFO data transfers. With FSEN=1, the GPIF engine can automatically re-trigger transactions when FIFO space is available, creating a continuous streaming pipeline without firmware intervention after initial setup. - -### GPIF Interrupt Enable - -```asm -CODE:0af6: 90e65c MOV DPTR,#0xe65c ; GPIFIE -CODE:0af9: e0 MOVX A,@DPTR -CODE:0afa: 443d ORL A,#0x3d ; Enable bits 0,2,3,4,5 -CODE:0afc: f0 MOVX @DPTR,A -``` - -**GPIFIE (0xE65C) |= 0x3D = 0011_1101:** - -| Bit | Name | Enabled | Purpose | -|-----|------|---------|---------| -| 0 | GPIFWF | Yes | Waveform completion interrupt | -| 1 | (reserved) | No | -- | -| 2 | GPIFTCEXP | Yes | Transaction count expired | -| 3 | GPIFGPIFDONE | Yes | GPIF operation done | -| 4 | GPIFFF | Yes | FIFO flag interrupt | -| 5 | GPIFWF2 | Yes | Waveform 2 completion | - -These interrupts drive the INT4/INT6 handlers described in section 6. The combination of TC expire, DONE, and FIFO flag interrupts allows the main loop to manage the streaming pipeline: restart GPIF when buffers become available, handle end-of-transfer, and detect error conditions. - ---- - -## 8. GPIF Pin Assignments - -### Control Outputs (CTL[5:0]) - -Based on the init table and the P3 register manipulation in the GPIF control function: - -| Pin | Direction | Function | Start State | Active State | -|-----|-----------|----------|-------------|--------------| -| P3.5 | Output | TS_EN (Transport Stream Enable) | HIGH (inactive) | LOW (active) | -| P3.6 | Output | BCM4500 control line | HIGH | LOW | -| P3.7 | Output | BCM4500 control line | HIGH | HIGH | -| P0.7 | Output | Host streaming indicator | HIGH (idle) | LOW (streaming) | - -### Data Bus - -The GPIF uses the FD[7:0] data bus (Port B/D depending on FX2 variant) in 8-bit mode (WORDWIDE=0). - -### Ready Signals (RDY[5:0]) - -The GPIF waveform references RDY pins for handshaking with the BCM4500. The specific RDY pin assignment is encoded in the waveform descriptors loaded from the init table. - ---- - -## 9. Main Loop Integration - -After initialization, the main loop (FUN_CODE_09a9 at CODE:0B0E) polls for GPIF events: - -```c -// Simplified main loop (Rev.2) -while (true) { - FUN_CODE_201e(); // Poll I2C / USB status - - if (gpif_event_flag) { // _0_1 set by INT4/INT6 handler - FUN_CODE_0319(); // Process vendor commands - gpif_event_flag = 0; - } - - if (gpif_done_flag) { // _0_3 set when GPIF transfer completes - FUN_CODE_2265(); // (stub -- no-op in Rev.2) - if (carry_set) { - gpif_done_flag = 0; - - // Re-arm GPIF or handle completion - while (true) { - FUN_CODE_1faa(); // Enter idle (PCON.0 = 1) - - if (remote_wakeup) break; - - // Check EP2CS for buffer availability - ep2cs = *(0xE682); // EP2CS register - if ((ep2cs & 0x80) && // EP2 busy - (ep2cs & 0x02)) // EP2 stall - continue; // Keep waiting - - if ((ep2cs & 0x40) && // EP2 full - (ep2cs & 0x01)) // EP2 empty - continue; // Keep waiting - - break; // Buffer available - } - - FUN_CODE_1eda(); // Handle USB re-enumerate if needed - FUN_CODE_2267(); // (stub -- no-op in Rev.2) - } - } -} -``` - -**Disassembly of the main loop polling (Rev.2 at CODE:0B0E):** -```asm -CODE:0b0e: 12201e LCALL 0x201e ; Poll I2C/USB -CODE:0b11: 300105 JNB 0x01,0x0b19 ; Check _0_1 (GPIF event flag) -CODE:0b14: 120319 LCALL 0x0319 ; Process vendor commands -CODE:0b17: c201 CLR 0x01 ; Clear flag -CODE:0b19: 3003f2 JNB 0x03,0x0b0e ; Check _0_3 (GPIF done flag) -CODE:0b1c: 122265 LCALL 0x2265 ; (no-op) -CODE:0b1f: 50ed JNC 0x0b0e ; If no carry, loop back - -CODE:0b21: c203 CLR 0x03 ; Clear GPIF done flag -CODE:0b23: 121faa LCALL 0x1faa ; Enter idle (PCON.0 = sleep) -CODE:0b26: 200016 JB 0x00,0x0b3f ; Remote wakeup -> skip - -; Poll EP2CS for buffer availability -CODE:0b29: 90e682 MOV DPTR,#0xe682 ; EP2CS register -CODE:0b2c: e0 MOVX A,@DPTR -CODE:0b2d: 30e704 JNB 0xe7,0x0b34 ; bit 7 = busy? -CODE:0b30: e0 MOVX A,@DPTR -CODE:0b31: 20e1ef JB 0xe1,0x0b23 ; bit 1 = stall? -> keep waiting -CODE:0b34: 90e682 MOV DPTR,#0xe682 -CODE:0b37: e0 MOVX A,@DPTR -CODE:0b38: 30e604 JNB 0xe6,0x0b3f ; bit 6 = full? -CODE:0b3b: e0 MOVX A,@DPTR -CODE:0b3c: 20e0e4 JB 0xe0,0x0b23 ; bit 0 = empty? -> keep waiting - -CODE:0b3f: 121eda LCALL 0x1eda ; USB re-enumerate check -CODE:0b42: 122267 LCALL 0x2267 ; (no-op) -CODE:0b45: 80c7 SJMP 0x0b0e ; Loop forever -``` - -The main loop uses CPU idle mode (PCON.0) between GPIF events, waking on interrupts. The EP2CS polling checks that EP2 buffers are available before re-arming the GPIF, preventing FIFO overrun. - ---- - -## 10. CPUCS and Clock Configuration - -**Register: CPUCS (0xE600)** - -Set during init (FUN_CODE_10d9): - -```asm -CODE:10e4: 90e600 MOV DPTR,#0xe600 ; CPUCS -CODE:10e7: e0 MOVX A,@DPTR ; Read current value -CODE:10e8: 54e5 ANL A,#0xe5 ; Clear bits 4,3,1 (CLKSPD, CLKINV) -CODE:10ea: 4410 ORL A,#0x10 ; Set bit 4 -CODE:10ec: f0 MOVX @DPTR,A ; Write back -``` - -CPUCS bits: -- Bits [4:3] CLKSPD = 10 = **48 MHz** CPU clock -- Other bits preserved from power-on defaults - ---- - -## 11. GPIO Pin Summary for Streaming - -| Pin | SFR | Direction | Streaming Active | Streaming Stopped | Function | -|-----|-----|-----------|-----------------|-------------------|----------| -| P0.2 | 0x80 | Out | Set during init | -- | BCM4500 config | -| P0.7 | 0x80 | Out | LOW | HIGH | Streaming status indicator | -| P3.5 | 0xB0 | Out | Pulsed LOW | HIGH | BCM4500 TS_EN (Transport Stream Enable) | -| P3.6 | 0xB0 | Out | Controlled | HIGH | BCM4500 control | -| P3.7 | 0xB0 | Out | Controlled | HIGH | BCM4500 control | - -During init (FUN_CODE_10d9): -```asm -CODE:111b: 758084 MOV 0x80,#0x84 ; P0 = 0x84 (bits 7,2 set) -CODE:1121: 75b0e1 MOV 0xb0,#0xe1 ; P3 = 0xE1 (bits 7,6,5,0 set) -``` - -P0 init = 0x84: P0.7=1 (streaming off), P0.2=1 (BCM4500 control) -P3 init = 0xE1: P3.7:5=1 (all control lines inactive), P3.0=1 - ---- - -## 12. Complete Streaming Sequence - -### Start (Host sends ARM_TRANSFER wValue=1): - -``` -1. Host -> USB vendor cmd 0x85, wValue=1 -2. FX2 checks demod active (config_byte bit 0) -3. FX2 sets arm_flag = 1 -4. gpif_ctrl(): - a. Set config_byte.7 = 1 (streaming active) - b. Load GPIF transaction count: GPIFTCB3:2 = 0x8000 (huge count) - c. Reset GPIF address and EP2 FIFO byte count - d. Initial GPIF setup via XRAM 0xE6D0-0xE6D3 - e. Assert P3.5 LOW -> BCM4500 transport stream output enabled - f. Wait for initial GPIF transaction to complete (poll GPIFTRIG.7) - g. De-assert P3.5 HIGH - h. Trigger continuous GPIF read: GPIFTRIG = 0x04 (read into EP2) - i. Set P0.7 LOW (streaming indicator) -5. GPIF engine now auto-reads BCM4500 data into EP2 FIFO -6. EP2FIFOCFG.AUTOIN commits full packets to USB automatically -7. Host reads EP2 (0x82) bulk IN pipe continuously -``` - -### Steady State: - -``` -BCM4500 data bus -> GPIF state machine read -> EP2 FIFO buffer - | | - | (8-bit async handshake via RDY/CTL) | AUTOIN = 1 - | | - v v - Continuous GPIF transactions Auto-commit when full - (FLOWSTATE re-triggers) (hardware, no CPU) - | - v - USB bulk IN transfer - (host polls EP2 0x82) -``` - -The FLOWSTATE engine (FLOWSTATEA bit 0 = FSEN) automatically re-triggers GPIF transactions when EP2 FIFO buffers become available after USB transfers complete. This creates a fully hardware-managed pipeline where the CPU only needs to handle exceptional conditions. - -### Stop (Host sends ARM_TRANSFER wValue=0): - -``` -1. Host -> USB vendor cmd 0x85, wValue=0 -2. gpif_ctrl(): - a. Set P0.7 HIGH (streaming stopped) - b. Write EP2FIFOBCH = 0xFF (force-flush current buffer) - c. Wait for GPIF idle (poll GPIFTRIG.7) - d. Write OUTPKTEND = 0x82 (skip/discard partial EP2 packet) - e. Clear config_byte.7 (streaming inactive) - f. Set P3.7:5 = 1 (de-assert all BCM4500 control lines) -``` - ---- - -## 13. Cross-Version Comparison - -The GPIF streaming path is functionally identical across all three firmware versions. The only differences are: - -| Aspect | v2.06 (port 8193) | v2.13 FW1 (port 8194) | Rev.2 v2.10.4 (port 8197) | -|--------|-------|-----------|---------------| -| ARM_TRANSFER handler | CODE:0110 | CODE:0110 | CODE:00FA | -| GPIF control function | CODE:1919 | CODE:1800 | CODE:0D7C | -| Config byte location | IRAM 0x6D | IRAM 0x4F | IRAM 0x4E | -| Arm bit flag | bit 0x09 | bit 0x02 | bit 0x06 | -| INT4/INT6 vector | 0x1600 (dispatch table) | 0x1500 (thunk to 0x2252) | 0x1200 (thunk to 0x2084) | -| GPIF ISR action | Set flag, clear EXIF/GPIFIRQ | Set flag, clear EXIF/GPIFIRQ | Set flag, clear EXIF/GPIFIRQ | -| EP2FIFOCFG value | 0x0C (AUTOIN, ZEROLENIN, 8-bit) | 0x0C | 0x0C | -| IFCONFIG value | 0xEE (GPIF master, async, 48MHz) | 0xEE | 0xEE | -| FLOWSTATEA setting | \|= 0x09 (FSEN + FS3) | \|= 0x09 | \|= 0x09 | -| GPIFIE setting | \|= 0x3D | \|= 0x3D | \|= 0x3D | - -The register sequences within the GPIF control function are byte-for-byte identical (the same XRAM addresses, same values, same NOP delays). Differences are limited to which IRAM locations store state variables and which bit-addressable flags are used. - ---- - -## 14. Register Reference - -### XRAM Registers Written During Streaming - -| Address | Name | Start Value | Stop Value | Notes | -|---------|------|-------------|------------|-------| -| 0xE600 | CPUCS | (val & 0xE5) \| 0x10 | -- | 48 MHz CPU clock | -| 0xE601 | IFCONFIG | 0xEE | -- | GPIF master, async, 48 MHz | -| 0xE604 | FIFORESET | 0x80/0x02/0x04/0x06/0x08/0x00 | -- | Init only | -| 0xE60B | REVCTL | 0x03 | -- | NOAUTOARM + SKIPCOMMIT | -| 0xE618 | EP2FIFOCFG | 0x0C | -- | AUTOIN, ZEROLENIN, 8-bit | -| 0xE630 | GPIFTCB3 | 0x80 | -- | TC = 0x8000xxxx | -| 0xE631 | GPIFTCB2 | 0x00 | -- | TC continued | -| 0xE648 | OUTPKTEND | -- | 0x82 | Skip EP2 (stop only) | -| 0xE65C | GPIFIE | \|= 0x3D | -- | WF, TC, DONE, FF, WF2 | -| 0xE65D | GPIFIRQ | 0x01 | -- | Clear in ISR | -| 0xE668 | FLOWSTATEA | \|= 0x09 | -- | FSEN + FS[3] | -| 0xE670 | GPIFHOLDAMOUNT | 0x00 | -- | No hold time | -| 0xE6D0 | (GPIF trigger/config) | 0x02 | -- | EP2 read setup | -| 0xE6D1 | (GPIF trigger latch) | 0x00 | -- | Trigger latch | -| 0xE6D2 | (GPIF addr high) | 0x00 | -- | Address = 0 | -| 0xE6D3 | (GPIF addr low) | 0x01 then 0x00 | -- | Address setup | -| 0xE6F1 | EP2FIFOBCL | 0x00 | -- | Reset byte count | -| 0xE6F5 | EP2FIFOBCH | -- | 0xFF | Force flush (stop only) | - -### SFR Registers - -| Address | Name | Start Value | Stop Value | Notes | -|---------|------|-------------|------------|-------| -| 0x80 | P0 | 0x84 | \|= 0x80 | Bit 7 = streaming indicator | -| 0x91 | EXIF | &= 0xEF | -- | Clear bit 4 (INT4/6 flag) | -| 0xB0 | P3 | 0xE1 | \|= 0xE0 | Bits 7:5 = BCM4500 control | -| 0xBB | GPIFTRIG | 0x04 (read EP2) | (poll) | Trigger / status | - ---- - -## 15. Timing Considerations - -- **GPIF clock**: 48 MHz internal (IFCONFIG IFCLKSRC=1, 3048MHZ=1) -- **CPU clock**: 48 MHz (CPUCS CLKSPD=10) -- **GPIF async mode**: Data capture not synchronized to IFCLK edges; uses RDY pin handshaking -- **NOP delays**: 3 NOPs between consecutive XRAM writes to same register (~62.5 ns at 48 MHz) -- **EP2 buffer commit**: Automatic via AUTOIN when FIFO reaches packet size -- **GPIF re-trigger**: Automatic via FLOWSTATE when EP2 buffer space available -- **Transport stream rate**: BCM4500 outputs at the satellite symbol rate (up to 30 Msps), but the effective byte rate depends on modulation and coding. USB 2.0 High Speed bulk bandwidth (480 Mbps theoretical, ~35 MB/s practical) is more than sufficient for DVB-S transport streams (typically 1-5 MB/s). - ---- - -## Sources - -- Ghidra decompilation/disassembly of firmware images on ports 8193, 8194, 8197 -- Cypress CY7C68013A (FX2LP) Technical Reference Manual, register map -- Linux kernel `gp8psk` driver (`drivers/media/usb/dvb-usb/gp8psk.c`) -- Prior analysis: `gp8psk-driver-analysis.md`, `firmware-analysis-v206-vs-v213.md` +# Genpix SkyWalker-1: GPIF MPEG-2 Streaming Path Analysis + +## Overview + +This document traces the MPEG-2 transport stream data path from the Broadcom BCM4500 satellite demodulator through the Cypress FX2's General Programmable Interface (GPIF) engine to USB bulk endpoint EP2 (0x82). Analysis covers all three firmware versions: + +| Instance | Port | Firmware | Functions | Config Byte | GPIF Ctrl Fn | +|----------|------|----------|-----------|-------------|-------------| +| v2.06 | 8193 | Original | 61 | IRAM 0x6D | 0x1919 | +| v2.13 FW1 | 8194 | Revised | 82 | IRAM 0x4F | 0x1800 | +| Rev.2 v2.10.4 | 8197 | HW Rev.2 | 107 | IRAM 0x4E | 0x0D7C | + +--- + +## Data Flow Architecture + +``` + Cypress FX2 (CY7C68013A) + +-----------------------------+ + | | + BCM4500 P3.5 TS_EN | GPIF Engine EP2 FIFO | USB 2.0 HS + Demodulator <-----------------+ (Master Read) (AUTOIN) +------------> Host + (I2C:0x3F) GPIF Data Bus | 0xE4xx wfm 4x buf | EP2 (0x82) + -------------------> CTL/RDY pins 8-bit | Bulk IN + 8-bit parallel TS | | 7 URBs x 8KB + +-----------------------------+ +``` + +**The path is: BCM4500 -> GPIF master read -> EP2 FIFO (auto-commit) -> USB bulk IN.** + +There is no software intermediary in the data path. The GPIF engine reads data directly into the EP2 FIFO buffer. The EP2FIFOCFG AUTOIN bit causes the hardware to automatically commit full packets to the USB controller for transfer to the host. The firmware only needs to start/stop the GPIF engine and handle completion interrupts. + +--- + +## 1. IFCONFIG: Interface Configuration + +**Register: IFCONFIG (0xE601) = 0xEE** + +Written in FUN_CODE_1000 (Rev.2 at CODE:1000), called during hardware initialization: + +```asm +; Rev.2 FUN_CODE_1000 at CODE:1000 +CODE:1000: 90e601 MOV DPTR,#0xe601 ; IFCONFIG register +CODE:1003: 74ee MOV A,#0xee ; 0xEE +CODE:1005: f0 MOVX @DPTR,A +``` + +**Bit decode (0xEE = 1110_1110):** + +| Bit | Name | Value | Meaning | +|-----|------|-------|---------| +| 7 | IFCLKSRC | 1 | Internal clock source | +| 6 | 3048MHZ | 1 | 48 MHz IFCLK frequency | +| 5 | IFCLKOE | 1 | IFCLK pin drives output (available to BCM4500) | +| 4 | IFCLKPOL | 0 | Non-inverted clock polarity | +| 3 | ASYNC | 1 | Asynchronous GPIF (no IFCLK-synchronized handshake) | +| 2 | GSTATE | 1 | GPIF state machine outputs visible on PORTE (debug) | +| 1:0 | IFCFG | 10 | **GPIF internal master mode** | + +The FX2 operates as a GPIF master, reading data from the BCM4500's parallel transport stream output. Asynchronous mode means the GPIF uses RDY pin handshaking rather than clock-edge sampling. The 48 MHz IFCLK output provides a reference clock to the BCM4500, though actual data capture is handshake-controlled. + +This value is identical across all three firmware versions. + +### Prior IFCONFIG Value + +During early init (FUN_CODE_10d9), IFCONFIG is temporarily set to 0xCA before FUN_CODE_1000 overwrites it: + +```asm +; Early init - temporary IFCONFIG +CODE:10f0: 90e601 MOV DPTR,#0xe601 +CODE:10f3: 74ca MOV A,#0xca ; 0xCA = internal 48MHz, IFCLKOE, no GSTATE, GPIF +CODE:10f5: f0 MOVX @DPTR,A +``` + +0xCA (1100_1010): Same as 0xEE but GSTATE=0 and ASYNC=0. The final value (0xEE) enables async mode and debug state output. + +--- + +## 2. FIFO Reset Sequence + +All endpoint FIFOs are reset during initialization using the Cypress-prescribed procedure. + +**Register: FIFORESET (0xE604)** + +```asm +; Rev.2 FUN_CODE_10d9 at CODE:1130-1152 +CODE:1130: 90e604 MOV DPTR,#0xe604 ; FIFORESET +CODE:1133: 7480 MOV A,#0x80 ; Bit 7 = NAKALL: NAK all host transfers +CODE:1135: f0 MOVX @DPTR,A +CODE:1136: 00 00 00 ; mandatory 3-NOP sync delay +CODE:1139: 7402 MOV A,#0x02 ; Reset EP2 FIFO +CODE:113b: f0 MOVX @DPTR,A +CODE:113c: 00 00 00 +CODE:113f: 7404 MOV A,#0x04 ; Reset EP4 FIFO +CODE:1141: f0 MOVX @DPTR,A +CODE:1142: 00 00 00 +CODE:1145: 7406 MOV A,#0x06 ; Reset EP6 FIFO +CODE:1147: f0 MOVX @DPTR,A +CODE:1148: 00 00 00 +CODE:114b: 7408 MOV A,#0x08 ; Reset EP8 FIFO +CODE:114d: f0 MOVX @DPTR,A +CODE:114e: 00 00 00 +CODE:1151: e4 CLR A ; NAKALL = 0 (resume) +CODE:1152: f0 MOVX @DPTR,A +``` + +The sequence: NAKALL on -> reset EP2 -> EP4 -> EP6 -> EP8 -> NAKALL off. Triple-NOP delays between writes are required by the FX2 architecture: XRAM register writes take 2 cycles to propagate, and back-to-back writes to the same register need at least 3 instruction cycles between them. + +--- + +## 3. Endpoint FIFO Configuration + +### EP2FIFOCFG (0xE618) = 0x0C + +```asm +; Rev.2 FUN_CODE_10d9 at CODE:1156 +CODE:1156: 90e618 MOV DPTR,#0xe618 ; EP2FIFOCFG +CODE:1159: 740c MOV A,#0x0c ; 0x0C +CODE:115b: f0 MOVX @DPTR,A +``` + +**Bit decode (0x0C = 0000_1100):** + +| Bit | Name | Value | Meaning | +|-----|------|-------|---------| +| 4 | INFM1 | 0 | IN endpoint: packet count not decremented | +| 3 | AUTOIN | 1 | **Auto-commit IN packets when FIFO buffer full** | +| 2 | ZEROLENIN | 1 | Allow zero-length IN packets | +| 1 | (reserved) | 0 | -- | +| 0 | WORDWIDE | 0 | **8-bit data path** (not 16-bit) | + +The AUTOIN bit is critical: when the GPIF engine fills an EP2 FIFO buffer to the configured packet size, the FX2 hardware automatically arms the buffer for USB transfer. No firmware intervention is needed in the data path. The 8-bit WORDWIDE setting matches the BCM4500's 8-bit parallel transport stream output. + +### Other Endpoint FIFOs (All Disabled) + +```asm +CODE:115f: e4 CLR A ; A = 0 +CODE:1160: 90e619 MOV DPTR,#0xe619 ; EP4FIFOCFG = 0x00 +CODE:1163: f0 MOVX @DPTR,A +CODE:1167: 90e61a MOV DPTR,#0xe61a ; EP6FIFOCFG = 0x00 +CODE:116a: f0 MOVX @DPTR,A +CODE:116e: 90e61b MOV DPTR,#0xe61b ; EP8FIFOCFG = 0x00 +CODE:1171: f0 MOVX @DPTR,A +``` + +Only EP2 is configured for streaming. EP4/EP6/EP8 FIFOs are left in default (manual/disabled) state. + +--- + +## 4. GPIF Waveform and Control Configuration + +### Init Table-Driven Configuration + +The GPIF waveform descriptors (0xE400-0xE47F), GPIFIDLECTL (0xE660), GPIFCTLCFG (0xE661), GPIFWFSELECT (0xE662), and related registers are programmed via a compressed init table processed during the main() entry point, before the main loop begins. + +| Version | Init Table Address | Parser Function | +|---------|-------------------|-----------------| +| v2.06 | CODE:0B46 | main (0x188D) | +| v2.13 FW1 | CODE:0B88 | main_entry (0x170D) | +| Rev.2 | CODE:0B48 | main_init (0x15A6) | + +The init table uses a custom encoding: +- **Control byte** bits [7:6] select the write mode (IRAM, XRAM 2-byte addr, bit manipulation, XRAM 1-byte addr) +- **Control byte** bits [5:0] encode the byte count (extended to 2 bytes if bit 5 is set) +- Data bytes follow, written sequentially to the target address range + +The table writes configuration data to the 0xE0xx scratch RAM area (device descriptors, I2C addresses, modulation parameters) and to the 0xE4xx/0xE6xx GPIF/USB register space. The GPIF waveform descriptors occupy 128 bytes at 0xE400-0xE47F and define the state machine transitions for reading data from the BCM4500. + +### GPIF Waveform Structure + +The GPIF waveform data embedded in the init table contains 4 waveform slots (32 bytes each): + +The waveform data visible in the init table (from 0x0BAD region in Rev.2) includes the pattern: +``` +01 01 01 01 01 01 07 00 01 00 00 00 00 00 00 +F0 F0 F0 F0 F0 F0 F0 F0 00 3F 00 00 00 00 3F +``` + +This is characteristic of a simple single-state GPIF read waveform: +- **States 0-5**: CTL outputs = 0x01 (single control line asserted), length = 1 IFCLK +- **State 6**: CTL = 0x07, length = 0 (idle/terminate state) +- **Opcode/branch**: 0x00 (SDP = sample data point, branch idle) +- **Output/logic**: 0xF0 pattern (FIFO write flags) + +The waveform programs a straightforward "assert read strobe, capture data, de-assert" cycle that reads one byte per GPIF transaction from the BCM4500's parallel port into the EP2 FIFO. + +### REVCTL (0xE60B) = 0x03 + +```asm +CODE:1127: 90e60b MOV DPTR,#0xe60b ; REVCTL +CODE:112a: 7403 MOV A,#0x3 +CODE:112c: f0 MOVX @DPTR,A +``` + +REVCTL = 0x03: Both NOAUTOARM and SKIPCOMMIT bits set. This disables the automatic arming of endpoint buffers on access and skips the auto-commit behavior. Combined with EP2FIFOCFG.AUTOIN=1, this means the GPIF engine explicitly controls when data is committed -- the AUTOIN triggers on FIFO fullness, not on CPU access. + +### GPIFSGLDATH (SFR 0xBB) - GPIF Trigger Register + +The SFR at address 0xBB is used as the GPIF trigger/status register: + +- **Read**: Bit 7 (DONE) = 1 when GPIF is idle +- **Write**: Bits [1:0] = endpoint select (00=EP2), Bit 2 = read direction + +```asm +; Poll for GPIF completion +CODE:0dca: e5bb MOV A,0xbb ; Read GPIFTRIG +CODE:0dcc: 30e7fb JNB 0xe7,0x0dca ; Loop until bit 7 (DONE) = 1 + +; Trigger next GPIF read into EP2 +CODE:0dd2: 75bb04 MOV 0xbb,#0x4 ; GPIFTRIG = 0x04 (read, EP2) +``` + +--- + +## 5. ARM_TRANSFER (Vendor Command 0x85) + +### Dispatch + +The host issues USB vendor command 0x85 to start or stop the MPEG-2 transport stream. The command dispatches via the vendor command jump table at CODE:0x0076: + +| Version | Jump Table Entry | Handler Address | GPIF Control Fn | +|---------|-----------------|-----------------|-----------------| +| v2.06 | `21 10` (AJMP) | CODE:0110 | LCALL 0x1919 | +| v2.13 FW1 | `21 10` (AJMP) | CODE:0110 | LCALL 0x1800 | +| Rev.2 | `01 fa` (AJMP) | CODE:00FA | LCALL 0x0D7C | + +The handler logic is identical across versions despite the address differences: + +```asm +; Rev.2 ARM_TRANSFER handler at CODE:00FA (v2.06/v2.13 at CODE:0110) +CODE:00fa: e54e MOV A,0x4e ; Read config byte +CODE:00fc: 30e00d JNB 0xe0,0x010c ; Test bit 0: demodulator active? +CODE:00ff: 90e6ba MOV DPTR,#0xe6ba ; Read USB SETUP wValue (low byte) +CODE:0102: e0 MOVX A,@DPTR +CODE:0103: 24ff ADD A,#0xff ; Set CY if wValue != 0 +CODE:0105: 9206 MOV 0x06,CY ; arm_flag = (wValue != 0) +CODE:0107: 120d7c LCALL 0x0d7c ; Call GPIF control function +CODE:010a: 8005 SJMP 0x0111 ; Skip to done +CODE:010c: c206 CLR 0x06 ; arm_flag = 0 (force stop if no demod) +CODE:010e: 120d7c LCALL 0x0d7c ; Call GPIF control function +CODE:0111: e4 CLR A +CODE:0112: 90e68b MOV DPTR,#0xe68b ; EP0BCL = 0 (acknowledge control transfer) +CODE:0115: f0 MOVX @DPTR,A +``` + +The handler checks bit 0 of the configuration byte (demodulator present/active). If active, it reads wValue from the USB SETUP packet: wValue=1 means start streaming, wValue=0 means stop. If the demodulator is not active, it forces a stop. + +### GPIF Control Function - START Path + +When arm_flag=1 (start streaming), the GPIF control function at 0x0D7C (Rev.2) / 0x1800 (v2.13) / 0x1919 (v2.06) executes: + +```c +// Decompiled from Rev.2 FUN_CODE_0d7c -- START STREAMING +if (arm_flag != 0 && config_byte.7 == 0) { + // Not already streaming -- initialize GPIF transfer + + config_byte |= 0x80; // Mark streaming active + + // 1. Set GPIF transaction count (0xE630:0xE631) + GPIFTCB3 = 0x80; // 0xE630 = 0x80 + GPIFTCB2 = 0x00; // 0xE631 = 0x00 + // Transaction count = 0x8000_0000 (2 GB) -- effectively infinite + + // 2. Clear GPIF address registers + *(0xE6D2) = 0x00; // Address high = 0 + *(0xE6D3) = 0x01; // Address low = 1 (initial) + EP2FIFOBCL = 0x00; // 0xE6F1: Reset byte count + *(0xE6D3) = 0x00; // Address low = 0 + + // 3. Initial GPIF trigger via XRAM registers + *(0xE6D0) = 0x02; // Trigger/config = EP2 read + *(0xE6D1) = 0x00; // Trigger latch + + // 4. Assert BCM4500 transport stream enable + P3 &= ~0x20; // P3.5 LOW = TS_EN asserted + + // 5. Wait for GPIF completion + while (!(GPIFTRIG & 0x80)) {} // Poll SFR 0xBB bit 7 (DONE) + + // 6. Re-enable P3.5 + P3 |= 0x20; // P3.5 HIGH + + // 7. Trigger continuous GPIF read into EP2 + GPIFTRIG = 0x04; // SFR 0xBB = read EP2 + + // 8. Signal data path active + P0 &= ~0x80; // P0.7 LOW = streaming active indicator +} +``` + +**Disassembly (Rev.2 at CODE:0D7C):** +```asm +; START path (arm_flag = 1, config.7 = 0) +CODE:0d84: 434e80 ORL 0x4e,#0x80 ; config_byte |= 0x80 (streaming flag) + +CODE:0d87: 90e630 MOV DPTR,#0xe630 ; GPIFTCB3 +CODE:0d8a: 7480 MOV A,#0x80 +CODE:0d8c: f0 MOVX @DPTR,A ; GPIFTCB3 = 0x80 + +CODE:0d90: e4 CLR A +CODE:0d91: 90e631 MOV DPTR,#0xe631 ; GPIFTCB2 +CODE:0d94: f0 MOVX @DPTR,A ; GPIFTCB2 = 0x00 + +CODE:0d98: 90e6d2 MOV DPTR,#0xe6d2 +CODE:0d9b: f0 MOVX @DPTR,A ; [0xE6D2] = 0x00 + +CODE:0d9f: 90e6d3 MOV DPTR,#0xe6d3 +CODE:0da2: 04 INC A ; A = 1 +CODE:0da3: f0 MOVX @DPTR,A ; [0xE6D3] = 0x01 + +CODE:0da7: e4 CLR A +CODE:0da8: 90e6f1 MOV DPTR,#0xe6f1 ; EP2FIFOBCL (byte count low) +CODE:0dab: f0 MOVX @DPTR,A ; EP2FIFOBCL = 0x00 + +CODE:0daf: 90e6d3 MOV DPTR,#0xe6d3 +CODE:0db2: f0 MOVX @DPTR,A ; [0xE6D3] = 0x00 + +CODE:0db6: 90e6d0 MOV DPTR,#0xe6d0 +CODE:0db9: 7402 MOV A,#0x2 +CODE:0dbb: f0 MOVX @DPTR,A ; [0xE6D0] = 0x02 + +CODE:0dbf: e4 CLR A +CODE:0dc0: 90e6d1 MOV DPTR,#0xe6d1 +CODE:0dc3: f0 MOVX @DPTR,A ; [0xE6D1] = 0x00 + +CODE:0dc7: 53b0df ANL 0xb0,#0xdf ; P3 &= 0xDF -> P3.5 = 0 (TS_EN assert) + +CODE:0dca: e5bb MOV A,0xbb ; Read GPIFTRIG status +CODE:0dcc: 30e7fb JNB 0xe7,0x0dca ; Wait for DONE bit + +CODE:0dcf: 43b020 ORL 0xb0,#0x20 ; P3 |= 0x20 -> P3.5 = 1 +CODE:0dd2: 75bb04 MOV 0xbb,#0x4 ; GPIFTRIG = 0x04 (read EP2) + +CODE:0dd8: 53807f ANL 0x80,#0x7f ; P0 &= 0x7F -> P0.7 = 0 +CODE:0ddb: 22 RET +``` + +### GPIF Control Function - STOP Path + +When arm_flag=0 (stop streaming) and config_byte.7=1 (currently streaming): + +```c +// Decompiled from Rev.2 FUN_CODE_0d7c -- STOP STREAMING +if (arm_flag == 0 && config_byte.7 == 1) { + // Currently streaming -- abort GPIF and flush + + P0 |= 0x80; // P0.7 HIGH = streaming stopped + EP2FIFOBCH = 0xFF; // 0xE6F5: Force byte count high = 0xFF + // (skip/flush current FIFO packet) + + while (!(GPIFTRIG & 0x80)) {} // Wait for GPIF to go idle + + OUTPKTEND = 0x82; // 0xE648 = 0x82: Force-commit EP2 packet + // Bit 7 = skip, bits[3:0] = EP2 + config_byte &= ~0x80; // Clear streaming flag + + P3 |= 0xE0; // P3.7:5 = 1 (de-assert all control lines) +} +``` + +**Disassembly (Rev.2 at CODE:0DDC):** +```asm +; STOP path (arm_flag = 0, config.7 = 1) +CODE:0de1: 438080 ORL 0x80,#0x80 ; P0 |= 0x80 -> P0.7 = 1 (stopped) + +CODE:0de4: 90e6f5 MOV DPTR,#0xe6f5 ; EP2FIFOBCH (byte count high) +CODE:0de7: 74ff MOV A,#0xff +CODE:0de9: f0 MOVX @DPTR,A ; = 0xFF (force flush) + +CODE:0dea: e5bb MOV A,0xbb ; Read GPIFTRIG +CODE:0dec: 30e7fb JNB 0xe7,0x0dea ; Wait for DONE + +CODE:0def: 90e648 MOV DPTR,#0xe648 ; OUTPKTEND +CODE:0df2: 7482 MOV A,#0x82 ; Skip=1, EP2 +CODE:0df4: f0 MOVX @DPTR,A ; Force-commit/skip EP2 packet + +CODE:0df5: 534e7f ANL 0x4e,#0x7f ; config_byte &= 0x7F (clear streaming) +CODE:0df8: 43b0e0 ORL 0xb0,#0xe0 ; P3 |= 0xE0 (de-assert controls) +CODE:0dfb: 22 RET +``` + +**OUTPKTEND (0xE648) = 0x82** is notable: bit 7 set means "skip" (discard partially filled packet), bits [3:0] = 2 = EP2. This ensures any partial FIFO buffer is flushed when stopping the stream. + +--- + +## 6. INT4/INT6 Interrupt Handlers (GPIF/FIFO Events) + +### Rev.2 v2.10.4: Shared Handler via Trampoline + +Both INT4 and INT6 vectors jump to the same handler: + +```asm +CODE:0043: 021200 LJMP 0x1200 ; INT4_FX2_vector -> trampoline +CODE:0053: 021200 LJMP 0x1200 ; INT6_FX2_vector -> trampoline + +; Trampoline at 0x1200 +CODE:1200: 022084 LJMP 0x2084 ; -> GPIF_INT4_INT6_handler +``` + +**GPIF_INT4_INT6_handler (CODE:2084):** +```asm +CODE:2084: c0e0 PUSH A ; Save accumulator +CODE:2086: c083 PUSH DPH ; Save DPTR high +CODE:2088: c082 PUSH DPL ; Save DPTR low + +CODE:208a: d201 SETB 0x01 ; Set bit flag _0_1 (GPIF event pending) + +CODE:208c: 5391ef ANL 0x91,#0xef ; Clear EXIF.4 (INT4/INT6 IRQ flag) + +CODE:208f: 90e65d MOV DPTR,#0xe65d ; GPIFIRQ (GPIF Interrupt Request) +CODE:2092: 7401 MOV A,#0x1 +CODE:2094: f0 MOVX @DPTR,A ; Clear GPIFIRQ bit 0 + +CODE:2095: d082 POP DPL ; Restore context +CODE:2097: d083 POP DPH +CODE:2099: d0e0 POP A +CODE:209b: 32 RETI ; Return from interrupt +``` + +The handler is minimal: it sets a software flag (_0_1) and clears the hardware interrupt. The actual GPIF processing happens in the main loop when this flag is polled. + +### v2.13 FW1: Same Pattern + +```asm +CODE:0043: 021500 LJMP 0x1500 ; INT4 -> thunk_FUN_CODE_2252 +CODE:1500: 022252 LJMP 0x2252 ; -> FUN_CODE_2252 +``` + +**FUN_CODE_2252 (CODE:2252):** identical logic, sets _0_6 flag, clears EXIF.4 and GPIFIRQ.0. + +### v2.06: Different Vector Dispatch + +In v2.06, INT4 and INT6 both jump to 0x1600 which is a jump table indexed by the interrupt source register. The GPIF-related entry jumps to the same pattern (set flag, clear IRQ, return). + +### INT2 (USB/GPIF Combined Vector) + +The INT2 vector at CODE:0033 handles USB/GPIF combined interrupts: + +```asm +; Rev.2 +CODE:0033: 02223f LJMP 0x223f ; USB_GPIF_handler + +; USB_GPIF_handler at CODE:223F +CODE:223f: c0e0 PUSH A +CODE:2241: 53d8ef ANL 0xd8,#0xef ; Clear CCON.4 (PCA counter flag) +CODE:2244: d0e0 POP A +CODE:2246: 32 RETI +``` + +This handler simply clears the PCA (Programmable Counter Array) interrupt flag. The actual USB processing is handled by polling in the main loop. + +--- + +## 7. FLOWSTATE Configuration + +During initialization (FUN_CODE_09a9 at CODE:0AEF), the GPIF flow state machine is configured: + +```asm +CODE:0aef: 90e668 MOV DPTR,#0xe668 ; FLOWSTATEA +CODE:0af2: e0 MOVX A,@DPTR +CODE:0af3: 4409 ORL A,#0x09 ; Set bits 0 and 3 +CODE:0af5: f0 MOVX @DPTR,A +``` + +**FLOWSTATEA (0xE668) |= 0x09:** + +| Bit | Value | Meaning | +|-----|-------|---------| +| 0 | 1 | FSEN: Flow State enable -- GPIF uses flow state logic | +| 3 | 1 | FS[3]: Flow state flag -- specific to waveform design | + +The flow state machine controls automated GPIF-to-FIFO data transfers. With FSEN=1, the GPIF engine can automatically re-trigger transactions when FIFO space is available, creating a continuous streaming pipeline without firmware intervention after initial setup. + +### GPIF Interrupt Enable + +```asm +CODE:0af6: 90e65c MOV DPTR,#0xe65c ; GPIFIE +CODE:0af9: e0 MOVX A,@DPTR +CODE:0afa: 443d ORL A,#0x3d ; Enable bits 0,2,3,4,5 +CODE:0afc: f0 MOVX @DPTR,A +``` + +**GPIFIE (0xE65C) |= 0x3D = 0011_1101:** + +| Bit | Name | Enabled | Purpose | +|-----|------|---------|---------| +| 0 | GPIFWF | Yes | Waveform completion interrupt | +| 1 | (reserved) | No | -- | +| 2 | GPIFTCEXP | Yes | Transaction count expired | +| 3 | GPIFGPIFDONE | Yes | GPIF operation done | +| 4 | GPIFFF | Yes | FIFO flag interrupt | +| 5 | GPIFWF2 | Yes | Waveform 2 completion | + +These interrupts drive the INT4/INT6 handlers described in section 6. The combination of TC expire, DONE, and FIFO flag interrupts allows the main loop to manage the streaming pipeline: restart GPIF when buffers become available, handle end-of-transfer, and detect error conditions. + +--- + +## 8. GPIF Pin Assignments + +### Control Outputs (CTL[5:0]) + +Based on the init table and the P3 register manipulation in the GPIF control function: + +| Pin | Direction | Function | Start State | Active State | +|-----|-----------|----------|-------------|--------------| +| P3.5 | Output | TS_EN (Transport Stream Enable) | HIGH (inactive) | LOW (active) | +| P3.6 | Output | BCM4500 control line | HIGH | LOW | +| P3.7 | Output | BCM4500 control line | HIGH | HIGH | +| P0.7 | Output | Host streaming indicator | HIGH (idle) | LOW (streaming) | + +### Data Bus + +The GPIF uses the FD[7:0] data bus (Port B/D depending on FX2 variant) in 8-bit mode (WORDWIDE=0). + +### Ready Signals (RDY[5:0]) + +The GPIF waveform references RDY pins for handshaking with the BCM4500. The specific RDY pin assignment is encoded in the waveform descriptors loaded from the init table. + +--- + +## 9. Main Loop Integration + +After initialization, the main loop (FUN_CODE_09a9 at CODE:0B0E) polls for GPIF events: + +```c +// Simplified main loop (Rev.2) +while (true) { + FUN_CODE_201e(); // Poll I2C / USB status + + if (gpif_event_flag) { // _0_1 set by INT4/INT6 handler + FUN_CODE_0319(); // Process vendor commands + gpif_event_flag = 0; + } + + if (gpif_done_flag) { // _0_3 set when GPIF transfer completes + FUN_CODE_2265(); // (stub -- no-op in Rev.2) + if (carry_set) { + gpif_done_flag = 0; + + // Re-arm GPIF or handle completion + while (true) { + FUN_CODE_1faa(); // Enter idle (PCON.0 = 1) + + if (remote_wakeup) break; + + // Check EP2CS for buffer availability + ep2cs = *(0xE682); // EP2CS register + if ((ep2cs & 0x80) && // EP2 busy + (ep2cs & 0x02)) // EP2 stall + continue; // Keep waiting + + if ((ep2cs & 0x40) && // EP2 full + (ep2cs & 0x01)) // EP2 empty + continue; // Keep waiting + + break; // Buffer available + } + + FUN_CODE_1eda(); // Handle USB re-enumerate if needed + FUN_CODE_2267(); // (stub -- no-op in Rev.2) + } + } +} +``` + +**Disassembly of the main loop polling (Rev.2 at CODE:0B0E):** +```asm +CODE:0b0e: 12201e LCALL 0x201e ; Poll I2C/USB +CODE:0b11: 300105 JNB 0x01,0x0b19 ; Check _0_1 (GPIF event flag) +CODE:0b14: 120319 LCALL 0x0319 ; Process vendor commands +CODE:0b17: c201 CLR 0x01 ; Clear flag +CODE:0b19: 3003f2 JNB 0x03,0x0b0e ; Check _0_3 (GPIF done flag) +CODE:0b1c: 122265 LCALL 0x2265 ; (no-op) +CODE:0b1f: 50ed JNC 0x0b0e ; If no carry, loop back + +CODE:0b21: c203 CLR 0x03 ; Clear GPIF done flag +CODE:0b23: 121faa LCALL 0x1faa ; Enter idle (PCON.0 = sleep) +CODE:0b26: 200016 JB 0x00,0x0b3f ; Remote wakeup -> skip + +; Poll EP2CS for buffer availability +CODE:0b29: 90e682 MOV DPTR,#0xe682 ; EP2CS register +CODE:0b2c: e0 MOVX A,@DPTR +CODE:0b2d: 30e704 JNB 0xe7,0x0b34 ; bit 7 = busy? +CODE:0b30: e0 MOVX A,@DPTR +CODE:0b31: 20e1ef JB 0xe1,0x0b23 ; bit 1 = stall? -> keep waiting +CODE:0b34: 90e682 MOV DPTR,#0xe682 +CODE:0b37: e0 MOVX A,@DPTR +CODE:0b38: 30e604 JNB 0xe6,0x0b3f ; bit 6 = full? +CODE:0b3b: e0 MOVX A,@DPTR +CODE:0b3c: 20e0e4 JB 0xe0,0x0b23 ; bit 0 = empty? -> keep waiting + +CODE:0b3f: 121eda LCALL 0x1eda ; USB re-enumerate check +CODE:0b42: 122267 LCALL 0x2267 ; (no-op) +CODE:0b45: 80c7 SJMP 0x0b0e ; Loop forever +``` + +The main loop uses CPU idle mode (PCON.0) between GPIF events, waking on interrupts. The EP2CS polling checks that EP2 buffers are available before re-arming the GPIF, preventing FIFO overrun. + +--- + +## 10. CPUCS and Clock Configuration + +**Register: CPUCS (0xE600)** + +Set during init (FUN_CODE_10d9): + +```asm +CODE:10e4: 90e600 MOV DPTR,#0xe600 ; CPUCS +CODE:10e7: e0 MOVX A,@DPTR ; Read current value +CODE:10e8: 54e5 ANL A,#0xe5 ; Clear bits 4,3,1 (CLKSPD, CLKINV) +CODE:10ea: 4410 ORL A,#0x10 ; Set bit 4 +CODE:10ec: f0 MOVX @DPTR,A ; Write back +``` + +CPUCS bits: +- Bits [4:3] CLKSPD = 10 = **48 MHz** CPU clock +- Other bits preserved from power-on defaults + +--- + +## 11. GPIO Pin Summary for Streaming + +| Pin | SFR | Direction | Streaming Active | Streaming Stopped | Function | +|-----|-----|-----------|-----------------|-------------------|----------| +| P0.2 | 0x80 | Out | Set during init | -- | BCM4500 config | +| P0.7 | 0x80 | Out | LOW | HIGH | Streaming status indicator | +| P3.5 | 0xB0 | Out | Pulsed LOW | HIGH | BCM4500 TS_EN (Transport Stream Enable) | +| P3.6 | 0xB0 | Out | Controlled | HIGH | BCM4500 control | +| P3.7 | 0xB0 | Out | Controlled | HIGH | BCM4500 control | + +During init (FUN_CODE_10d9): +```asm +CODE:111b: 758084 MOV 0x80,#0x84 ; P0 = 0x84 (bits 7,2 set) +CODE:1121: 75b0e1 MOV 0xb0,#0xe1 ; P3 = 0xE1 (bits 7,6,5,0 set) +``` + +P0 init = 0x84: P0.7=1 (streaming off), P0.2=1 (BCM4500 control) +P3 init = 0xE1: P3.7:5=1 (all control lines inactive), P3.0=1 + +--- + +## 12. Complete Streaming Sequence + +### Start (Host sends ARM_TRANSFER wValue=1): + +``` +1. Host -> USB vendor cmd 0x85, wValue=1 +2. FX2 checks demod active (config_byte bit 0) +3. FX2 sets arm_flag = 1 +4. gpif_ctrl(): + a. Set config_byte.7 = 1 (streaming active) + b. Load GPIF transaction count: GPIFTCB3:2 = 0x8000 (huge count) + c. Reset GPIF address and EP2 FIFO byte count + d. Initial GPIF setup via XRAM 0xE6D0-0xE6D3 + e. Assert P3.5 LOW -> BCM4500 transport stream output enabled + f. Wait for initial GPIF transaction to complete (poll GPIFTRIG.7) + g. De-assert P3.5 HIGH + h. Trigger continuous GPIF read: GPIFTRIG = 0x04 (read into EP2) + i. Set P0.7 LOW (streaming indicator) +5. GPIF engine now auto-reads BCM4500 data into EP2 FIFO +6. EP2FIFOCFG.AUTOIN commits full packets to USB automatically +7. Host reads EP2 (0x82) bulk IN pipe continuously +``` + +### Steady State: + +``` +BCM4500 data bus -> GPIF state machine read -> EP2 FIFO buffer + | | + | (8-bit async handshake via RDY/CTL) | AUTOIN = 1 + | | + v v + Continuous GPIF transactions Auto-commit when full + (FLOWSTATE re-triggers) (hardware, no CPU) + | + v + USB bulk IN transfer + (host polls EP2 0x82) +``` + +The FLOWSTATE engine (FLOWSTATEA bit 0 = FSEN) automatically re-triggers GPIF transactions when EP2 FIFO buffers become available after USB transfers complete. This creates a fully hardware-managed pipeline where the CPU only needs to handle exceptional conditions. + +### Stop (Host sends ARM_TRANSFER wValue=0): + +``` +1. Host -> USB vendor cmd 0x85, wValue=0 +2. gpif_ctrl(): + a. Set P0.7 HIGH (streaming stopped) + b. Write EP2FIFOBCH = 0xFF (force-flush current buffer) + c. Wait for GPIF idle (poll GPIFTRIG.7) + d. Write OUTPKTEND = 0x82 (skip/discard partial EP2 packet) + e. Clear config_byte.7 (streaming inactive) + f. Set P3.7:5 = 1 (de-assert all BCM4500 control lines) +``` + +--- + +## 13. Cross-Version Comparison + +The GPIF streaming path is functionally identical across all three firmware versions. The only differences are: + +| Aspect | v2.06 (port 8193) | v2.13 FW1 (port 8194) | Rev.2 v2.10.4 (port 8197) | +|--------|-------|-----------|---------------| +| ARM_TRANSFER handler | CODE:0110 | CODE:0110 | CODE:00FA | +| GPIF control function | CODE:1919 | CODE:1800 | CODE:0D7C | +| Config byte location | IRAM 0x6D | IRAM 0x4F | IRAM 0x4E | +| Arm bit flag | bit 0x09 | bit 0x02 | bit 0x06 | +| INT4/INT6 vector | 0x1600 (dispatch table) | 0x1500 (thunk to 0x2252) | 0x1200 (thunk to 0x2084) | +| GPIF ISR action | Set flag, clear EXIF/GPIFIRQ | Set flag, clear EXIF/GPIFIRQ | Set flag, clear EXIF/GPIFIRQ | +| EP2FIFOCFG value | 0x0C (AUTOIN, ZEROLENIN, 8-bit) | 0x0C | 0x0C | +| IFCONFIG value | 0xEE (GPIF master, async, 48MHz) | 0xEE | 0xEE | +| FLOWSTATEA setting | \|= 0x09 (FSEN + FS3) | \|= 0x09 | \|= 0x09 | +| GPIFIE setting | \|= 0x3D | \|= 0x3D | \|= 0x3D | + +The register sequences within the GPIF control function are byte-for-byte identical (the same XRAM addresses, same values, same NOP delays). Differences are limited to which IRAM locations store state variables and which bit-addressable flags are used. + +--- + +## 14. Register Reference + +### XRAM Registers Written During Streaming + +| Address | Name | Start Value | Stop Value | Notes | +|---------|------|-------------|------------|-------| +| 0xE600 | CPUCS | (val & 0xE5) \| 0x10 | -- | 48 MHz CPU clock | +| 0xE601 | IFCONFIG | 0xEE | -- | GPIF master, async, 48 MHz | +| 0xE604 | FIFORESET | 0x80/0x02/0x04/0x06/0x08/0x00 | -- | Init only | +| 0xE60B | REVCTL | 0x03 | -- | NOAUTOARM + SKIPCOMMIT | +| 0xE618 | EP2FIFOCFG | 0x0C | -- | AUTOIN, ZEROLENIN, 8-bit | +| 0xE630 | GPIFTCB3 | 0x80 | -- | TC = 0x8000xxxx | +| 0xE631 | GPIFTCB2 | 0x00 | -- | TC continued | +| 0xE648 | OUTPKTEND | -- | 0x82 | Skip EP2 (stop only) | +| 0xE65C | GPIFIE | \|= 0x3D | -- | WF, TC, DONE, FF, WF2 | +| 0xE65D | GPIFIRQ | 0x01 | -- | Clear in ISR | +| 0xE668 | FLOWSTATEA | \|= 0x09 | -- | FSEN + FS[3] | +| 0xE670 | GPIFHOLDAMOUNT | 0x00 | -- | No hold time | +| 0xE6D0 | (GPIF trigger/config) | 0x02 | -- | EP2 read setup | +| 0xE6D1 | (GPIF trigger latch) | 0x00 | -- | Trigger latch | +| 0xE6D2 | (GPIF addr high) | 0x00 | -- | Address = 0 | +| 0xE6D3 | (GPIF addr low) | 0x01 then 0x00 | -- | Address setup | +| 0xE6F1 | EP2FIFOBCL | 0x00 | -- | Reset byte count | +| 0xE6F5 | EP2FIFOBCH | -- | 0xFF | Force flush (stop only) | + +### SFR Registers + +| Address | Name | Start Value | Stop Value | Notes | +|---------|------|-------------|------------|-------| +| 0x80 | P0 | 0x84 | \|= 0x80 | Bit 7 = streaming indicator | +| 0x91 | EXIF | &= 0xEF | -- | Clear bit 4 (INT4/6 flag) | +| 0xB0 | P3 | 0xE1 | \|= 0xE0 | Bits 7:5 = BCM4500 control | +| 0xBB | GPIFTRIG | 0x04 (read EP2) | (poll) | Trigger / status | + +--- + +## 15. Timing Considerations + +- **GPIF clock**: 48 MHz internal (IFCONFIG IFCLKSRC=1, 3048MHZ=1) +- **CPU clock**: 48 MHz (CPUCS CLKSPD=10) +- **GPIF async mode**: Data capture not synchronized to IFCLK edges; uses RDY pin handshaking +- **NOP delays**: 3 NOPs between consecutive XRAM writes to same register (~62.5 ns at 48 MHz) +- **EP2 buffer commit**: Automatic via AUTOIN when FIFO reaches packet size +- **GPIF re-trigger**: Automatic via FLOWSTATE when EP2 buffer space available +- **Transport stream rate**: BCM4500 outputs at the satellite symbol rate (up to 30 Msps), but the effective byte rate depends on modulation and coding. USB 2.0 High Speed bulk bandwidth (480 Mbps theoretical, ~35 MB/s practical) is more than sufficient for DVB-S transport streams (typically 1-5 MB/s). + +--- + +## Sources + +- Ghidra decompilation/disassembly of firmware images on ports 8193, 8194, 8197 +- Cypress CY7C68013A (FX2LP) Technical Reference Manual, register map +- Linux kernel `gp8psk` driver (`drivers/media/usb/dvb-usb/gp8psk.c`) +- Prior analysis: `gp8psk-driver-analysis.md`, `firmware-analysis-v206-vs-v213.md` diff --git a/kernel-fw01-analysis.md b/kernel-fw01-analysis.md index 7a5dbd4..402b397 100644 --- a/kernel-fw01-analysis.md +++ b/kernel-fw01-analysis.md @@ -1,425 +1,425 @@ -# Genpix GP8PSK Kernel Firmware File Analysis - -## 1. Firmware File Search Results - -### Standard Locations Checked - -| Path | Result | -|------|--------| -| `/lib/firmware/dvb-usb-gp8psk-01.fw` | **Not found** | -| `/lib/firmware/dvb-usb-gp8psk-02.fw` | **Not found** | -| `/usr/lib/firmware/dvb-usb-gp8psk-01.fw` | **Not found** | -| `/usr/lib/firmware/dvb-usb-gp8psk-02.fw` | **Not found** | -| `/usr/share/firmware/` | Directory does not exist | - -### Package Search - -- `linux-firmware` (v20260110-1) is installed but contains **no gp8psk files** -- The `/usr/lib/firmware/WHENCE` manifest has **no gp8psk entry** -- `pacman -F dvb-usb-gp8psk-01.fw` returns no results -- no Arch package provides it -- `pacman -Ss gp8psk` finds nothing - -### linux-firmware Git Repository - -The upstream linux-firmware repository at `git.kernel.org` was checked. The gp8psk firmware files have **never been included** in the linux-firmware collection. Other DVB-USB firmware files exist (dib0700, it9135, terratec-h5-drxk), but gp8psk is absent. - -### Kernel get_dvb_firmware Script - -The kernel's `scripts/get_dvb_firmware` helper (which downloads firmware from various vendor sites) has **no entry for gp8psk**. The original firmware was presumably distributed by Genpix Electronics directly, likely packaged with their Windows BDA driver installer. - -### Filesystem-Wide Search - -A full filesystem search (`find / -name 'dvb-usb-gp8psk*'`) found only the compiled kernel module (`.ko.zst`), no `.fw` firmware files anywhere on the system. - -### Conclusion - -**Neither `dvb-usb-gp8psk-01.fw` nor `dvb-usb-gp8psk-02.fw` exist on this system or in any standard distribution channel.** The gp8psk firmware was never open-sourced or contributed to linux-firmware. - ---- - -## 2. Why SkyWalker-1 Works Without FW01/FW02 - -### The Driver Device Table Tells the Story - -From `gp8psk.c` (kernel source): - -```c -static struct dvb_usb_device_properties gp8psk_properties = { - .usb_ctrl = CYPRESS_FX2, - .firmware = "dvb-usb-gp8psk-01.fw", - // ... - .devices = { - { .name = "Genpix 8PSK-to-USB2 Rev.1 DVB-S receiver", - .cold_ids = { &gp8psk_usb_table[GENPIX_8PSK_REV_1_COLD], NULL }, // <-- HAS cold_ids - .warm_ids = { &gp8psk_usb_table[GENPIX_8PSK_REV_1_WARM], NULL }, - }, - { .name = "Genpix 8PSK-to-USB2 Rev.2 DVB-S receiver", - .cold_ids = { NULL }, // <-- NO cold_ids - .warm_ids = { &gp8psk_usb_table[GENPIX_8PSK_REV_2], NULL }, - }, - { .name = "Genpix SkyWalker-1 DVB-S receiver", - .cold_ids = { NULL }, // <-- NO cold_ids - .warm_ids = { &gp8psk_usb_table[GENPIX_SKYWALKER_1], NULL }, - }, - // ... - } -}; -``` - -**Only Rev.1 Cold (PID 0x0200) triggers firmware download.** When `cold_ids` is NULL, the DVB-USB framework skips the firmware loading step entirely. The device is assumed to already be in "warm" state. - -### FW01 Loading Path - -``` -USB device enumeration - | - +-- DVB-USB framework matches USB IDs - +-- Checks cold_ids vs warm_ids - | | - | +-- cold_ids match? -> dvb_usb_download_firmware() - | | | -> request_firmware("dvb-usb-gp8psk-01.fw") - | | | -> usb_cypress_load_firmware() [hexline parser] - | | | -> Device re-enumerates with warm PID - | | | - | +-- warm_ids match? -> Skip firmware, proceed to frontend attach - | - +-- SkyWalker-1 (PID 0x0203) -> warm_ids only -> NO FW01 NEEDED -``` - -### FW02 Loading Path - -From `gp8psk_power_ctrl()`: -```c -if (gp_product_id == USB_PID_GENPIX_8PSK_REV_1_WARM) // Only for Rev.1! - if (!(status & bm8pskFW_Loaded)) - if (gp8psk_load_bcm4500fw(d)) - return -EINVAL; -``` - -BCM4500 firmware loading (`dvb-usb-gp8psk-02.fw`) is **only attempted for Rev.1 Warm devices** (PID 0x0201). Rev.2 and SkyWalker devices have the BCM4500 firmware burned into ROM, so `bm8pskFW_Loaded` (bit 1 of `GET_8PSK_CONFIG`) is already set at boot. - -### Summary - -| Device | PID | Needs FW01? | Needs FW02? | Boot Source | -|--------|-----|-------------|-------------|-------------| -| Rev.1 Cold | 0x0200 | **YES** | -- | RAM (empty) | -| Rev.1 Warm | 0x0201 | No | **YES** | RAM (FW01 loaded) | -| Rev.2 | 0x0202 | No | No | EEPROM | -| SkyWalker-1 | 0x0203 | No | No | EEPROM | -| SkyWalker-2 | 0x0205 | No | No | EEPROM | -| SkyWalker CW3K | 0x0206 | No | No | EEPROM | - -**The SkyWalker-1 boots entirely from its onboard EEPROM. It never requests either firmware file from the host.** - ---- - -## 3. FW01 Expected Format: DVB-USB Binary Hexline - -### Format Specification - -The DVB-USB framework's `dvb_usb_get_hexline()` parser reads a **compact binary representation** of Intel HEX records. This is NOT standard Intel HEX text (`:10000000...`), nor the kernel's `ihex_binrec` format from ``. - -**Record structure:** - -``` -Offset Size Field ------- ---- ----- -0 1 len - Number of data bytes in this record -1 1 addr_lo - Target address, low byte -2 1 addr_hi - Target address, high byte -3 1 type - Record type (0x00=data, 0x01=EOF, 0x04=extended addr) -4 len data[] - Payload bytes -4+len 1 chk - Checksum byte -``` - -Total bytes per record: `len + 5` - -The parser advances position by `len + 5` each iteration. Type 0x04 records carry extended linear address bits in `data[0]` and `data[1]`, allowing 32-bit addressing. - -### Loading Mechanism - -`usb_cypress_load_firmware()` performs: -1. Halt FX2 CPU: write `0x01` to CPUCS register (0xE600) -2. For each hexline record: write `data[0..len-1]` to FX2 RAM at `addr` via 0xA0 vendor request -3. Restart FX2 CPU: write `0x00` to CPUCS register (0xE600) - -The 0xA0 vendor request is handled by the FX2's built-in boot ROM, which writes directly to program/data RAM regardless of whether user firmware is running. - -### Comparison with Other DVB-USB Firmware Files - -Other firmware files installed on this system confirm the format: - -| File | Size | First Record | -|------|------|-------------| -| `dvb-usb-dib0700-1.20.fw` | 33,768 bytes | len=2, addr=0x0000, type=0x04 (ext addr) | -| `dvb-usb-it9135-01.fw` | 8,128 bytes | len=3, addr=0x0000, type=0x03 | -| `dvb-usb-it9135-02.fw` | 5,834 bytes | len=3, addr=0x0000, type=0x03 | - ---- - -## 4. Our Extracted Firmware Format: Cypress C2 EEPROM Boot - -### C2 Header Structure - -All extracted firmware dumps are in **Cypress C2 IIC second-stage boot format**, as stored in the device's EEPROM. This format is read by the FX2's internal boot ROM on power-up. - -**C2 header (8 bytes):** - -``` -Offset Size Field ------- ---- ----- -0 1 marker - Always 0xC2 (indicates external memory, large code model) -1 2 VID - USB Vendor ID (little-endian) -> 0x09C0 (Genpix) -3 2 PID - USB Product ID (little-endian) -5 2 DID - Device ID (little-endian) -> 0x0000 -7 1 config - 0x40 (400kHz I2C bus speed) -``` - -**Followed by code segments:** - -``` -Offset Size Field ------- ---------- ----- -0 2 seg_len - Segment length (big-endian) -2 2 seg_addr - Target RAM address (big-endian) -4 seg_len data[] - Code/data bytes -``` - -**Terminated by:** -``` -0 2 0x8001 - High bit set signals terminator -2 2 entry - Entry point address (big-endian) -> 0xE600 (CPUCS) -``` - -### Decoded C2 Headers - -| File | VID | PID | Segments | Code Size | Entry | -|------|-----|-----|----------|-----------|-------| -| `skywalker1_eeprom.bin` (v2.06) | 0x09C0 | **0x0203** | 10 | 9,472 bytes | 0xE600 | -| `sw1_v213_fw_1_c2.bin` (v2.13.1) | 0x09C0 | **0x0203** | 10 | 9,322 bytes | 0xE600 | -| `sw1_v213_fw_2_c2.bin` (v2.13.2) | 0x09C0 | **0x0203** | 10 | 9,377 bytes | 0xE600 | -| `sw1_v213_fw_3_c2.bin` (v2.13.3) | 0x09C0 | **0x0203** | 10 | 9,369 bytes | 0xE600 | -| `rev2_v210_fw_1_c2.bin` (Rev2 v2.10) | 0x09C0 | **0x0202** | 9 | 8,843 bytes | 0xE600 | - -Note: The PID in the C2 header determines the USB Product ID that the FX2 enumerates with after booting. SkyWalker-1 variants all use PID 0x0203, while Rev.2 uses 0x0202. - -### C2 Segment Layout (All SkyWalker-1 Variants) - -All SkyWalker-1 C2 files use uniform 1023-byte segments (except the last): - -``` -Segment Address Length Notes -------- ------- ------ ----- -1 0x0000 1023 Contains reset vector, interrupt handlers -2 0x03FF 1023 -3 0x07FE 1023 -4 0x0BFD 1023 -5 0x0FFC 1023 -6 0x13FB 1023 -7 0x17FA 1023 -8 0x1BF9 1023 -9 0x1FF8 1023 -10 0x23F7 varies (115-265 bytes depending on version) -``` - -The 1023-byte segment size is the maximum the FX2 I2C EEPROM boot ROM reads per transaction (limited by internal buffer). - ---- - -## 5. Format Incompatibility: C2 vs Kernel Hexline - -The firmware as stored in EEPROM (C2 format) is **structurally different** from what the kernel expects (binary hexline format). They cannot be used interchangeably. - -| Property | C2 (EEPROM) | Hexline (Kernel FW01) | -|----------|-------------|-----------------------| -| Header | 8-byte C2 with VID/PID/DID | None | -| Address encoding | Big-endian 16-bit per segment | Little-endian split (addr_lo, addr_hi) per record | -| Data chunking | 1023-byte segments | Typically 16-byte records | -| Record overhead | 4 bytes per segment | 5 bytes per record | -| Terminator | 0x80xx + entry point | Type 0x01 EOF record | -| Entry point | Explicit in terminator | Implicit (CPUCS at 0xE600) | - -However, the **payload data is identical**. The "flat" extracted firmware (C2 segments concatenated, headers stripped) contains the same 8051 machine code that a hexline-format FW01 would carry. The difference is purely container format. - -### Theoretical Conversion: C2 -> Hexline - -A C2 file can be converted to the kernel's hexline format by: -1. Strip the 8-byte C2 header -2. For each segment: emit 16-byte hexline records with type=0x00, splitting the segment data -3. Append an EOF record (len=0, type=0x01) - -The resulting file would be the equivalent of `dvb-usb-gp8psk-01.fw` for that firmware version. For the v2.06 EEPROM (9,472 code bytes), this produces approximately 12,442 bytes in hexline format. - ---- - -## 6. FW02 (BCM4500 Demodulator Firmware) Analysis - -### Loading Protocol - -From the kernel source (`gp8psk_load_bcm4500fw()`), FW02 uses a **custom chunk protocol** sent via USB bulk endpoint: - -``` -Chunk format: - Byte 0: payload_length (N) - Bytes 1-3: header/address bytes (3 bytes) - Bytes 4..N+3: payload data - - Total chunk size = ptr[0] + 4 = payload_length + 4 - Maximum chunk size: 64 bytes (USB control transfer limit) - -Terminator: single byte 0xFF -``` - -The loading sequence: -1. Send `LOAD_BCM4500` command (0x88, wValue=1) to initiate transfer mode -2. Iterate through chunks until 0xFF terminator -3. Each chunk is sent via `dvb_usb_generic_write()` (bulk endpoint 0x01) - -### Relevance to SkyWalker-1 - -**FW02 is irrelevant for SkyWalker-1.** The BCM4500 demodulator firmware is burned into ROM on all Rev.2 and later hardware. The kernel driver explicitly checks: -```c -if (gp_product_id == USB_PID_GENPIX_8PSK_REV_1_WARM) // 0x0201 only -``` - -On the SkyWalker-1 (PID 0x0203), command 0x88 (`LOAD_BCM4500`) routes to the STALL handler in the FX2 firmware -- confirmed by Ghidra disassembly of all extracted firmware versions. - ---- - -## 7. Firmware Version Identification - -### Kernel dmesg Output - -``` -gp8psk: FW Version = 2.06.4 (0x20604) Build 2007/07/13 -gp8psk: usb in 149 operation failed. -gp8psk: failed to get FPGA version -gp8psk_fe: Frontend attached -gp8psk: found Genpix USB device pID = 203 (hex) -``` - -The version is read via USB command `GET_FW_VERS` (0x92), which returns 6 bytes: `[minor, build, major, day, month, year-2000]`. The FX2 firmware computes and returns this at runtime from internal constants -- the version bytes are not stored as a simple searchable pattern in the binary. - -### Kernel Firmware Revision Constants - -From `gp8psk-fe.h`: -```c -#define GP8PSK_FW_REV1 0x020604 // v2.06.4 -#define GP8PSK_FW_REV2 0x020704 // v2.07.4 -``` - -The kernel only knows about two firmware revisions. Our v2.10 and v2.13 firmwares are significantly newer and unknown to the kernel. - -### FPGA Version Failure - -``` -gp8psk: usb in 149 operation failed. -gp8psk: failed to get FPGA version -``` - -Command 0x95 (`GET_FPGA_VERS`, decimal 149) fails on the SkyWalker-1. This command likely targets a separate FPGA that exists only on certain hardware revisions, or the SkyWalker-1 firmware does not implement this vendor command. The driver logs the failure but continues normally. - -### Version Strings in Firmware - -No human-readable version strings were found in the v2.06 or Rev2 v2.10 firmware binaries. The v2.13.x variants contain a single notable string: - -``` -Offset 0x1880 (all three v2.13 sub-variants): -"Tampering is detected. Attempt is logged. Warranty is voided ! \n" -``` - -Followed by bytes `01 10 aa 82 02 41 41 83` -- likely I2C register write commands related to the tampering detection/logging mechanism. This string is absent from v2.06 and v2.10 firmware, indicating Genpix added anti-tampering measures in the v2.13 firmware generation. - ---- - -## 8. Binary Comparison: Extracted Firmware Dumps - -### File Sizes - -| File | Size | Format | -|------|------|--------| -| `skywalker1_eeprom_flat.bin` (v2.06.4) | 9,472 bytes | Flat binary | -| `sw1_v213_fw_1_flat.bin` (v2.13.1) | 9,322 bytes | Flat binary | -| `sw1_v213_fw_2_flat.bin` (v2.13.2) | 9,377 bytes | Flat binary | -| `sw1_v213_fw_3_flat.bin` (v2.13.3) | 9,369 bytes | Flat binary | -| `rev2_v210_fw_1_flat.bin` (Rev2 v2.10.4) | 8,843 bytes | Flat binary | - -### Byte-Level Similarity Matrix - -Percentage of matching bytes within the shared length of each pair: - -| | v2.06 | v2.13.1 | v2.13.2 | v2.13.3 | Rev2 v2.10 | -|---|---|---|---|---|---| -| **v2.06** | -- | 4.8% | 4.3% | 4.3% | 6.0% | -| **v2.13.1** | | -- | 57.2% | 59.4% | 8.0% | -| **v2.13.2** | | | -- | 83.5% | 5.8% | -| **v2.13.3** | | | | -- | 5.8% | -| **Rev2 v2.10** | | | | | -- | - -### Differing Byte Counts - -| Pair | Different Bytes | Shared Length | Mismatch Rate | -|------|----------------|---------------|---------------| -| v2.06 vs v2.13.1 | 8,878 | 9,322 | 95.2% | -| v2.06 vs Rev2 v2.10 | 8,312 | 8,843 | 94.0% | -| v2.13.1 vs Rev2 v2.10 | 8,133 | 8,843 | 92.0% | -| v2.13.1 vs v2.13.2 | 3,994 | 9,322 | 42.8% | -| v2.13.2 vs v2.13.3 | 1,549 | 9,369 | 16.5% | - -### Longest Identical Run - -| Pair | Longest Match | Starting Offset | -|------|--------------|-----------------| -| v2.06 vs v2.13.1 | 22 bytes | 0x0055 | -| v2.06 vs Rev2 v2.10 | 36 bytes | 0x080C | - -### Interpretation - -The very low byte-level similarity between different major versions (v2.06 vs v2.10 vs v2.13) indicates **complete recompilation** with different toolchains or linker configurations. Functions are relocated to different addresses even when their logic is identical. The first byte of every flat dump already differs (the second byte of the reset vector LJMP target), confirming different code layout starting from the entry point. - -Within the v2.13 family, FW2 and FW3 share 83.5% of bytes, reflecting minor hardware-specific changes (parallel bus timing, GPIO defaults, IRAM layout). FW1 differs more substantially (57-59% match) because it targets fundamentally different hardware (I2C vs parallel bus demodulator interface). - ---- - -## 9. Other Extracted Files - -### FX2 Internal ROM (`skywalker1_fx2_internal.bin`, 8,192 bytes) - -The FX2 internal ROM dump does **not** contain program code in a recognizable 8051 format. It appears to be the FX2's built-in boot ROM (mask ROM), which handles: -- USB enumeration in "unconfigured" state -- I2C EEPROM boot loading (reading C2 format images) -- 0xA0 vendor request handling (RAM write for host firmware upload) - -This ROM is identical across all Cypress CY7C68013A chips and is not Genpix-specific. - -### FX2 External Memory (`skywalker1_fx2_external.bin`, 65,536 bytes) - -A 64KB dump of the FX2's full external address space. The first ~9.5KB contains the same code as `skywalker1_eeprom_flat.bin`; the remainder is the full 64KB memory space including RAM, SFR shadows, and unused regions. - ---- - -## 10. Summary of Findings - -1. **Firmware files `dvb-usb-gp8psk-01.fw` and `dvb-usb-gp8psk-02.fw` do not exist** on this system, in linux-firmware, or in any standard distribution. They were never open-sourced. - -2. **SkyWalker-1 does not need either file.** The kernel driver only requests FW01 for Rev.1 Cold devices (PID 0x0200) and FW02 for Rev.1 Warm devices (PID 0x0201). The SkyWalker-1 boots from its onboard EEPROM and appears directly as a "warm" device (PID 0x0203). - -3. **FW01 format** would be DVB-USB binary hexline records (a compact binary encoding of Intel HEX), loaded via Cypress FX2 0xA0 vendor requests to CPUCS. Our extracted dumps are in Cypress C2 EEPROM boot format -- different container, identical payload. - -4. **FW02 format** is a custom Genpix chunk protocol (length byte + 3 header bytes + data, terminated by 0xFF), loaded via USB bulk transfers after a LOAD_BCM4500 command (0x88). This is only relevant for Rev.1 hardware. - -5. **The currently running firmware** is v2.06.4 (build 2007/07/13), matching the kernel's `GP8PSK_FW_REV1` constant (0x020604). This is the original factory firmware burned into the SkyWalker-1 EEPROM. - -6. **The v2.13 firmware** (extracted from the Windows updater) adds anti-tampering detection and supports three different hardware sub-variants. The v2.10 Rev.2 firmware is for a different product (PID 0x0202). - ---- - -## Sources - -- Linux kernel 6.16.5 source: `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h` -- Linux kernel 6.16.5 source: `drivers/media/dvb-frontends/gp8psk-fe.h` -- Linux kernel 6.16.5 source: `drivers/media/usb/dvb-usb/dvb-usb-firmware.c` -- Linux kernel 6.16.5 source: `include/linux/ihex.h` -- Firmware dumps: `/home/rpm/claude/ham/satellite/genpix/skywalker-1/firmware-dump/` -- `dmesg` output from running SkyWalker-1 hardware -- linux-firmware git repository (https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git) +# Genpix GP8PSK Kernel Firmware File Analysis + +## 1. Firmware File Search Results + +### Standard Locations Checked + +| Path | Result | +|------|--------| +| `/lib/firmware/dvb-usb-gp8psk-01.fw` | **Not found** | +| `/lib/firmware/dvb-usb-gp8psk-02.fw` | **Not found** | +| `/usr/lib/firmware/dvb-usb-gp8psk-01.fw` | **Not found** | +| `/usr/lib/firmware/dvb-usb-gp8psk-02.fw` | **Not found** | +| `/usr/share/firmware/` | Directory does not exist | + +### Package Search + +- `linux-firmware` (v20260110-1) is installed but contains **no gp8psk files** +- The `/usr/lib/firmware/WHENCE` manifest has **no gp8psk entry** +- `pacman -F dvb-usb-gp8psk-01.fw` returns no results -- no Arch package provides it +- `pacman -Ss gp8psk` finds nothing + +### linux-firmware Git Repository + +The upstream linux-firmware repository at `git.kernel.org` was checked. The gp8psk firmware files have **never been included** in the linux-firmware collection. Other DVB-USB firmware files exist (dib0700, it9135, terratec-h5-drxk), but gp8psk is absent. + +### Kernel get_dvb_firmware Script + +The kernel's `scripts/get_dvb_firmware` helper (which downloads firmware from various vendor sites) has **no entry for gp8psk**. The original firmware was presumably distributed by Genpix Electronics directly, likely packaged with their Windows BDA driver installer. + +### Filesystem-Wide Search + +A full filesystem search (`find / -name 'dvb-usb-gp8psk*'`) found only the compiled kernel module (`.ko.zst`), no `.fw` firmware files anywhere on the system. + +### Conclusion + +**Neither `dvb-usb-gp8psk-01.fw` nor `dvb-usb-gp8psk-02.fw` exist on this system or in any standard distribution channel.** The gp8psk firmware was never open-sourced or contributed to linux-firmware. + +--- + +## 2. Why SkyWalker-1 Works Without FW01/FW02 + +### The Driver Device Table Tells the Story + +From `gp8psk.c` (kernel source): + +```c +static struct dvb_usb_device_properties gp8psk_properties = { + .usb_ctrl = CYPRESS_FX2, + .firmware = "dvb-usb-gp8psk-01.fw", + // ... + .devices = { + { .name = "Genpix 8PSK-to-USB2 Rev.1 DVB-S receiver", + .cold_ids = { &gp8psk_usb_table[GENPIX_8PSK_REV_1_COLD], NULL }, // <-- HAS cold_ids + .warm_ids = { &gp8psk_usb_table[GENPIX_8PSK_REV_1_WARM], NULL }, + }, + { .name = "Genpix 8PSK-to-USB2 Rev.2 DVB-S receiver", + .cold_ids = { NULL }, // <-- NO cold_ids + .warm_ids = { &gp8psk_usb_table[GENPIX_8PSK_REV_2], NULL }, + }, + { .name = "Genpix SkyWalker-1 DVB-S receiver", + .cold_ids = { NULL }, // <-- NO cold_ids + .warm_ids = { &gp8psk_usb_table[GENPIX_SKYWALKER_1], NULL }, + }, + // ... + } +}; +``` + +**Only Rev.1 Cold (PID 0x0200) triggers firmware download.** When `cold_ids` is NULL, the DVB-USB framework skips the firmware loading step entirely. The device is assumed to already be in "warm" state. + +### FW01 Loading Path + +``` +USB device enumeration + | + +-- DVB-USB framework matches USB IDs + +-- Checks cold_ids vs warm_ids + | | + | +-- cold_ids match? -> dvb_usb_download_firmware() + | | | -> request_firmware("dvb-usb-gp8psk-01.fw") + | | | -> usb_cypress_load_firmware() [hexline parser] + | | | -> Device re-enumerates with warm PID + | | | + | +-- warm_ids match? -> Skip firmware, proceed to frontend attach + | + +-- SkyWalker-1 (PID 0x0203) -> warm_ids only -> NO FW01 NEEDED +``` + +### FW02 Loading Path + +From `gp8psk_power_ctrl()`: +```c +if (gp_product_id == USB_PID_GENPIX_8PSK_REV_1_WARM) // Only for Rev.1! + if (!(status & bm8pskFW_Loaded)) + if (gp8psk_load_bcm4500fw(d)) + return -EINVAL; +``` + +BCM4500 firmware loading (`dvb-usb-gp8psk-02.fw`) is **only attempted for Rev.1 Warm devices** (PID 0x0201). Rev.2 and SkyWalker devices have the BCM4500 firmware burned into ROM, so `bm8pskFW_Loaded` (bit 1 of `GET_8PSK_CONFIG`) is already set at boot. + +### Summary + +| Device | PID | Needs FW01? | Needs FW02? | Boot Source | +|--------|-----|-------------|-------------|-------------| +| Rev.1 Cold | 0x0200 | **YES** | -- | RAM (empty) | +| Rev.1 Warm | 0x0201 | No | **YES** | RAM (FW01 loaded) | +| Rev.2 | 0x0202 | No | No | EEPROM | +| SkyWalker-1 | 0x0203 | No | No | EEPROM | +| SkyWalker-2 | 0x0205 | No | No | EEPROM | +| SkyWalker CW3K | 0x0206 | No | No | EEPROM | + +**The SkyWalker-1 boots entirely from its onboard EEPROM. It never requests either firmware file from the host.** + +--- + +## 3. FW01 Expected Format: DVB-USB Binary Hexline + +### Format Specification + +The DVB-USB framework's `dvb_usb_get_hexline()` parser reads a **compact binary representation** of Intel HEX records. This is NOT standard Intel HEX text (`:10000000...`), nor the kernel's `ihex_binrec` format from ``. + +**Record structure:** + +``` +Offset Size Field +------ ---- ----- +0 1 len - Number of data bytes in this record +1 1 addr_lo - Target address, low byte +2 1 addr_hi - Target address, high byte +3 1 type - Record type (0x00=data, 0x01=EOF, 0x04=extended addr) +4 len data[] - Payload bytes +4+len 1 chk - Checksum byte +``` + +Total bytes per record: `len + 5` + +The parser advances position by `len + 5` each iteration. Type 0x04 records carry extended linear address bits in `data[0]` and `data[1]`, allowing 32-bit addressing. + +### Loading Mechanism + +`usb_cypress_load_firmware()` performs: +1. Halt FX2 CPU: write `0x01` to CPUCS register (0xE600) +2. For each hexline record: write `data[0..len-1]` to FX2 RAM at `addr` via 0xA0 vendor request +3. Restart FX2 CPU: write `0x00` to CPUCS register (0xE600) + +The 0xA0 vendor request is handled by the FX2's built-in boot ROM, which writes directly to program/data RAM regardless of whether user firmware is running. + +### Comparison with Other DVB-USB Firmware Files + +Other firmware files installed on this system confirm the format: + +| File | Size | First Record | +|------|------|-------------| +| `dvb-usb-dib0700-1.20.fw` | 33,768 bytes | len=2, addr=0x0000, type=0x04 (ext addr) | +| `dvb-usb-it9135-01.fw` | 8,128 bytes | len=3, addr=0x0000, type=0x03 | +| `dvb-usb-it9135-02.fw` | 5,834 bytes | len=3, addr=0x0000, type=0x03 | + +--- + +## 4. Our Extracted Firmware Format: Cypress C2 EEPROM Boot + +### C2 Header Structure + +All extracted firmware dumps are in **Cypress C2 IIC second-stage boot format**, as stored in the device's EEPROM. This format is read by the FX2's internal boot ROM on power-up. + +**C2 header (8 bytes):** + +``` +Offset Size Field +------ ---- ----- +0 1 marker - Always 0xC2 (indicates external memory, large code model) +1 2 VID - USB Vendor ID (little-endian) -> 0x09C0 (Genpix) +3 2 PID - USB Product ID (little-endian) +5 2 DID - Device ID (little-endian) -> 0x0000 +7 1 config - 0x40 (400kHz I2C bus speed) +``` + +**Followed by code segments:** + +``` +Offset Size Field +------ ---------- ----- +0 2 seg_len - Segment length (big-endian) +2 2 seg_addr - Target RAM address (big-endian) +4 seg_len data[] - Code/data bytes +``` + +**Terminated by:** +``` +0 2 0x8001 - High bit set signals terminator +2 2 entry - Entry point address (big-endian) -> 0xE600 (CPUCS) +``` + +### Decoded C2 Headers + +| File | VID | PID | Segments | Code Size | Entry | +|------|-----|-----|----------|-----------|-------| +| `skywalker1_eeprom.bin` (v2.06) | 0x09C0 | **0x0203** | 10 | 9,472 bytes | 0xE600 | +| `sw1_v213_fw_1_c2.bin` (v2.13.1) | 0x09C0 | **0x0203** | 10 | 9,322 bytes | 0xE600 | +| `sw1_v213_fw_2_c2.bin` (v2.13.2) | 0x09C0 | **0x0203** | 10 | 9,377 bytes | 0xE600 | +| `sw1_v213_fw_3_c2.bin` (v2.13.3) | 0x09C0 | **0x0203** | 10 | 9,369 bytes | 0xE600 | +| `rev2_v210_fw_1_c2.bin` (Rev2 v2.10) | 0x09C0 | **0x0202** | 9 | 8,843 bytes | 0xE600 | + +Note: The PID in the C2 header determines the USB Product ID that the FX2 enumerates with after booting. SkyWalker-1 variants all use PID 0x0203, while Rev.2 uses 0x0202. + +### C2 Segment Layout (All SkyWalker-1 Variants) + +All SkyWalker-1 C2 files use uniform 1023-byte segments (except the last): + +``` +Segment Address Length Notes +------- ------- ------ ----- +1 0x0000 1023 Contains reset vector, interrupt handlers +2 0x03FF 1023 +3 0x07FE 1023 +4 0x0BFD 1023 +5 0x0FFC 1023 +6 0x13FB 1023 +7 0x17FA 1023 +8 0x1BF9 1023 +9 0x1FF8 1023 +10 0x23F7 varies (115-265 bytes depending on version) +``` + +The 1023-byte segment size is the maximum the FX2 I2C EEPROM boot ROM reads per transaction (limited by internal buffer). + +--- + +## 5. Format Incompatibility: C2 vs Kernel Hexline + +The firmware as stored in EEPROM (C2 format) is **structurally different** from what the kernel expects (binary hexline format). They cannot be used interchangeably. + +| Property | C2 (EEPROM) | Hexline (Kernel FW01) | +|----------|-------------|-----------------------| +| Header | 8-byte C2 with VID/PID/DID | None | +| Address encoding | Big-endian 16-bit per segment | Little-endian split (addr_lo, addr_hi) per record | +| Data chunking | 1023-byte segments | Typically 16-byte records | +| Record overhead | 4 bytes per segment | 5 bytes per record | +| Terminator | 0x80xx + entry point | Type 0x01 EOF record | +| Entry point | Explicit in terminator | Implicit (CPUCS at 0xE600) | + +However, the **payload data is identical**. The "flat" extracted firmware (C2 segments concatenated, headers stripped) contains the same 8051 machine code that a hexline-format FW01 would carry. The difference is purely container format. + +### Theoretical Conversion: C2 -> Hexline + +A C2 file can be converted to the kernel's hexline format by: +1. Strip the 8-byte C2 header +2. For each segment: emit 16-byte hexline records with type=0x00, splitting the segment data +3. Append an EOF record (len=0, type=0x01) + +The resulting file would be the equivalent of `dvb-usb-gp8psk-01.fw` for that firmware version. For the v2.06 EEPROM (9,472 code bytes), this produces approximately 12,442 bytes in hexline format. + +--- + +## 6. FW02 (BCM4500 Demodulator Firmware) Analysis + +### Loading Protocol + +From the kernel source (`gp8psk_load_bcm4500fw()`), FW02 uses a **custom chunk protocol** sent via USB bulk endpoint: + +``` +Chunk format: + Byte 0: payload_length (N) + Bytes 1-3: header/address bytes (3 bytes) + Bytes 4..N+3: payload data + + Total chunk size = ptr[0] + 4 = payload_length + 4 + Maximum chunk size: 64 bytes (USB control transfer limit) + +Terminator: single byte 0xFF +``` + +The loading sequence: +1. Send `LOAD_BCM4500` command (0x88, wValue=1) to initiate transfer mode +2. Iterate through chunks until 0xFF terminator +3. Each chunk is sent via `dvb_usb_generic_write()` (bulk endpoint 0x01) + +### Relevance to SkyWalker-1 + +**FW02 is irrelevant for SkyWalker-1.** The BCM4500 demodulator firmware is burned into ROM on all Rev.2 and later hardware. The kernel driver explicitly checks: +```c +if (gp_product_id == USB_PID_GENPIX_8PSK_REV_1_WARM) // 0x0201 only +``` + +On the SkyWalker-1 (PID 0x0203), command 0x88 (`LOAD_BCM4500`) routes to the STALL handler in the FX2 firmware -- confirmed by Ghidra disassembly of all extracted firmware versions. + +--- + +## 7. Firmware Version Identification + +### Kernel dmesg Output + +``` +gp8psk: FW Version = 2.06.4 (0x20604) Build 2007/07/13 +gp8psk: usb in 149 operation failed. +gp8psk: failed to get FPGA version +gp8psk_fe: Frontend attached +gp8psk: found Genpix USB device pID = 203 (hex) +``` + +The version is read via USB command `GET_FW_VERS` (0x92), which returns 6 bytes: `[minor, build, major, day, month, year-2000]`. The FX2 firmware computes and returns this at runtime from internal constants -- the version bytes are not stored as a simple searchable pattern in the binary. + +### Kernel Firmware Revision Constants + +From `gp8psk-fe.h`: +```c +#define GP8PSK_FW_REV1 0x020604 // v2.06.4 +#define GP8PSK_FW_REV2 0x020704 // v2.07.4 +``` + +The kernel only knows about two firmware revisions. Our v2.10 and v2.13 firmwares are significantly newer and unknown to the kernel. + +### FPGA Version Failure + +``` +gp8psk: usb in 149 operation failed. +gp8psk: failed to get FPGA version +``` + +Command 0x95 (`GET_FPGA_VERS`, decimal 149) fails on the SkyWalker-1. This command likely targets a separate FPGA that exists only on certain hardware revisions, or the SkyWalker-1 firmware does not implement this vendor command. The driver logs the failure but continues normally. + +### Version Strings in Firmware + +No human-readable version strings were found in the v2.06 or Rev2 v2.10 firmware binaries. The v2.13.x variants contain a single notable string: + +``` +Offset 0x1880 (all three v2.13 sub-variants): +"Tampering is detected. Attempt is logged. Warranty is voided ! \n" +``` + +Followed by bytes `01 10 aa 82 02 41 41 83` -- likely I2C register write commands related to the tampering detection/logging mechanism. This string is absent from v2.06 and v2.10 firmware, indicating Genpix added anti-tampering measures in the v2.13 firmware generation. + +--- + +## 8. Binary Comparison: Extracted Firmware Dumps + +### File Sizes + +| File | Size | Format | +|------|------|--------| +| `skywalker1_eeprom_flat.bin` (v2.06.4) | 9,472 bytes | Flat binary | +| `sw1_v213_fw_1_flat.bin` (v2.13.1) | 9,322 bytes | Flat binary | +| `sw1_v213_fw_2_flat.bin` (v2.13.2) | 9,377 bytes | Flat binary | +| `sw1_v213_fw_3_flat.bin` (v2.13.3) | 9,369 bytes | Flat binary | +| `rev2_v210_fw_1_flat.bin` (Rev2 v2.10.4) | 8,843 bytes | Flat binary | + +### Byte-Level Similarity Matrix + +Percentage of matching bytes within the shared length of each pair: + +| | v2.06 | v2.13.1 | v2.13.2 | v2.13.3 | Rev2 v2.10 | +|---|---|---|---|---|---| +| **v2.06** | -- | 4.8% | 4.3% | 4.3% | 6.0% | +| **v2.13.1** | | -- | 57.2% | 59.4% | 8.0% | +| **v2.13.2** | | | -- | 83.5% | 5.8% | +| **v2.13.3** | | | | -- | 5.8% | +| **Rev2 v2.10** | | | | | -- | + +### Differing Byte Counts + +| Pair | Different Bytes | Shared Length | Mismatch Rate | +|------|----------------|---------------|---------------| +| v2.06 vs v2.13.1 | 8,878 | 9,322 | 95.2% | +| v2.06 vs Rev2 v2.10 | 8,312 | 8,843 | 94.0% | +| v2.13.1 vs Rev2 v2.10 | 8,133 | 8,843 | 92.0% | +| v2.13.1 vs v2.13.2 | 3,994 | 9,322 | 42.8% | +| v2.13.2 vs v2.13.3 | 1,549 | 9,369 | 16.5% | + +### Longest Identical Run + +| Pair | Longest Match | Starting Offset | +|------|--------------|-----------------| +| v2.06 vs v2.13.1 | 22 bytes | 0x0055 | +| v2.06 vs Rev2 v2.10 | 36 bytes | 0x080C | + +### Interpretation + +The very low byte-level similarity between different major versions (v2.06 vs v2.10 vs v2.13) indicates **complete recompilation** with different toolchains or linker configurations. Functions are relocated to different addresses even when their logic is identical. The first byte of every flat dump already differs (the second byte of the reset vector LJMP target), confirming different code layout starting from the entry point. + +Within the v2.13 family, FW2 and FW3 share 83.5% of bytes, reflecting minor hardware-specific changes (parallel bus timing, GPIO defaults, IRAM layout). FW1 differs more substantially (57-59% match) because it targets fundamentally different hardware (I2C vs parallel bus demodulator interface). + +--- + +## 9. Other Extracted Files + +### FX2 Internal ROM (`skywalker1_fx2_internal.bin`, 8,192 bytes) + +The FX2 internal ROM dump does **not** contain program code in a recognizable 8051 format. It appears to be the FX2's built-in boot ROM (mask ROM), which handles: +- USB enumeration in "unconfigured" state +- I2C EEPROM boot loading (reading C2 format images) +- 0xA0 vendor request handling (RAM write for host firmware upload) + +This ROM is identical across all Cypress CY7C68013A chips and is not Genpix-specific. + +### FX2 External Memory (`skywalker1_fx2_external.bin`, 65,536 bytes) + +A 64KB dump of the FX2's full external address space. The first ~9.5KB contains the same code as `skywalker1_eeprom_flat.bin`; the remainder is the full 64KB memory space including RAM, SFR shadows, and unused regions. + +--- + +## 10. Summary of Findings + +1. **Firmware files `dvb-usb-gp8psk-01.fw` and `dvb-usb-gp8psk-02.fw` do not exist** on this system, in linux-firmware, or in any standard distribution. They were never open-sourced. + +2. **SkyWalker-1 does not need either file.** The kernel driver only requests FW01 for Rev.1 Cold devices (PID 0x0200) and FW02 for Rev.1 Warm devices (PID 0x0201). The SkyWalker-1 boots from its onboard EEPROM and appears directly as a "warm" device (PID 0x0203). + +3. **FW01 format** would be DVB-USB binary hexline records (a compact binary encoding of Intel HEX), loaded via Cypress FX2 0xA0 vendor requests to CPUCS. Our extracted dumps are in Cypress C2 EEPROM boot format -- different container, identical payload. + +4. **FW02 format** is a custom Genpix chunk protocol (length byte + 3 header bytes + data, terminated by 0xFF), loaded via USB bulk transfers after a LOAD_BCM4500 command (0x88). This is only relevant for Rev.1 hardware. + +5. **The currently running firmware** is v2.06.4 (build 2007/07/13), matching the kernel's `GP8PSK_FW_REV1` constant (0x020604). This is the original factory firmware burned into the SkyWalker-1 EEPROM. + +6. **The v2.13 firmware** (extracted from the Windows updater) adds anti-tampering detection and supports three different hardware sub-variants. The v2.10 Rev.2 firmware is for a different product (PID 0x0202). + +--- + +## Sources + +- Linux kernel 6.16.5 source: `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h` +- Linux kernel 6.16.5 source: `drivers/media/dvb-frontends/gp8psk-fe.h` +- Linux kernel 6.16.5 source: `drivers/media/usb/dvb-usb/dvb-usb-firmware.c` +- Linux kernel 6.16.5 source: `include/linux/ihex.h` +- Firmware dumps: `/home/rpm/claude/ham/satellite/genpix/skywalker-1/firmware-dump/` +- `dmesg` output from running SkyWalker-1 hardware +- linux-firmware git repository (https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git) diff --git a/mcp/skywalker-mcp/.mcp.json b/mcp/skywalker-mcp/.mcp.json index 225f9cb..56ad3e8 100644 --- a/mcp/skywalker-mcp/.mcp.json +++ b/mcp/skywalker-mcp/.mcp.json @@ -1,8 +1,8 @@ -{ - "mcpServers": { - "skywalker-mcp": { - "command": "uv", - "args": ["run", "--directory", ".", "skywalker-mcp"] - } - } -} +{ + "mcpServers": { + "skywalker-mcp": { + "command": "uv", + "args": ["run", "--directory", ".", "skywalker-mcp"] + } + } +} diff --git a/mcp/skywalker-mcp/pyproject.toml b/mcp/skywalker-mcp/pyproject.toml index 047cadf..b1b214a 100644 --- a/mcp/skywalker-mcp/pyproject.toml +++ b/mcp/skywalker-mcp/pyproject.toml @@ -1,35 +1,35 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "skywalker-mcp" -version = "2026.2.17" -description = "MCP server for the Genpix SkyWalker-1 DVB-S USB receiver" -requires-python = ">=3.11" -authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}] -dependencies = [ - "fastmcp>=2.0", - "pyusb>=1.3", -] - -[project.scripts] -skywalker-mcp = "skywalker_mcp.server:main" - -[tool.hatch.build.targets.wheel] -packages = ["src/skywalker_mcp"] - -[project.optional-dependencies] -dev = [ - "pytest>=8.0", - "pytest-asyncio>=0.24", - "ruff>=0.9", -] - -[tool.ruff] -target-version = "py311" -line-length = 100 - -[tool.pytest.ini_options] -testpaths = ["tests"] -asyncio_mode = "auto" +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "skywalker-mcp" +version = "2026.2.17" +description = "MCP server for the Genpix SkyWalker-1 DVB-S USB receiver" +requires-python = ">=3.11" +authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}] +dependencies = [ + "fastmcp>=2.0", + "pyusb>=1.3", +] + +[project.scripts] +skywalker-mcp = "skywalker_mcp.server:main" + +[tool.hatch.build.targets.wheel] +packages = ["src/skywalker_mcp"] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", + "pytest-asyncio>=0.24", + "ruff>=0.9", +] + +[tool.ruff] +target-version = "py311" +line-length = 100 + +[tool.pytest.ini_options] +testpaths = ["tests"] +asyncio_mode = "auto" diff --git a/mcp/skywalker-mcp/src/skywalker_mcp/__init__.py b/mcp/skywalker-mcp/src/skywalker_mcp/__init__.py index c0988db..18e59e0 100644 --- a/mcp/skywalker-mcp/src/skywalker_mcp/__init__.py +++ b/mcp/skywalker-mcp/src/skywalker_mcp/__init__.py @@ -1,3 +1,3 @@ -"""MCP server for the Genpix SkyWalker-1 DVB-S USB receiver.""" - -__version__ = "2026.2.17" +"""MCP server for the Genpix SkyWalker-1 DVB-S USB receiver.""" + +__version__ = "2026.2.17" diff --git a/mcp/skywalker-mcp/src/skywalker_mcp/mock_device.py b/mcp/skywalker-mcp/src/skywalker_mcp/mock_device.py index 41e88f4..93955fa 100644 --- a/mcp/skywalker-mcp/src/skywalker_mcp/mock_device.py +++ b/mcp/skywalker-mcp/src/skywalker_mcp/mock_device.py @@ -1,136 +1,136 @@ -"""Mock SkyWalker1 device for testing without USB hardware. - -Used by: - - Server lifespan when SKYWALKER_MOCK=1 is set (integration testing) - - conftest.py fixtures for unit tests -""" - - -class MockSkyWalker1: - """Returns plausible data for all SkyWalker1 API calls without USB hardware.""" - - def __init__(self, verbose=False): - self.verbose = verbose - self._motor_halted = False - self._armed = False - self._lnb_on = True - self._calls = [] - - def _record(self, method, *args, **kwargs): - self._calls.append((method, args, kwargs)) - - def open(self): - self._record("open") - - def close(self): - self._record("close") - - def ensure_booted(self): - self._record("ensure_booted") - - def get_fw_version(self): - self._record("get_fw_version") - return {"version": "3.05.0-mock", "date": "2026-02-17", "raw": b"\x03\x05\x00"} - - def get_config(self): - self._record("get_config") - return 0x3F - - def get_usb_speed(self): - self._record("get_usb_speed") - return 2 - - def get_serial_number(self): - self._record("get_serial_number") - return bytes([0xDE, 0xAD, 0xBE, 0xEF]) - - def get_last_error(self): - self._record("get_last_error") - return 0x00 - - def signal_monitor(self): - self._record("signal_monitor") - return { - "snr_raw": 200, "snr_db": 8.5, "snr_pct": 42.5, - "agc1": 1200, "agc2": 800, "power_db": -45.3, - "locked": True, "lock": 0x1F, "status": 0x01, - } - - def get_stream_diag(self, reset=False): - self._record("get_stream_diag", reset=reset) - return {"poll_count": 100, "overflow_count": 0, "sync_loss": 0, "armed": self._armed} - - def sweep_spectrum(self, start_mhz, stop_mhz, step_mhz=5.0, dwell_ms=15): - self._record("sweep_spectrum", start_mhz, stop_mhz, step_mhz=step_mhz, dwell_ms=dwell_ms) - n_points = int((stop_mhz - start_mhz) / step_mhz) + 1 - freqs = [start_mhz + i * step_mhz for i in range(n_points)] - powers = [] - for f in freqs: - base = -50.0 - if abs(f - 1420.0) < 5: - base += 8.0 * (1.0 - abs(f - 1420.0) / 5.0) - powers.append(base) - raw = [(int((p + 60) * 100), 0) for p in powers] - return freqs, powers, raw - - def tune_monitor(self, sr_sps, freq_khz, mod_idx, fec_idx, dwell_ms): - self._record("tune_monitor", sr_sps, freq_khz, mod_idx, fec_idx, dwell_ms) - return { - "snr_raw": 180, "snr_db": 7.8, "snr_pct": 39.0, - "agc1": 1100, "agc2": 750, "power_db": -46.1, - "locked": True, "lock": 0x1F, "status": 0x01, - "dwell_ms": dwell_ms, - } - - def adaptive_blind_scan(self, freq_khz, sr_min, sr_max, sr_step): - self._record("adaptive_blind_scan", freq_khz, sr_min, sr_max, sr_step) - return {"freq_khz": freq_khz, "locked": True, "sr_sps": 20000000} - - def motor_halt(self): - self._record("motor_halt") - self._motor_halted = True - - def motor_drive_east(self, steps): - self._record("motor_drive_east", steps) - self._motor_halted = False - - def motor_drive_west(self, steps): - self._record("motor_drive_west", steps) - self._motor_halted = False - - def motor_goto_position(self, slot): - self._record("motor_goto_position", slot) - - def motor_goto_x(self, observer_lon, sat_lon): - self._record("motor_goto_x", observer_lon, sat_lon) - - def motor_store_position(self, slot): - self._record("motor_store_position", slot) - - def start_intersil(self, on=True): - self._record("start_intersil", on=on) - self._lnb_on = on - - def set_lnb_voltage(self, high): - self._record("set_lnb_voltage", high) - - def set_22khz_tone(self, on): - self._record("set_22khz_tone", on) - - def i2c_bus_scan(self): - self._record("i2c_bus_scan") - return [0x08, 0x10, 0x51] - - def i2c_raw_read(self, slave, register): - self._record("i2c_raw_read", slave, register) - return 0xAB - - def arm_transfer(self, on): - self._record("arm_transfer", on) - self._armed = on - - def read_stream(self, timeout=500): - self._record("read_stream", timeout=timeout) - if self._armed: - return bytes([0x47, 0x00, 0x00, 0x10] + [0xFF] * 184) - return None +"""Mock SkyWalker1 device for testing without USB hardware. + +Used by: + - Server lifespan when SKYWALKER_MOCK=1 is set (integration testing) + - conftest.py fixtures for unit tests +""" + + +class MockSkyWalker1: + """Returns plausible data for all SkyWalker1 API calls without USB hardware.""" + + def __init__(self, verbose=False): + self.verbose = verbose + self._motor_halted = False + self._armed = False + self._lnb_on = True + self._calls = [] + + def _record(self, method, *args, **kwargs): + self._calls.append((method, args, kwargs)) + + def open(self): + self._record("open") + + def close(self): + self._record("close") + + def ensure_booted(self): + self._record("ensure_booted") + + def get_fw_version(self): + self._record("get_fw_version") + return {"version": "3.05.0-mock", "date": "2026-02-17", "raw": b"\x03\x05\x00"} + + def get_config(self): + self._record("get_config") + return 0x3F + + def get_usb_speed(self): + self._record("get_usb_speed") + return 2 + + def get_serial_number(self): + self._record("get_serial_number") + return bytes([0xDE, 0xAD, 0xBE, 0xEF]) + + def get_last_error(self): + self._record("get_last_error") + return 0x00 + + def signal_monitor(self): + self._record("signal_monitor") + return { + "snr_raw": 200, "snr_db": 8.5, "snr_pct": 42.5, + "agc1": 1200, "agc2": 800, "power_db": -45.3, + "locked": True, "lock": 0x1F, "status": 0x01, + } + + def get_stream_diag(self, reset=False): + self._record("get_stream_diag", reset=reset) + return {"poll_count": 100, "overflow_count": 0, "sync_loss": 0, "armed": self._armed} + + def sweep_spectrum(self, start_mhz, stop_mhz, step_mhz=5.0, dwell_ms=15): + self._record("sweep_spectrum", start_mhz, stop_mhz, step_mhz=step_mhz, dwell_ms=dwell_ms) + n_points = int((stop_mhz - start_mhz) / step_mhz) + 1 + freqs = [start_mhz + i * step_mhz for i in range(n_points)] + powers = [] + for f in freqs: + base = -50.0 + if abs(f - 1420.0) < 5: + base += 8.0 * (1.0 - abs(f - 1420.0) / 5.0) + powers.append(base) + raw = [(int((p + 60) * 100), 0) for p in powers] + return freqs, powers, raw + + def tune_monitor(self, sr_sps, freq_khz, mod_idx, fec_idx, dwell_ms): + self._record("tune_monitor", sr_sps, freq_khz, mod_idx, fec_idx, dwell_ms) + return { + "snr_raw": 180, "snr_db": 7.8, "snr_pct": 39.0, + "agc1": 1100, "agc2": 750, "power_db": -46.1, + "locked": True, "lock": 0x1F, "status": 0x01, + "dwell_ms": dwell_ms, + } + + def adaptive_blind_scan(self, freq_khz, sr_min, sr_max, sr_step): + self._record("adaptive_blind_scan", freq_khz, sr_min, sr_max, sr_step) + return {"freq_khz": freq_khz, "locked": True, "sr_sps": 20000000} + + def motor_halt(self): + self._record("motor_halt") + self._motor_halted = True + + def motor_drive_east(self, steps): + self._record("motor_drive_east", steps) + self._motor_halted = False + + def motor_drive_west(self, steps): + self._record("motor_drive_west", steps) + self._motor_halted = False + + def motor_goto_position(self, slot): + self._record("motor_goto_position", slot) + + def motor_goto_x(self, observer_lon, sat_lon): + self._record("motor_goto_x", observer_lon, sat_lon) + + def motor_store_position(self, slot): + self._record("motor_store_position", slot) + + def start_intersil(self, on=True): + self._record("start_intersil", on=on) + self._lnb_on = on + + def set_lnb_voltage(self, high): + self._record("set_lnb_voltage", high) + + def set_22khz_tone(self, on): + self._record("set_22khz_tone", on) + + def i2c_bus_scan(self): + self._record("i2c_bus_scan") + return [0x08, 0x10, 0x51] + + def i2c_raw_read(self, slave, register): + self._record("i2c_raw_read", slave, register) + return 0xAB + + def arm_transfer(self, on): + self._record("arm_transfer", on) + self._armed = on + + def read_stream(self, timeout=500): + self._record("read_stream", timeout=timeout) + if self._armed: + return bytes([0x47, 0x00, 0x00, 0x10] + [0xFF] * 184) + return None diff --git a/mcp/skywalker-mcp/src/skywalker_mcp/server.py b/mcp/skywalker-mcp/src/skywalker_mcp/server.py index a7062e1..0ae6c69 100644 --- a/mcp/skywalker-mcp/src/skywalker_mcp/server.py +++ b/mcp/skywalker-mcp/src/skywalker_mcp/server.py @@ -1,957 +1,957 @@ -""" -Genpix SkyWalker-1 MCP server. - -Wraps the entire skywalker_lib.py API as MCP tools, making every hardware -function accessible to LLMs. Thread-safe concurrent access via asyncio.to_thread -and a reentrant lock, following the same USBBridge pattern used by the TUI. -""" - -import os -import sys -import asyncio -import threading -import json -from contextlib import asynccontextmanager -from pathlib import Path - -from fastmcp import FastMCP, Context - -# Add the tools directory to path so we can import the hardware library -_TOOLS_DIR = Path(__file__).resolve().parents[4] / "tools" -if str(_TOOLS_DIR) not in sys.path: - sys.path.insert(0, str(_TOOLS_DIR)) - -from skywalker_lib import ( # noqa: E402 - SkyWalker1, - MODULATIONS, - FEC_RATES, - MOD_FEC_GROUP, - LBAND_ALLOCATIONS, - format_config_bits, - ERROR_NAMES, -) -from carrier_catalog import CarrierCatalog, CatalogDiff # noqa: E402 -from signal_analysis import adaptive_noise_floor, detect_peaks_enhanced # noqa: E402 -from survey_engine import SurveyEngine # noqa: E402 - - -MOTOR_WATCHDOG_SECS = 30 - - -class DeviceBridge: - """Thread-safe wrapper around SkyWalker1 for MCP tool access. - - Same principle as the TUI's USBBridge: every hardware call goes through - a reentrant lock to prevent overlapping USB control transfers. - - Internal access pattern: always use `with bridge.lock:` then `bridge._dev.method()`. - The `call()` convenience method does this automatically for simple cases. - - Includes a motor watchdog: when continuous drive is started, a background - asyncio task will automatically halt the motor after MOTOR_WATCHDOG_SECS - unless cancelled by a halt or new motor command. - """ - - def __init__(self, device: SkyWalker1): - self._dev = device - self._lock = threading.RLock() - self._motor_watchdog: asyncio.Task | None = None - - def call(self, method_name: str, *args, **kwargs): - """Call a SkyWalker1 method under the lock.""" - with self._lock: - return getattr(self._dev, method_name)(*args, **kwargs) - - @property - def lock(self) -> threading.RLock: - return self._lock - - def cancel_motor_watchdog(self): - """Cancel any running motor watchdog timer.""" - if self._motor_watchdog is not None and not self._motor_watchdog.done(): - self._motor_watchdog.cancel() - self._motor_watchdog = None - - def start_motor_watchdog(self, timeout: float = MOTOR_WATCHDOG_SECS): - """Start or restart the motor watchdog timer. - - After `timeout` seconds, the motor is automatically halted. - Any subsequent motor command or explicit halt cancels the watchdog. - """ - self.cancel_motor_watchdog() - - async def _watchdog(): - await asyncio.sleep(timeout) - print( - f"skywalker-mcp: MOTOR WATCHDOG fired after {timeout}s — halting motor", - file=sys.stderr, - ) - with self._lock: - try: - self._dev.motor_halt() - except Exception as e: - print(f"skywalker-mcp: watchdog halt failed: {e}", file=sys.stderr) - - self._motor_watchdog = asyncio.create_task(_watchdog()) - - -# Global bridge reference, set during lifespan -_bridge: DeviceBridge | None = None - - -@asynccontextmanager -async def lifespan(server: FastMCP): - """Open the USB device on startup, close on shutdown. - - Set SKYWALKER_MOCK=1 to use a mock device for integration testing - without USB hardware. - """ - global _bridge - if os.environ.get("SKYWALKER_MOCK"): - from skywalker_mcp.mock_device import MockSkyWalker1 - dev = MockSkyWalker1(verbose=False) - print("skywalker-mcp: MOCK MODE — no USB hardware", file=sys.stderr) - else: - dev = SkyWalker1(verbose=False) - try: - dev.open() - dev.ensure_booted() - _bridge = DeviceBridge(dev) - print(f"skywalker-mcp: device open, fw {dev.get_fw_version()['version']}", file=sys.stderr) - yield {"bridge": _bridge} - finally: - if _bridge is not None: - _bridge.cancel_motor_watchdog() - _bridge = None - dev.close() - print("skywalker-mcp: device closed", file=sys.stderr) - - -mcp = FastMCP( - "skywalker-mcp", - instructions="MCP server for the Genpix SkyWalker-1 DVB-S USB receiver. " - "Provides spectrum sweep, signal monitoring, carrier survey, " - "dish motor control, and transport stream analysis.", - lifespan=lifespan, -) - - -def _get_bridge(ctx: Context) -> DeviceBridge: - """Retrieve the DeviceBridge from lifespan context.""" - return ctx.request_context.lifespan_context["bridge"] - - -async def _dev_call(ctx: Context, method: str, *args, **kwargs): - """Run a device method in a thread (blocking USB I/O).""" - bridge = _get_bridge(ctx) - return await asyncio.to_thread(bridge.call, method, *args, **kwargs) - - -# ───────────────────────────────────────────── -# Device Status Tools -# ───────────────────────────────────────────── - -@mcp.tool() -async def get_device_status(ctx: Context) -> dict: - """Read comprehensive device status: firmware version, config bits, - USB speed, serial number, and last error code.""" - bridge = _get_bridge(ctx) - - def _read(): - with bridge.lock: - fw = bridge._dev.get_fw_version() - config = bridge._dev.get_config() - # Some commands may STALL on certain firmware builds; don't let - # one failure kill the entire status read. - try: - speed_raw = bridge._dev.get_usb_speed() - speed = {0: "unknown", 1: "Full (12 Mbps)", 2: "High (480 Mbps)"}.get( - speed_raw, f"unknown ({speed_raw})") - except Exception: - speed = "unavailable" - try: - serial = bridge._dev.get_serial_number().hex(' ') - except Exception: - serial = "unavailable" - try: - error_code = bridge._dev.get_last_error() - error = ERROR_NAMES.get(error_code, f"0x{error_code:02X}") - except Exception: - error = "unavailable" - return { - "firmware": fw, - "config_byte": config, - "config_bits": {name: is_set for name, is_set in format_config_bits(config)}, - "usb_speed": speed, - "serial": serial, - "last_error": error, - } - - return await asyncio.to_thread(_read) - - -@mcp.tool() -async def get_signal_quality(ctx: Context) -> dict: - """Read current signal quality: SNR, AGC levels, lock status, - and estimated power. Requires a prior tune() call.""" - result = await _dev_call(ctx, "signal_monitor") - return { - "snr_db": round(result["snr_db"], 2), - "snr_pct": round(result["snr_pct"], 1), - "agc1": result["agc1"], - "agc2": result["agc2"], - "power_db": round(result["power_db"], 2), - "locked": result["locked"], - "lock_byte": f"0x{result['lock']:02X}", - "status_byte": f"0x{result['status']:02X}", - } - - -@mcp.tool() -async def get_stream_diagnostics(ctx: Context, reset: bool = False) -> dict: - """Read streaming diagnostics: poll count, overflow count, sync loss, - and arm status. Set reset=True to clear counters after reading.""" - return await _dev_call(ctx, "get_stream_diag", reset=reset) - - -# ───────────────────────────────────────────── -# Spectrum & Tuning Tools -# ───────────────────────────────────────────── - -@mcp.tool() -async def sweep_spectrum( - ctx: Context, - start_mhz: float = 950.0, - stop_mhz: float = 2150.0, - step_mhz: float = 5.0, - dwell_ms: int = 15, -) -> dict: - """Sweep a frequency range and return power measurements at each step. - - Default covers the full IF range (950-2150 MHz) at 5 MHz steps. - For direct L-band input (no LNB), use the full range. - For LNB-converted signals, the IF range maps to the RF band via the LO. - - Returns frequency/power arrays plus detected peaks.""" - if start_mhz < 950 or start_mhz > 2150: - return {"error": f"start_mhz must be 950-2150, got {start_mhz}"} - if stop_mhz < 950 or stop_mhz > 2150: - return {"error": f"stop_mhz must be 950-2150, got {stop_mhz}"} - if start_mhz >= stop_mhz: - return {"error": f"start_mhz ({start_mhz}) must be less than stop_mhz ({stop_mhz})"} - if step_mhz < 0.1 or step_mhz > 100: - return {"error": f"step_mhz must be 0.1-100, got {step_mhz}"} - if dwell_ms < 1 or dwell_ms > 255: - return {"error": f"dwell_ms must be 1-255, got {dwell_ms}"} - - bridge = _get_bridge(ctx) - - def _sweep(): - with bridge.lock: - freqs, powers, raw = bridge._dev.sweep_spectrum( - start_mhz, stop_mhz, step_mhz=step_mhz, dwell_ms=dwell_ms, - ) - noise_floor, mad = adaptive_noise_floor(powers) - peaks = detect_peaks_enhanced(freqs, powers, threshold_db=6.0) - return { - "start_mhz": start_mhz, - "stop_mhz": stop_mhz, - "step_mhz": step_mhz, - "num_points": len(freqs), - "noise_floor_db": round(noise_floor, 2), - "noise_mad_db": round(mad, 3), - "frequencies_mhz": [round(f, 3) for f in freqs], - "powers_db": [round(p, 2) for p in powers], - "peaks": [ - { - "freq_mhz": round(pk["freq"], 3), - "power_db": round(pk["power"], 2), - "width_mhz": round(pk["width_mhz"], 2), - "prominence_db": round(pk["prominence_db"], 2), - } - for pk in peaks - ], - } - - await ctx.report_progress(0, 100) - result = await asyncio.to_thread(_sweep) - await ctx.report_progress(100, 100) - return result - - -@mcp.tool() -async def tune_frequency( - ctx: Context, - freq_mhz: float, - symbol_rate_ksps: int = 20000, - modulation: str = "qpsk", - fec: str = "auto", - dwell_ms: int = 10, -) -> dict: - """Tune to a specific frequency and read signal quality. - - freq_mhz: IF frequency in MHz (950-2150) - symbol_rate_ksps: symbol rate in ksps (256-30000) - modulation: one of qpsk, turbo-qpsk, turbo-8psk, turbo-16qam, - dcii-combo, dcii-i, dcii-q, dcii-oqpsk, dss, bpsk - fec: FEC rate (depends on modulation) or 'auto' - dwell_ms: time to wait after tuning before reading signal (1-255)""" - if freq_mhz < 950 or freq_mhz > 2150: - return {"error": f"freq_mhz must be 950-2150, got {freq_mhz}"} - if symbol_rate_ksps < 256 or symbol_rate_ksps > 30000: - return {"error": f"symbol_rate_ksps must be 256-30000, got {symbol_rate_ksps}"} - if dwell_ms < 1 or dwell_ms > 255: - return {"error": f"dwell_ms must be 1-255, got {dwell_ms}"} - - mod_entry = MODULATIONS.get(modulation) - if mod_entry is None: - return {"error": f"Unknown modulation '{modulation}'. Valid: {list(MODULATIONS.keys())}"} - - mod_idx = mod_entry[0] - fec_group = MOD_FEC_GROUP.get(modulation, "dvbs") - fec_table = FEC_RATES.get(fec_group, {}) - fec_idx = fec_table.get(fec, fec_table.get("auto", 0)) - - result = await _dev_call( - ctx, "tune_monitor", - symbol_rate_ksps * 1000, - int(freq_mhz * 1000), - mod_idx, fec_idx, dwell_ms, - ) - return { - "freq_mhz": freq_mhz, - "symbol_rate_ksps": symbol_rate_ksps, - "modulation": modulation, - "fec": fec, - "snr_db": round(result["snr_db"], 2), - "agc1": result["agc1"], - "agc2": result["agc2"], - "power_db": round(result["power_db"], 2), - "locked": result["locked"], - "dwell_ms": result["dwell_ms"], - } - - -@mcp.tool() -async def run_blind_scan( - ctx: Context, - freq_mhz: float, - sr_min_ksps: int = 1000, - sr_max_ksps: int = 30000, - sr_step_ksps: int = 1000, -) -> dict: - """Run adaptive blind scan at a single frequency, sweeping symbol rates - to find a lock. Returns the locked symbol rate if found, or null.""" - if freq_mhz < 950 or freq_mhz > 2150: - return {"error": f"freq_mhz must be 950-2150, got {freq_mhz}"} - if sr_min_ksps < 256: - return {"error": f"sr_min_ksps must be >= 256, got {sr_min_ksps}"} - if sr_max_ksps > 30000: - return {"error": f"sr_max_ksps must be <= 30000, got {sr_max_ksps}"} - - result = await _dev_call( - ctx, "adaptive_blind_scan", - int(freq_mhz * 1000), - sr_min_ksps * 1000, - sr_max_ksps * 1000, - sr_step_ksps * 1000, - ) - if result is None: - return {"freq_mhz": freq_mhz, "locked": False, "sr_sps": None} - return { - "freq_mhz": result["freq_khz"] / 1000.0, - "locked": result["locked"], - "sr_sps": result["sr_sps"], - "sr_ksps": result["sr_sps"] / 1000.0, - } - - -# ───────────────────────────────────────────── -# Survey & Catalog Tools -# ───────────────────────────────────────────── - -@mcp.tool() -async def run_carrier_survey( - ctx: Context, - start_mhz: float = 950.0, - stop_mhz: float = 2150.0, - coarse_step: float = 5.0, - band: str = "", - pol: str = "", - lnb_lo_mhz: float = 0.0, - save: bool = True, -) -> dict: - """Run the six-stage carrier survey pipeline: coarse sweep, peak detection, - fine sweep, blind scan, TS sampling, and catalog assembly. - - This is the full automated survey. Takes 5-15 minutes depending on range. - Results are saved to ~/.skywalker1/surveys/ as JSON.""" - bridge = _get_bridge(ctx) - - def _survey(): - # NOTE: We intentionally do NOT hold bridge.lock for the entire survey. - # The survey takes 5-15 minutes and holding the lock would block - # emergency motor halt commands. SurveyEngine calls device methods - # individually — each USB transfer is atomic at the hardware level. - # The RLock still protects concurrent tool calls from interleaving - # mid-transfer through the _dev_call / bridge.call path. - engine = SurveyEngine(bridge._dev) - catalog = engine.run_full_scan( - start_mhz=start_mhz, stop_mhz=stop_mhz, - coarse_step=coarse_step, - ) - catalog.band = band - catalog.pol = pol - catalog.lnb_lo_mhz = lnb_lo_mhz - - result = { - "carrier_count": len(catalog.carriers), - "locked_count": sum(1 for c in catalog.carriers if c.locked), - "summary": catalog.summary(), - } - - if save: - path = catalog.save() - result["saved_to"] = str(path) - - return result - - return await asyncio.to_thread(_survey) - - -@mcp.tool() -async def compare_surveys( - ctx: Context, - old_filename: str, - new_filename: str, -) -> dict: - """Compare two saved survey catalogs and report new, missing, and changed - carriers between them. Filenames are looked up in ~/.skywalker1/surveys/.""" - # Sanitize filenames: strip path separators to prevent directory traversal - for name, label in [(old_filename, "old_filename"), (new_filename, "new_filename")]: - basename = Path(name).name - if basename != name or ".." in name: - return {"error": f"{label} must be a plain filename, not a path. Got: {name}"} - - def _compare(): - old_cat = CarrierCatalog.load(old_filename) - new_cat = CarrierCatalog.load(new_filename) - diff = CatalogDiff.diff(old_cat, new_cat) - return { - "new_count": len(diff["new"]), - "missing_count": len(diff["missing"]), - "changed_count": len(diff["changed"]), - "stable_count": len(diff["stable"]), - "formatted": CatalogDiff.format_diff(diff), - } - - return await asyncio.to_thread(_compare) - - -@mcp.tool() -async def list_surveys(ctx: Context) -> list[dict]: - """List saved survey catalog files from ~/.skywalker1/surveys/, - newest first, with carrier counts and metadata.""" - return await asyncio.to_thread(CarrierCatalog.list_surveys) - - -# ───────────────────────────────────────────── -# Dish Motor Control Tools -# ───────────────────────────────────────────── - -@mcp.tool() -async def move_dish( - ctx: Context, - action: str, - value: float = 0.0, - observer_lon: float = 0.0, - continuous: bool = False, -) -> dict: - """Control the DiSEqC 1.2 dish motor. - - action: one of 'halt', 'east', 'west', 'goto', 'gotox' - value: - - For east/west: number of steps (1-127). Must set continuous=True for non-stop drive. - - For goto: position slot number (0-255, 0=reference) - - For gotox: satellite longitude (negative=west) - observer_lon: your longitude (for gotox only, negative=west) - continuous: must be explicitly True to allow continuous (non-stop) motor drive. - Without this, steps=0 is rejected as a safety measure.""" - bridge = _get_bridge(ctx) - - def _move(): - with bridge.lock: - if action == "halt": - bridge._dev.motor_halt() - return {"action": "halt", "status": "stopped"} - - elif action in ("east", "west"): - steps = int(value) - if steps == 0 and not continuous: - return { - "error": "steps=0 means CONTINUOUS drive (motor never stops). " - "Set continuous=True to confirm, or use steps=1-127. " - "Send action='halt' to stop a running motor.", - } - if steps < 0 or steps > 127: - return {"error": f"steps must be 0-127, got {steps}"} - if action == "east": - bridge._dev.motor_drive_east(steps) - else: - bridge._dev.motor_drive_west(steps) - mode = "continuous (send halt to stop)" if steps == 0 else "stepped" - return {"action": action, "steps": steps, "mode": mode, "status": "driving", - "continuous": steps == 0} - - elif action == "goto": - slot = int(value) - if slot < 0 or slot > 255: - return {"error": f"Slot must be 0-255, got {slot}"} - bridge._dev.motor_goto_position(slot) - return {"action": "goto", "slot": slot, "status": "moving"} - - elif action == "gotox": - from skywalker_lib import usals_angle - angle = usals_angle(observer_lon, value) - bridge._dev.motor_goto_x(observer_lon, value) - return { - "action": "gotox", - "satellite_lon": value, - "observer_lon": observer_lon, - "motor_angle_deg": round(angle, 2), - "direction": "west" if angle < 0 else "east", - "status": "moving", - } - - else: - return {"error": f"Unknown action '{action}'. Valid: halt, east, west, goto, gotox"} - - result = await asyncio.to_thread(_move) - - # Motor watchdog management: cancel on halt/goto/gotox, start on continuous drive - if "error" not in result: - if action == "halt": - bridge.cancel_motor_watchdog() - elif action in ("goto", "gotox"): - # GotoX/Goto have inherent motor-stop at destination - bridge.cancel_motor_watchdog() - elif result.get("continuous"): - bridge.start_motor_watchdog() - result["watchdog_secs"] = MOTOR_WATCHDOG_SECS - result["warning"] = ( - f"Motor watchdog active: auto-halt in {MOTOR_WATCHDOG_SECS}s. " - "Send action='halt' to stop sooner." - ) - - return result - - -@mcp.tool() -async def jog_dish( - ctx: Context, - direction: str, - steps: int = 5, -) -> dict: - """Jog the dish a small number of steps east or west, then read signal quality. - Useful for fine-tuning dish alignment. Steps capped at 30 for safety.""" - if direction not in ("east", "west"): - return {"error": "direction must be 'east' or 'west'"} - if steps < 1 or steps > 30: - return {"error": f"steps must be 1-30 for jog (got {steps}). Use move_dish for larger moves."} - - bridge = _get_bridge(ctx) - - def _jog(): - import time - with bridge.lock: - if direction == "east": - bridge._dev.motor_drive_east(steps) - else: - bridge._dev.motor_drive_west(steps) - - time.sleep(0.5 + steps * 0.05) - sig = bridge._dev.signal_monitor() - return { - "direction": direction, - "steps": steps, - "snr_db": round(sig["snr_db"], 2), - "agc1": sig["agc1"], - "power_db": round(sig["power_db"], 2), - "locked": sig["locked"], - } - - return await asyncio.to_thread(_jog) - - -@mcp.tool() -async def store_position( - ctx: Context, - slot: int, -) -> dict: - """Store the current dish position to a memory slot (1-255).""" - if slot < 1 or slot > 255: - return {"error": "Slot must be 1-255 (slot 0 is reference)"} - await _dev_call(ctx, "motor_store_position", slot) - return {"stored": True, "slot": slot} - - -# ───────────────────────────────────────────── -# LNB & Power Tools -# ───────────────────────────────────────────── - -@mcp.tool() -async def set_lnb_config( - ctx: Context, - voltage: str = "", - tone_22khz: bool | None = None, - disable_lnb: bool = False, -) -> dict: - """Configure LNB power supply and 22 kHz tone. - - voltage: '13V' or '18V' (controls polarization: 13V=vertical, 18V=horizontal) - tone_22khz: True to enable (high band), False to disable (low band) - disable_lnb: True to turn off LNB power entirely (for direct L-band input)""" - bridge = _get_bridge(ctx) - - def _configure(): - with bridge.lock: - result = {} - - if disable_lnb: - bridge._dev.start_intersil(on=False) - result["lnb_power"] = "off" - return result - - if voltage: - high = voltage.upper() in ("18V", "18", "H", "L") - bridge._dev.set_lnb_voltage(high) - result["voltage"] = "18V" if high else "13V" - - if tone_22khz is not None: - bridge._dev.set_22khz_tone(tone_22khz) - result["tone_22khz"] = tone_22khz - - return result - - return await asyncio.to_thread(_configure) - - -# ───────────────────────────────────────────── -# I2C Bus Tools -# ───────────────────────────────────────────── - -@mcp.tool() -async def scan_i2c_bus(ctx: Context) -> dict: - """Scan the I2C bus for all responding devices. - Returns list of 7-bit slave addresses that ACK'd.""" - addresses = await _dev_call(ctx, "i2c_bus_scan") - known_devices = { - 0x08: "BCM4500 (demodulator)", - 0x10: "Tuner / LNB controller", - 0x51: "24Cxx EEPROM (config/serial)", - } - devices = [] - for addr in addresses: - devices.append({ - "address": f"0x{addr:02X}", - "decimal": addr, - "known_as": known_devices.get(addr, "unknown"), - }) - return {"device_count": len(devices), "devices": devices} - - -@mcp.tool() -async def read_i2c_register( - ctx: Context, - slave_address: int, - register: int, -) -> dict: - """Read a single byte from an I2C device register. - - slave_address: 7-bit I2C address (e.g. 0x08 for BCM4500) - register: register address to read""" - value = await _dev_call(ctx, "i2c_raw_read", slave_address, register) - return { - "slave": f"0x{slave_address:02X}", - "register": f"0x{register:02X}", - "value": value, - "hex": f"0x{value:02X}", - "binary": f"0b{value:08b}", - } - - -# ───────────────────────────────────────────── -# Transport Stream Tools -# ───────────────────────────────────────────── - -@mcp.tool() -async def capture_transport_stream( - ctx: Context, - duration_secs: float = 3.0, -) -> dict: - """Capture transport stream data from the currently tuned carrier and - parse PAT/PMT/SDT for service information. - - The device must already be tuned and locked to a carrier. - duration_secs: capture time (0.5-30 seconds). - Returns parsed service names, program table, and stream metadata.""" - if duration_secs < 0.5 or duration_secs > 30: - return {"error": f"duration_secs must be 0.5-30, got {duration_secs}"} - - bridge = _get_bridge(ctx) - - def _capture(): - import time - import io - with bridge.lock: - sig = bridge._dev.signal_monitor() - if not sig.get("locked"): - return {"error": "No signal lock. Tune to a carrier first."} - - bridge._dev.arm_transfer(True) - ts_data = bytearray() - try: - deadline = time.time() + duration_secs - while time.time() < deadline: - chunk = bridge._dev.read_stream(timeout=500) - if chunk: - ts_data.extend(chunk) - finally: - bridge._dev.arm_transfer(False) - - if not ts_data: - return {"error": "No TS data received", "bytes_captured": 0} - - result = { - "bytes_captured": len(ts_data), - "packets": len(ts_data) // 188, - "services": [], - "programs": {}, - } - - try: - from ts_analyze import TSReader, PSIParser, parse_pat, parse_sdt - source = io.BytesIO(bytes(ts_data)) - reader = TSReader(source) - psi_pat = PSIParser() - psi_sdt = PSIParser() - pat = None - pmt_pids = set() - - for pkt in reader.iter_packets(max_packets=50000): - if pkt.pid == 0x0000 and pat is None: - section = psi_pat.feed(pkt) - if section is not None: - pat = parse_pat(section) - if pat: - result["programs"] = pat["programs"] - for prog, pid in pat["programs"].items(): - if prog != 0: - pmt_pids.add(pid) - - if pkt.pid == 0x0011: - section = psi_sdt.feed(pkt) - if section is not None: - sdt = parse_sdt(section) - if sdt: - for svc in sdt.get("services", []): - name = svc.get("service_name", "") - if name: - result["services"].append(name) - break - - except Exception as e: - result["parse_error"] = str(e) - - return result - - return await asyncio.to_thread(_capture) - - -# ───────────────────────────────────────────── -# Frequency Identification Tool -# ───────────────────────────────────────────── - -@mcp.tool() -async def identify_frequency( - ctx: Context, - freq_mhz: float, - lnb_lo_mhz: float = 0.0, -) -> dict: - """Look up what service or allocation is at a given frequency. - - freq_mhz: IF frequency (950-2150 MHz) - lnb_lo_mhz: LNB local oscillator (0 = direct input, no conversion) - - Cross-references against L-band allocations and known satellite bands.""" - rf_mhz = freq_mhz + lnb_lo_mhz if lnb_lo_mhz else freq_mhz - - matches = [] - for start, stop, name in LBAND_ALLOCATIONS: - if start <= rf_mhz <= stop: - matches.append({"band": name, "range_mhz": f"{start}-{stop}"}) - - # Known point frequencies - known_freqs = [ - (1420.405, 2.0, "Hydrogen 21 cm line (galactic emission)"), - (1575.42, 2.0, "GPS L1"), - (1227.6, 2.0, "GPS L2"), - (1176.45, 2.0, "GPS L5 / Galileo E5a"), - (1207.14, 2.0, "Galileo E5b"), - (1602.0, 10.0, "GLONASS L1 (FDMA center)"), - (1246.0, 10.0, "GLONASS L2 (FDMA center)"), - (1544.5, 1.0, "COSPAS-SARSAT (EPIRB)"), - ] - for center, tolerance, name in known_freqs: - if abs(rf_mhz - center) <= tolerance: - matches.append({"signal": name, "center_mhz": center}) - - return { - "if_freq_mhz": freq_mhz, - "rf_freq_mhz": rf_mhz if lnb_lo_mhz else None, - "lnb_lo_mhz": lnb_lo_mhz or None, - "matches": matches, - "in_if_range": 950 <= freq_mhz <= 2150, - } - - -# ───────────────────────────────────────────── -# MCP Resources -# ───────────────────────────────────────────── - -@mcp.resource("skywalker://status") -async def resource_status() -> str: - """Live device status: firmware, config, signal.""" - if _bridge is None: - return json.dumps({"error": "Device not connected"}) - - def _read(): - with _bridge.lock: - fw = _bridge._dev.get_fw_version() - config = _bridge._dev.get_config() - sig = _bridge._dev.signal_monitor() - error = _bridge._dev.get_last_error() - return json.dumps({ - "firmware": fw["version"], - "firmware_date": fw["date"], - "config_bits": {name: is_set for name, is_set in format_config_bits(config)}, - "signal": { - "snr_db": round(sig["snr_db"], 2), - "agc1": sig["agc1"], - "power_db": round(sig["power_db"], 2), - "locked": sig["locked"], - }, - "last_error": ERROR_NAMES.get(error, f"0x{error:02X}"), - }, indent=2) - - return await asyncio.to_thread(_read) - - -@mcp.resource("skywalker://catalog/latest") -async def resource_latest_catalog() -> str: - """Most recent survey catalog.""" - def _read(): - surveys = CarrierCatalog.list_surveys() - if not surveys: - return json.dumps({"error": "No surveys saved yet"}) - latest = surveys[0] - cat = CarrierCatalog.load(latest["filename"]) - return json.dumps(cat.to_dict(), indent=2) - - return await asyncio.to_thread(_read) - - -@mcp.resource("skywalker://allocations/lband") -async def resource_lband_allocations() -> str: - """L-band frequency allocation table (950-2150 MHz IF range).""" - allocations = [] - for start, stop, name in LBAND_ALLOCATIONS: - allocations.append({ - "start_mhz": start, - "stop_mhz": stop, - "name": name, - "in_if_range": (start >= 950 or stop >= 950) and (start <= 2150), - }) - - known = [ - {"freq_mhz": 1420.405, "name": "Hydrogen 21 cm line", "type": "spectral_line"}, - {"freq_mhz": 1575.42, "name": "GPS L1", "type": "navigation"}, - {"freq_mhz": 1227.6, "name": "GPS L2", "type": "navigation"}, - {"freq_mhz": 1176.45, "name": "GPS L5 / Galileo E5a", "type": "navigation"}, - {"freq_mhz": 1602.0, "name": "GLONASS L1", "type": "navigation"}, - {"freq_mhz": 1544.5, "name": "COSPAS-SARSAT", "type": "distress"}, - ] - - return json.dumps({ - "allocations": allocations, - "known_frequencies": known, - "note": "These frequencies are directly receivable (no LNB) when " - "an antenna is connected to the F-connector input.", - }, indent=2) - - -@mcp.resource("skywalker://modulations") -async def resource_modulations() -> str: - """Supported modulation types and FEC rates.""" - mods = {} - for name, (idx, desc) in MODULATIONS.items(): - fec_group = MOD_FEC_GROUP.get(name, "dvbs") - fec_options = list(FEC_RATES.get(fec_group, {}).keys()) - mods[name] = { - "index": idx, - "description": desc, - "fec_options": fec_options, - } - return json.dumps(mods, indent=2) - - -# ───────────────────────────────────────────── -# MCP Prompts -# ───────────────────────────────────────────── - -@mcp.prompt() -async def explore_rf_environment() -> str: - """Autonomous RF environment exploration prompt. - Instructs the LLM to systematically survey and document the RF spectrum.""" - return """You have access to a Genpix SkyWalker-1 DVB-S USB receiver via MCP tools. -Your task: systematically explore the RF environment and build a knowledge base -of what you discover. - -Strategy: -1. Start with get_device_status to verify the hardware is working -2. Run a full-band sweep_spectrum (950-2150 MHz) to see what's out there -3. For each detected peak, use identify_frequency to classify it -4. For strong carriers, try tune_frequency with different modulations -5. If a carrier locks, use capture_transport_stream to identify services -6. Use the dish motor (move_dish/jog_dish) to explore different satellites -7. Compare results with any previous surveys (list_surveys/compare_surveys) - -Report your findings as you go. Note any anomalies or interesting signals.""" - - -@mcp.prompt() -async def hydrogen_line_observation() -> str: - """Guide for observing the hydrogen 21 cm line at 1420.405 MHz.""" - return """You have access to a Genpix SkyWalker-1 receiver for hydrogen 21 cm observation. - -IMPORTANT: This requires direct antenna input (no LNB). Set disable_lnb=True. - -Procedure: -1. Verify device with get_device_status -2. Configure: set_lnb_config(disable_lnb=True) -3. Sweep the hydrogen line region: sweep_spectrum(start_mhz=1418, stop_mhz=1423, step_mhz=0.5) -4. Look for a broad power bump centered near 1420.405 MHz -5. Compare with adjacent "empty" spectrum (e.g., 1430-1435 MHz) as a control -6. The velocity of the hydrogen gas can be calculated from Doppler shift: - v = c * (f_observed - 1420.405) / 1420.405 - -The emission is broad (several MHz) due to galactic rotation velocity dispersion. -You won't see a sharp spike — look for elevated power across the 1419-1421 MHz range.""" - - -def main(): - mcp.run() - - -if __name__ == "__main__": - main() +""" +Genpix SkyWalker-1 MCP server. + +Wraps the entire skywalker_lib.py API as MCP tools, making every hardware +function accessible to LLMs. Thread-safe concurrent access via asyncio.to_thread +and a reentrant lock, following the same USBBridge pattern used by the TUI. +""" + +import os +import sys +import asyncio +import threading +import json +from contextlib import asynccontextmanager +from pathlib import Path + +from fastmcp import FastMCP, Context + +# Add the tools directory to path so we can import the hardware library +_TOOLS_DIR = Path(__file__).resolve().parents[4] / "tools" +if str(_TOOLS_DIR) not in sys.path: + sys.path.insert(0, str(_TOOLS_DIR)) + +from skywalker_lib import ( # noqa: E402 + SkyWalker1, + MODULATIONS, + FEC_RATES, + MOD_FEC_GROUP, + LBAND_ALLOCATIONS, + format_config_bits, + ERROR_NAMES, +) +from carrier_catalog import CarrierCatalog, CatalogDiff # noqa: E402 +from signal_analysis import adaptive_noise_floor, detect_peaks_enhanced # noqa: E402 +from survey_engine import SurveyEngine # noqa: E402 + + +MOTOR_WATCHDOG_SECS = 30 + + +class DeviceBridge: + """Thread-safe wrapper around SkyWalker1 for MCP tool access. + + Same principle as the TUI's USBBridge: every hardware call goes through + a reentrant lock to prevent overlapping USB control transfers. + + Internal access pattern: always use `with bridge.lock:` then `bridge._dev.method()`. + The `call()` convenience method does this automatically for simple cases. + + Includes a motor watchdog: when continuous drive is started, a background + asyncio task will automatically halt the motor after MOTOR_WATCHDOG_SECS + unless cancelled by a halt or new motor command. + """ + + def __init__(self, device: SkyWalker1): + self._dev = device + self._lock = threading.RLock() + self._motor_watchdog: asyncio.Task | None = None + + def call(self, method_name: str, *args, **kwargs): + """Call a SkyWalker1 method under the lock.""" + with self._lock: + return getattr(self._dev, method_name)(*args, **kwargs) + + @property + def lock(self) -> threading.RLock: + return self._lock + + def cancel_motor_watchdog(self): + """Cancel any running motor watchdog timer.""" + if self._motor_watchdog is not None and not self._motor_watchdog.done(): + self._motor_watchdog.cancel() + self._motor_watchdog = None + + def start_motor_watchdog(self, timeout: float = MOTOR_WATCHDOG_SECS): + """Start or restart the motor watchdog timer. + + After `timeout` seconds, the motor is automatically halted. + Any subsequent motor command or explicit halt cancels the watchdog. + """ + self.cancel_motor_watchdog() + + async def _watchdog(): + await asyncio.sleep(timeout) + print( + f"skywalker-mcp: MOTOR WATCHDOG fired after {timeout}s — halting motor", + file=sys.stderr, + ) + with self._lock: + try: + self._dev.motor_halt() + except Exception as e: + print(f"skywalker-mcp: watchdog halt failed: {e}", file=sys.stderr) + + self._motor_watchdog = asyncio.create_task(_watchdog()) + + +# Global bridge reference, set during lifespan +_bridge: DeviceBridge | None = None + + +@asynccontextmanager +async def lifespan(server: FastMCP): + """Open the USB device on startup, close on shutdown. + + Set SKYWALKER_MOCK=1 to use a mock device for integration testing + without USB hardware. + """ + global _bridge + if os.environ.get("SKYWALKER_MOCK"): + from skywalker_mcp.mock_device import MockSkyWalker1 + dev = MockSkyWalker1(verbose=False) + print("skywalker-mcp: MOCK MODE — no USB hardware", file=sys.stderr) + else: + dev = SkyWalker1(verbose=False) + try: + dev.open() + dev.ensure_booted() + _bridge = DeviceBridge(dev) + print(f"skywalker-mcp: device open, fw {dev.get_fw_version()['version']}", file=sys.stderr) + yield {"bridge": _bridge} + finally: + if _bridge is not None: + _bridge.cancel_motor_watchdog() + _bridge = None + dev.close() + print("skywalker-mcp: device closed", file=sys.stderr) + + +mcp = FastMCP( + "skywalker-mcp", + instructions="MCP server for the Genpix SkyWalker-1 DVB-S USB receiver. " + "Provides spectrum sweep, signal monitoring, carrier survey, " + "dish motor control, and transport stream analysis.", + lifespan=lifespan, +) + + +def _get_bridge(ctx: Context) -> DeviceBridge: + """Retrieve the DeviceBridge from lifespan context.""" + return ctx.request_context.lifespan_context["bridge"] + + +async def _dev_call(ctx: Context, method: str, *args, **kwargs): + """Run a device method in a thread (blocking USB I/O).""" + bridge = _get_bridge(ctx) + return await asyncio.to_thread(bridge.call, method, *args, **kwargs) + + +# ───────────────────────────────────────────── +# Device Status Tools +# ───────────────────────────────────────────── + +@mcp.tool() +async def get_device_status(ctx: Context) -> dict: + """Read comprehensive device status: firmware version, config bits, + USB speed, serial number, and last error code.""" + bridge = _get_bridge(ctx) + + def _read(): + with bridge.lock: + fw = bridge._dev.get_fw_version() + config = bridge._dev.get_config() + # Some commands may STALL on certain firmware builds; don't let + # one failure kill the entire status read. + try: + speed_raw = bridge._dev.get_usb_speed() + speed = {0: "unknown", 1: "Full (12 Mbps)", 2: "High (480 Mbps)"}.get( + speed_raw, f"unknown ({speed_raw})") + except Exception: + speed = "unavailable" + try: + serial = bridge._dev.get_serial_number().hex(' ') + except Exception: + serial = "unavailable" + try: + error_code = bridge._dev.get_last_error() + error = ERROR_NAMES.get(error_code, f"0x{error_code:02X}") + except Exception: + error = "unavailable" + return { + "firmware": fw, + "config_byte": config, + "config_bits": {name: is_set for name, is_set in format_config_bits(config)}, + "usb_speed": speed, + "serial": serial, + "last_error": error, + } + + return await asyncio.to_thread(_read) + + +@mcp.tool() +async def get_signal_quality(ctx: Context) -> dict: + """Read current signal quality: SNR, AGC levels, lock status, + and estimated power. Requires a prior tune() call.""" + result = await _dev_call(ctx, "signal_monitor") + return { + "snr_db": round(result["snr_db"], 2), + "snr_pct": round(result["snr_pct"], 1), + "agc1": result["agc1"], + "agc2": result["agc2"], + "power_db": round(result["power_db"], 2), + "locked": result["locked"], + "lock_byte": f"0x{result['lock']:02X}", + "status_byte": f"0x{result['status']:02X}", + } + + +@mcp.tool() +async def get_stream_diagnostics(ctx: Context, reset: bool = False) -> dict: + """Read streaming diagnostics: poll count, overflow count, sync loss, + and arm status. Set reset=True to clear counters after reading.""" + return await _dev_call(ctx, "get_stream_diag", reset=reset) + + +# ───────────────────────────────────────────── +# Spectrum & Tuning Tools +# ───────────────────────────────────────────── + +@mcp.tool() +async def sweep_spectrum( + ctx: Context, + start_mhz: float = 950.0, + stop_mhz: float = 2150.0, + step_mhz: float = 5.0, + dwell_ms: int = 15, +) -> dict: + """Sweep a frequency range and return power measurements at each step. + + Default covers the full IF range (950-2150 MHz) at 5 MHz steps. + For direct L-band input (no LNB), use the full range. + For LNB-converted signals, the IF range maps to the RF band via the LO. + + Returns frequency/power arrays plus detected peaks.""" + if start_mhz < 950 or start_mhz > 2150: + return {"error": f"start_mhz must be 950-2150, got {start_mhz}"} + if stop_mhz < 950 or stop_mhz > 2150: + return {"error": f"stop_mhz must be 950-2150, got {stop_mhz}"} + if start_mhz >= stop_mhz: + return {"error": f"start_mhz ({start_mhz}) must be less than stop_mhz ({stop_mhz})"} + if step_mhz < 0.1 or step_mhz > 100: + return {"error": f"step_mhz must be 0.1-100, got {step_mhz}"} + if dwell_ms < 1 or dwell_ms > 255: + return {"error": f"dwell_ms must be 1-255, got {dwell_ms}"} + + bridge = _get_bridge(ctx) + + def _sweep(): + with bridge.lock: + freqs, powers, raw = bridge._dev.sweep_spectrum( + start_mhz, stop_mhz, step_mhz=step_mhz, dwell_ms=dwell_ms, + ) + noise_floor, mad = adaptive_noise_floor(powers) + peaks = detect_peaks_enhanced(freqs, powers, threshold_db=6.0) + return { + "start_mhz": start_mhz, + "stop_mhz": stop_mhz, + "step_mhz": step_mhz, + "num_points": len(freqs), + "noise_floor_db": round(noise_floor, 2), + "noise_mad_db": round(mad, 3), + "frequencies_mhz": [round(f, 3) for f in freqs], + "powers_db": [round(p, 2) for p in powers], + "peaks": [ + { + "freq_mhz": round(pk["freq"], 3), + "power_db": round(pk["power"], 2), + "width_mhz": round(pk["width_mhz"], 2), + "prominence_db": round(pk["prominence_db"], 2), + } + for pk in peaks + ], + } + + await ctx.report_progress(0, 100) + result = await asyncio.to_thread(_sweep) + await ctx.report_progress(100, 100) + return result + + +@mcp.tool() +async def tune_frequency( + ctx: Context, + freq_mhz: float, + symbol_rate_ksps: int = 20000, + modulation: str = "qpsk", + fec: str = "auto", + dwell_ms: int = 10, +) -> dict: + """Tune to a specific frequency and read signal quality. + + freq_mhz: IF frequency in MHz (950-2150) + symbol_rate_ksps: symbol rate in ksps (256-30000) + modulation: one of qpsk, turbo-qpsk, turbo-8psk, turbo-16qam, + dcii-combo, dcii-i, dcii-q, dcii-oqpsk, dss, bpsk + fec: FEC rate (depends on modulation) or 'auto' + dwell_ms: time to wait after tuning before reading signal (1-255)""" + if freq_mhz < 950 or freq_mhz > 2150: + return {"error": f"freq_mhz must be 950-2150, got {freq_mhz}"} + if symbol_rate_ksps < 256 or symbol_rate_ksps > 30000: + return {"error": f"symbol_rate_ksps must be 256-30000, got {symbol_rate_ksps}"} + if dwell_ms < 1 or dwell_ms > 255: + return {"error": f"dwell_ms must be 1-255, got {dwell_ms}"} + + mod_entry = MODULATIONS.get(modulation) + if mod_entry is None: + return {"error": f"Unknown modulation '{modulation}'. Valid: {list(MODULATIONS.keys())}"} + + mod_idx = mod_entry[0] + fec_group = MOD_FEC_GROUP.get(modulation, "dvbs") + fec_table = FEC_RATES.get(fec_group, {}) + fec_idx = fec_table.get(fec, fec_table.get("auto", 0)) + + result = await _dev_call( + ctx, "tune_monitor", + symbol_rate_ksps * 1000, + int(freq_mhz * 1000), + mod_idx, fec_idx, dwell_ms, + ) + return { + "freq_mhz": freq_mhz, + "symbol_rate_ksps": symbol_rate_ksps, + "modulation": modulation, + "fec": fec, + "snr_db": round(result["snr_db"], 2), + "agc1": result["agc1"], + "agc2": result["agc2"], + "power_db": round(result["power_db"], 2), + "locked": result["locked"], + "dwell_ms": result["dwell_ms"], + } + + +@mcp.tool() +async def run_blind_scan( + ctx: Context, + freq_mhz: float, + sr_min_ksps: int = 1000, + sr_max_ksps: int = 30000, + sr_step_ksps: int = 1000, +) -> dict: + """Run adaptive blind scan at a single frequency, sweeping symbol rates + to find a lock. Returns the locked symbol rate if found, or null.""" + if freq_mhz < 950 or freq_mhz > 2150: + return {"error": f"freq_mhz must be 950-2150, got {freq_mhz}"} + if sr_min_ksps < 256: + return {"error": f"sr_min_ksps must be >= 256, got {sr_min_ksps}"} + if sr_max_ksps > 30000: + return {"error": f"sr_max_ksps must be <= 30000, got {sr_max_ksps}"} + + result = await _dev_call( + ctx, "adaptive_blind_scan", + int(freq_mhz * 1000), + sr_min_ksps * 1000, + sr_max_ksps * 1000, + sr_step_ksps * 1000, + ) + if result is None: + return {"freq_mhz": freq_mhz, "locked": False, "sr_sps": None} + return { + "freq_mhz": result["freq_khz"] / 1000.0, + "locked": result["locked"], + "sr_sps": result["sr_sps"], + "sr_ksps": result["sr_sps"] / 1000.0, + } + + +# ───────────────────────────────────────────── +# Survey & Catalog Tools +# ───────────────────────────────────────────── + +@mcp.tool() +async def run_carrier_survey( + ctx: Context, + start_mhz: float = 950.0, + stop_mhz: float = 2150.0, + coarse_step: float = 5.0, + band: str = "", + pol: str = "", + lnb_lo_mhz: float = 0.0, + save: bool = True, +) -> dict: + """Run the six-stage carrier survey pipeline: coarse sweep, peak detection, + fine sweep, blind scan, TS sampling, and catalog assembly. + + This is the full automated survey. Takes 5-15 minutes depending on range. + Results are saved to ~/.skywalker1/surveys/ as JSON.""" + bridge = _get_bridge(ctx) + + def _survey(): + # NOTE: We intentionally do NOT hold bridge.lock for the entire survey. + # The survey takes 5-15 minutes and holding the lock would block + # emergency motor halt commands. SurveyEngine calls device methods + # individually — each USB transfer is atomic at the hardware level. + # The RLock still protects concurrent tool calls from interleaving + # mid-transfer through the _dev_call / bridge.call path. + engine = SurveyEngine(bridge._dev) + catalog = engine.run_full_scan( + start_mhz=start_mhz, stop_mhz=stop_mhz, + coarse_step=coarse_step, + ) + catalog.band = band + catalog.pol = pol + catalog.lnb_lo_mhz = lnb_lo_mhz + + result = { + "carrier_count": len(catalog.carriers), + "locked_count": sum(1 for c in catalog.carriers if c.locked), + "summary": catalog.summary(), + } + + if save: + path = catalog.save() + result["saved_to"] = str(path) + + return result + + return await asyncio.to_thread(_survey) + + +@mcp.tool() +async def compare_surveys( + ctx: Context, + old_filename: str, + new_filename: str, +) -> dict: + """Compare two saved survey catalogs and report new, missing, and changed + carriers between them. Filenames are looked up in ~/.skywalker1/surveys/.""" + # Sanitize filenames: strip path separators to prevent directory traversal + for name, label in [(old_filename, "old_filename"), (new_filename, "new_filename")]: + basename = Path(name).name + if basename != name or ".." in name: + return {"error": f"{label} must be a plain filename, not a path. Got: {name}"} + + def _compare(): + old_cat = CarrierCatalog.load(old_filename) + new_cat = CarrierCatalog.load(new_filename) + diff = CatalogDiff.diff(old_cat, new_cat) + return { + "new_count": len(diff["new"]), + "missing_count": len(diff["missing"]), + "changed_count": len(diff["changed"]), + "stable_count": len(diff["stable"]), + "formatted": CatalogDiff.format_diff(diff), + } + + return await asyncio.to_thread(_compare) + + +@mcp.tool() +async def list_surveys(ctx: Context) -> list[dict]: + """List saved survey catalog files from ~/.skywalker1/surveys/, + newest first, with carrier counts and metadata.""" + return await asyncio.to_thread(CarrierCatalog.list_surveys) + + +# ───────────────────────────────────────────── +# Dish Motor Control Tools +# ───────────────────────────────────────────── + +@mcp.tool() +async def move_dish( + ctx: Context, + action: str, + value: float = 0.0, + observer_lon: float = 0.0, + continuous: bool = False, +) -> dict: + """Control the DiSEqC 1.2 dish motor. + + action: one of 'halt', 'east', 'west', 'goto', 'gotox' + value: + - For east/west: number of steps (1-127). Must set continuous=True for non-stop drive. + - For goto: position slot number (0-255, 0=reference) + - For gotox: satellite longitude (negative=west) + observer_lon: your longitude (for gotox only, negative=west) + continuous: must be explicitly True to allow continuous (non-stop) motor drive. + Without this, steps=0 is rejected as a safety measure.""" + bridge = _get_bridge(ctx) + + def _move(): + with bridge.lock: + if action == "halt": + bridge._dev.motor_halt() + return {"action": "halt", "status": "stopped"} + + elif action in ("east", "west"): + steps = int(value) + if steps == 0 and not continuous: + return { + "error": "steps=0 means CONTINUOUS drive (motor never stops). " + "Set continuous=True to confirm, or use steps=1-127. " + "Send action='halt' to stop a running motor.", + } + if steps < 0 or steps > 127: + return {"error": f"steps must be 0-127, got {steps}"} + if action == "east": + bridge._dev.motor_drive_east(steps) + else: + bridge._dev.motor_drive_west(steps) + mode = "continuous (send halt to stop)" if steps == 0 else "stepped" + return {"action": action, "steps": steps, "mode": mode, "status": "driving", + "continuous": steps == 0} + + elif action == "goto": + slot = int(value) + if slot < 0 or slot > 255: + return {"error": f"Slot must be 0-255, got {slot}"} + bridge._dev.motor_goto_position(slot) + return {"action": "goto", "slot": slot, "status": "moving"} + + elif action == "gotox": + from skywalker_lib import usals_angle + angle = usals_angle(observer_lon, value) + bridge._dev.motor_goto_x(observer_lon, value) + return { + "action": "gotox", + "satellite_lon": value, + "observer_lon": observer_lon, + "motor_angle_deg": round(angle, 2), + "direction": "west" if angle < 0 else "east", + "status": "moving", + } + + else: + return {"error": f"Unknown action '{action}'. Valid: halt, east, west, goto, gotox"} + + result = await asyncio.to_thread(_move) + + # Motor watchdog management: cancel on halt/goto/gotox, start on continuous drive + if "error" not in result: + if action == "halt": + bridge.cancel_motor_watchdog() + elif action in ("goto", "gotox"): + # GotoX/Goto have inherent motor-stop at destination + bridge.cancel_motor_watchdog() + elif result.get("continuous"): + bridge.start_motor_watchdog() + result["watchdog_secs"] = MOTOR_WATCHDOG_SECS + result["warning"] = ( + f"Motor watchdog active: auto-halt in {MOTOR_WATCHDOG_SECS}s. " + "Send action='halt' to stop sooner." + ) + + return result + + +@mcp.tool() +async def jog_dish( + ctx: Context, + direction: str, + steps: int = 5, +) -> dict: + """Jog the dish a small number of steps east or west, then read signal quality. + Useful for fine-tuning dish alignment. Steps capped at 30 for safety.""" + if direction not in ("east", "west"): + return {"error": "direction must be 'east' or 'west'"} + if steps < 1 or steps > 30: + return {"error": f"steps must be 1-30 for jog (got {steps}). Use move_dish for larger moves."} + + bridge = _get_bridge(ctx) + + def _jog(): + import time + with bridge.lock: + if direction == "east": + bridge._dev.motor_drive_east(steps) + else: + bridge._dev.motor_drive_west(steps) + + time.sleep(0.5 + steps * 0.05) + sig = bridge._dev.signal_monitor() + return { + "direction": direction, + "steps": steps, + "snr_db": round(sig["snr_db"], 2), + "agc1": sig["agc1"], + "power_db": round(sig["power_db"], 2), + "locked": sig["locked"], + } + + return await asyncio.to_thread(_jog) + + +@mcp.tool() +async def store_position( + ctx: Context, + slot: int, +) -> dict: + """Store the current dish position to a memory slot (1-255).""" + if slot < 1 or slot > 255: + return {"error": "Slot must be 1-255 (slot 0 is reference)"} + await _dev_call(ctx, "motor_store_position", slot) + return {"stored": True, "slot": slot} + + +# ───────────────────────────────────────────── +# LNB & Power Tools +# ───────────────────────────────────────────── + +@mcp.tool() +async def set_lnb_config( + ctx: Context, + voltage: str = "", + tone_22khz: bool | None = None, + disable_lnb: bool = False, +) -> dict: + """Configure LNB power supply and 22 kHz tone. + + voltage: '13V' or '18V' (controls polarization: 13V=vertical, 18V=horizontal) + tone_22khz: True to enable (high band), False to disable (low band) + disable_lnb: True to turn off LNB power entirely (for direct L-band input)""" + bridge = _get_bridge(ctx) + + def _configure(): + with bridge.lock: + result = {} + + if disable_lnb: + bridge._dev.start_intersil(on=False) + result["lnb_power"] = "off" + return result + + if voltage: + high = voltage.upper() in ("18V", "18", "H", "L") + bridge._dev.set_lnb_voltage(high) + result["voltage"] = "18V" if high else "13V" + + if tone_22khz is not None: + bridge._dev.set_22khz_tone(tone_22khz) + result["tone_22khz"] = tone_22khz + + return result + + return await asyncio.to_thread(_configure) + + +# ───────────────────────────────────────────── +# I2C Bus Tools +# ───────────────────────────────────────────── + +@mcp.tool() +async def scan_i2c_bus(ctx: Context) -> dict: + """Scan the I2C bus for all responding devices. + Returns list of 7-bit slave addresses that ACK'd.""" + addresses = await _dev_call(ctx, "i2c_bus_scan") + known_devices = { + 0x08: "BCM4500 (demodulator)", + 0x10: "Tuner / LNB controller", + 0x51: "24Cxx EEPROM (config/serial)", + } + devices = [] + for addr in addresses: + devices.append({ + "address": f"0x{addr:02X}", + "decimal": addr, + "known_as": known_devices.get(addr, "unknown"), + }) + return {"device_count": len(devices), "devices": devices} + + +@mcp.tool() +async def read_i2c_register( + ctx: Context, + slave_address: int, + register: int, +) -> dict: + """Read a single byte from an I2C device register. + + slave_address: 7-bit I2C address (e.g. 0x08 for BCM4500) + register: register address to read""" + value = await _dev_call(ctx, "i2c_raw_read", slave_address, register) + return { + "slave": f"0x{slave_address:02X}", + "register": f"0x{register:02X}", + "value": value, + "hex": f"0x{value:02X}", + "binary": f"0b{value:08b}", + } + + +# ───────────────────────────────────────────── +# Transport Stream Tools +# ───────────────────────────────────────────── + +@mcp.tool() +async def capture_transport_stream( + ctx: Context, + duration_secs: float = 3.0, +) -> dict: + """Capture transport stream data from the currently tuned carrier and + parse PAT/PMT/SDT for service information. + + The device must already be tuned and locked to a carrier. + duration_secs: capture time (0.5-30 seconds). + Returns parsed service names, program table, and stream metadata.""" + if duration_secs < 0.5 or duration_secs > 30: + return {"error": f"duration_secs must be 0.5-30, got {duration_secs}"} + + bridge = _get_bridge(ctx) + + def _capture(): + import time + import io + with bridge.lock: + sig = bridge._dev.signal_monitor() + if not sig.get("locked"): + return {"error": "No signal lock. Tune to a carrier first."} + + bridge._dev.arm_transfer(True) + ts_data = bytearray() + try: + deadline = time.time() + duration_secs + while time.time() < deadline: + chunk = bridge._dev.read_stream(timeout=500) + if chunk: + ts_data.extend(chunk) + finally: + bridge._dev.arm_transfer(False) + + if not ts_data: + return {"error": "No TS data received", "bytes_captured": 0} + + result = { + "bytes_captured": len(ts_data), + "packets": len(ts_data) // 188, + "services": [], + "programs": {}, + } + + try: + from ts_analyze import TSReader, PSIParser, parse_pat, parse_sdt + source = io.BytesIO(bytes(ts_data)) + reader = TSReader(source) + psi_pat = PSIParser() + psi_sdt = PSIParser() + pat = None + pmt_pids = set() + + for pkt in reader.iter_packets(max_packets=50000): + if pkt.pid == 0x0000 and pat is None: + section = psi_pat.feed(pkt) + if section is not None: + pat = parse_pat(section) + if pat: + result["programs"] = pat["programs"] + for prog, pid in pat["programs"].items(): + if prog != 0: + pmt_pids.add(pid) + + if pkt.pid == 0x0011: + section = psi_sdt.feed(pkt) + if section is not None: + sdt = parse_sdt(section) + if sdt: + for svc in sdt.get("services", []): + name = svc.get("service_name", "") + if name: + result["services"].append(name) + break + + except Exception as e: + result["parse_error"] = str(e) + + return result + + return await asyncio.to_thread(_capture) + + +# ───────────────────────────────────────────── +# Frequency Identification Tool +# ───────────────────────────────────────────── + +@mcp.tool() +async def identify_frequency( + ctx: Context, + freq_mhz: float, + lnb_lo_mhz: float = 0.0, +) -> dict: + """Look up what service or allocation is at a given frequency. + + freq_mhz: IF frequency (950-2150 MHz) + lnb_lo_mhz: LNB local oscillator (0 = direct input, no conversion) + + Cross-references against L-band allocations and known satellite bands.""" + rf_mhz = freq_mhz + lnb_lo_mhz if lnb_lo_mhz else freq_mhz + + matches = [] + for start, stop, name in LBAND_ALLOCATIONS: + if start <= rf_mhz <= stop: + matches.append({"band": name, "range_mhz": f"{start}-{stop}"}) + + # Known point frequencies + known_freqs = [ + (1420.405, 2.0, "Hydrogen 21 cm line (galactic emission)"), + (1575.42, 2.0, "GPS L1"), + (1227.6, 2.0, "GPS L2"), + (1176.45, 2.0, "GPS L5 / Galileo E5a"), + (1207.14, 2.0, "Galileo E5b"), + (1602.0, 10.0, "GLONASS L1 (FDMA center)"), + (1246.0, 10.0, "GLONASS L2 (FDMA center)"), + (1544.5, 1.0, "COSPAS-SARSAT (EPIRB)"), + ] + for center, tolerance, name in known_freqs: + if abs(rf_mhz - center) <= tolerance: + matches.append({"signal": name, "center_mhz": center}) + + return { + "if_freq_mhz": freq_mhz, + "rf_freq_mhz": rf_mhz if lnb_lo_mhz else None, + "lnb_lo_mhz": lnb_lo_mhz or None, + "matches": matches, + "in_if_range": 950 <= freq_mhz <= 2150, + } + + +# ───────────────────────────────────────────── +# MCP Resources +# ───────────────────────────────────────────── + +@mcp.resource("skywalker://status") +async def resource_status() -> str: + """Live device status: firmware, config, signal.""" + if _bridge is None: + return json.dumps({"error": "Device not connected"}) + + def _read(): + with _bridge.lock: + fw = _bridge._dev.get_fw_version() + config = _bridge._dev.get_config() + sig = _bridge._dev.signal_monitor() + error = _bridge._dev.get_last_error() + return json.dumps({ + "firmware": fw["version"], + "firmware_date": fw["date"], + "config_bits": {name: is_set for name, is_set in format_config_bits(config)}, + "signal": { + "snr_db": round(sig["snr_db"], 2), + "agc1": sig["agc1"], + "power_db": round(sig["power_db"], 2), + "locked": sig["locked"], + }, + "last_error": ERROR_NAMES.get(error, f"0x{error:02X}"), + }, indent=2) + + return await asyncio.to_thread(_read) + + +@mcp.resource("skywalker://catalog/latest") +async def resource_latest_catalog() -> str: + """Most recent survey catalog.""" + def _read(): + surveys = CarrierCatalog.list_surveys() + if not surveys: + return json.dumps({"error": "No surveys saved yet"}) + latest = surveys[0] + cat = CarrierCatalog.load(latest["filename"]) + return json.dumps(cat.to_dict(), indent=2) + + return await asyncio.to_thread(_read) + + +@mcp.resource("skywalker://allocations/lband") +async def resource_lband_allocations() -> str: + """L-band frequency allocation table (950-2150 MHz IF range).""" + allocations = [] + for start, stop, name in LBAND_ALLOCATIONS: + allocations.append({ + "start_mhz": start, + "stop_mhz": stop, + "name": name, + "in_if_range": (start >= 950 or stop >= 950) and (start <= 2150), + }) + + known = [ + {"freq_mhz": 1420.405, "name": "Hydrogen 21 cm line", "type": "spectral_line"}, + {"freq_mhz": 1575.42, "name": "GPS L1", "type": "navigation"}, + {"freq_mhz": 1227.6, "name": "GPS L2", "type": "navigation"}, + {"freq_mhz": 1176.45, "name": "GPS L5 / Galileo E5a", "type": "navigation"}, + {"freq_mhz": 1602.0, "name": "GLONASS L1", "type": "navigation"}, + {"freq_mhz": 1544.5, "name": "COSPAS-SARSAT", "type": "distress"}, + ] + + return json.dumps({ + "allocations": allocations, + "known_frequencies": known, + "note": "These frequencies are directly receivable (no LNB) when " + "an antenna is connected to the F-connector input.", + }, indent=2) + + +@mcp.resource("skywalker://modulations") +async def resource_modulations() -> str: + """Supported modulation types and FEC rates.""" + mods = {} + for name, (idx, desc) in MODULATIONS.items(): + fec_group = MOD_FEC_GROUP.get(name, "dvbs") + fec_options = list(FEC_RATES.get(fec_group, {}).keys()) + mods[name] = { + "index": idx, + "description": desc, + "fec_options": fec_options, + } + return json.dumps(mods, indent=2) + + +# ───────────────────────────────────────────── +# MCP Prompts +# ───────────────────────────────────────────── + +@mcp.prompt() +async def explore_rf_environment() -> str: + """Autonomous RF environment exploration prompt. + Instructs the LLM to systematically survey and document the RF spectrum.""" + return """You have access to a Genpix SkyWalker-1 DVB-S USB receiver via MCP tools. +Your task: systematically explore the RF environment and build a knowledge base +of what you discover. + +Strategy: +1. Start with get_device_status to verify the hardware is working +2. Run a full-band sweep_spectrum (950-2150 MHz) to see what's out there +3. For each detected peak, use identify_frequency to classify it +4. For strong carriers, try tune_frequency with different modulations +5. If a carrier locks, use capture_transport_stream to identify services +6. Use the dish motor (move_dish/jog_dish) to explore different satellites +7. Compare results with any previous surveys (list_surveys/compare_surveys) + +Report your findings as you go. Note any anomalies or interesting signals.""" + + +@mcp.prompt() +async def hydrogen_line_observation() -> str: + """Guide for observing the hydrogen 21 cm line at 1420.405 MHz.""" + return """You have access to a Genpix SkyWalker-1 receiver for hydrogen 21 cm observation. + +IMPORTANT: This requires direct antenna input (no LNB). Set disable_lnb=True. + +Procedure: +1. Verify device with get_device_status +2. Configure: set_lnb_config(disable_lnb=True) +3. Sweep the hydrogen line region: sweep_spectrum(start_mhz=1418, stop_mhz=1423, step_mhz=0.5) +4. Look for a broad power bump centered near 1420.405 MHz +5. Compare with adjacent "empty" spectrum (e.g., 1430-1435 MHz) as a control +6. The velocity of the hydrogen gas can be calculated from Doppler shift: + v = c * (f_observed - 1420.405) / 1420.405 + +The emission is broad (several MHz) due to galactic rotation velocity dispersion. +You won't see a sharp spike — look for elevated power across the 1419-1421 MHz range.""" + + +def main(): + mcp.run() + + +if __name__ == "__main__": + main() diff --git a/mcp/skywalker-mcp/tests/conftest.py b/mcp/skywalker-mcp/tests/conftest.py index 6010bb2..67865b8 100644 --- a/mcp/skywalker-mcp/tests/conftest.py +++ b/mcp/skywalker-mcp/tests/conftest.py @@ -1,47 +1,47 @@ -"""Shared fixtures for skywalker-mcp tests.""" - -from unittest.mock import MagicMock - -import pytest - -import skywalker_mcp.server as srv -from skywalker_mcp.mock_device import MockSkyWalker1 -from skywalker_mcp.server import DeviceBridge - - -class MockContext: - """Minimal mock of FastMCP Context for direct tool function calls. - - Provides the bridge via request_context.lifespan_context["bridge"], - matching what _get_bridge(ctx) expects. - """ - - def __init__(self, bridge: DeviceBridge): - self.request_context = MagicMock() - self.request_context.lifespan_context = {"bridge": bridge} - self._progress = [] - - async def report_progress(self, current, total): - self._progress.append((current, total)) - - -@pytest.fixture -def mock_device(): - """Provide a fresh MockSkyWalker1 instance.""" - return MockSkyWalker1() - - -@pytest.fixture -def bridge(mock_device): - """Provide a DeviceBridge wrapping the mock device.""" - b = DeviceBridge(mock_device) - srv._bridge = b - yield b - b.cancel_motor_watchdog() - srv._bridge = None - - -@pytest.fixture -def ctx(bridge): - """Provide a MockContext wired to the bridge.""" - return MockContext(bridge) +"""Shared fixtures for skywalker-mcp tests.""" + +from unittest.mock import MagicMock + +import pytest + +import skywalker_mcp.server as srv +from skywalker_mcp.mock_device import MockSkyWalker1 +from skywalker_mcp.server import DeviceBridge + + +class MockContext: + """Minimal mock of FastMCP Context for direct tool function calls. + + Provides the bridge via request_context.lifespan_context["bridge"], + matching what _get_bridge(ctx) expects. + """ + + def __init__(self, bridge: DeviceBridge): + self.request_context = MagicMock() + self.request_context.lifespan_context = {"bridge": bridge} + self._progress = [] + + async def report_progress(self, current, total): + self._progress.append((current, total)) + + +@pytest.fixture +def mock_device(): + """Provide a fresh MockSkyWalker1 instance.""" + return MockSkyWalker1() + + +@pytest.fixture +def bridge(mock_device): + """Provide a DeviceBridge wrapping the mock device.""" + b = DeviceBridge(mock_device) + srv._bridge = b + yield b + b.cancel_motor_watchdog() + srv._bridge = None + + +@pytest.fixture +def ctx(bridge): + """Provide a MockContext wired to the bridge.""" + return MockContext(bridge) diff --git a/mcp/skywalker-mcp/tests/test_server.py b/mcp/skywalker-mcp/tests/test_server.py index d1b78d4..62418c5 100644 --- a/mcp/skywalker-mcp/tests/test_server.py +++ b/mcp/skywalker-mcp/tests/test_server.py @@ -1,503 +1,503 @@ -"""Tests for skywalker-mcp server tools, validation, and safety. - -Calls async tool functions directly with a MockContext. No real USB hardware -or MCP transport needed — tests validation, safety, and response formatting. -""" - -import asyncio - -import pytest - -from skywalker_mcp.server import ( - get_device_status as _get_device_status, - get_signal_quality as _get_signal_quality, - get_stream_diagnostics as _get_stream_diagnostics, - sweep_spectrum as _sweep_spectrum, - tune_frequency as _tune_frequency, - run_blind_scan as _run_blind_scan, - move_dish as _move_dish, - jog_dish as _jog_dish, - store_position as _store_position, - set_lnb_config as _set_lnb_config, - scan_i2c_bus as _scan_i2c_bus, - read_i2c_register as _read_i2c_register, - capture_transport_stream as _capture_transport_stream, - identify_frequency as _identify_frequency, - compare_surveys as _compare_surveys, - mcp, - MOTOR_WATCHDOG_SECS, -) - -# Unwrap FastMCP Tool objects → raw async functions for direct testing. -# @mcp.tool() wraps each function as a Tool(fn=...) Pydantic model; -# .fn gives us the original async def we can call with MockContext. -get_device_status = _get_device_status.fn -get_signal_quality = _get_signal_quality.fn -get_stream_diagnostics = _get_stream_diagnostics.fn -sweep_spectrum = _sweep_spectrum.fn -tune_frequency = _tune_frequency.fn -run_blind_scan = _run_blind_scan.fn -move_dish = _move_dish.fn -jog_dish = _jog_dish.fn -store_position = _store_position.fn -set_lnb_config = _set_lnb_config.fn -scan_i2c_bus = _scan_i2c_bus.fn -read_i2c_register = _read_i2c_register.fn -capture_transport_stream = _capture_transport_stream.fn -identify_frequency = _identify_frequency.fn -compare_surveys = _compare_surveys.fn - - -# ───────────────────────────────────────────── -# Tool Registration -# ───────────────────────────────────────────── - -def test_tool_count(): - """17 tools should be registered.""" - assert len(mcp._tool_manager._tools) == 17 - - -def test_tool_names(): - """All expected tool names are present.""" - names = set(mcp._tool_manager._tools.keys()) - expected = { - "get_device_status", "get_signal_quality", "get_stream_diagnostics", - "sweep_spectrum", "tune_frequency", "run_blind_scan", - "run_carrier_survey", "compare_surveys", "list_surveys", - "move_dish", "jog_dish", "store_position", - "set_lnb_config", "scan_i2c_bus", "read_i2c_register", - "capture_transport_stream", "identify_frequency", - } - assert expected == names - - -def test_resource_count(): - """4 resources registered.""" - assert len(mcp._resource_manager._resources) == 4 - - -def test_prompt_count(): - """2 prompts registered.""" - assert len(mcp._prompt_manager._prompts) == 2 - - -# ───────────────────────────────────────────── -# Device Status Tools -# ───────────────────────────────────────────── - -async def test_get_device_status(ctx): - result = await get_device_status(ctx) - assert "3.05.0" in result["firmware"]["version"] - assert result["usb_speed"] == "High (480 Mbps)" - assert "de ad be ef" in result["serial"] - - -async def test_get_signal_quality(ctx): - result = await get_signal_quality(ctx) - assert result["snr_db"] == 8.5 - assert result["locked"] is True - assert result["agc1"] == 1200 - - -async def test_get_stream_diagnostics(ctx): - result = await get_stream_diagnostics(ctx) - assert result["poll_count"] == 100 - assert result["overflow_count"] == 0 - - -# ───────────────────────────────────────────── -# Spectrum Sweep Validation -# ───────────────────────────────────────────── - -async def test_sweep_defaults(ctx): - result = await sweep_spectrum(ctx) - assert result["start_mhz"] == 950.0 - assert result["stop_mhz"] == 2150.0 - assert result["num_points"] > 0 - assert len(result["frequencies_mhz"]) == result["num_points"] - assert len(result["powers_db"]) == result["num_points"] - - -async def test_sweep_narrow_band(ctx): - result = await sweep_spectrum(ctx, start_mhz=1418.0, stop_mhz=1423.0, step_mhz=0.5) - assert result["num_points"] == 11 - assert result["step_mhz"] == 0.5 - - -async def test_sweep_freq_below_range(ctx): - result = await sweep_spectrum(ctx, start_mhz=800.0) - assert "error" in result - assert "950" in result["error"] - - -async def test_sweep_freq_above_range(ctx): - result = await sweep_spectrum(ctx, stop_mhz=3000.0) - assert "error" in result - assert "2150" in result["error"] - - -async def test_sweep_start_gt_stop(ctx): - result = await sweep_spectrum(ctx, start_mhz=1500.0, stop_mhz=1000.0) - assert "error" in result - assert "less than" in result["error"] - - -async def test_sweep_bad_step(ctx): - result = await sweep_spectrum(ctx, step_mhz=0.01) - assert "error" in result - assert "step_mhz" in result["error"] - - -async def test_sweep_step_too_large(ctx): - result = await sweep_spectrum(ctx, step_mhz=200.0) - assert "error" in result - - -async def test_sweep_bad_dwell(ctx): - result = await sweep_spectrum(ctx, dwell_ms=0) - assert "error" in result - assert "dwell_ms" in result["error"] - - -async def test_sweep_dwell_too_high(ctx): - result = await sweep_spectrum(ctx, dwell_ms=300) - assert "error" in result - - -# ───────────────────────────────────────────── -# Tune Frequency Validation -# ───────────────────────────────────────────── - -async def test_tune_valid(ctx): - result = await tune_frequency(ctx, freq_mhz=1420.0, symbol_rate_ksps=5000) - assert result["locked"] is True - assert result["freq_mhz"] == 1420.0 - assert result["modulation"] == "qpsk" - - -async def test_tune_below_range(ctx): - result = await tune_frequency(ctx, freq_mhz=500.0) - assert "error" in result - assert "950" in result["error"] - - -async def test_tune_above_range(ctx): - result = await tune_frequency(ctx, freq_mhz=2200.0) - assert "error" in result - - -async def test_tune_bad_sr_low(ctx): - result = await tune_frequency(ctx, freq_mhz=1200.0, symbol_rate_ksps=100) - assert "error" in result - assert "256" in result["error"] - - -async def test_tune_bad_sr_high(ctx): - result = await tune_frequency(ctx, freq_mhz=1200.0, symbol_rate_ksps=50000) - assert "error" in result - assert "30000" in result["error"] - - -async def test_tune_bad_modulation(ctx): - result = await tune_frequency(ctx, freq_mhz=1200.0, modulation="dvb-s2") - assert "error" in result - assert "dvb-s2" in result["error"] - - -async def test_tune_bad_dwell(ctx): - result = await tune_frequency(ctx, freq_mhz=1200.0, dwell_ms=0) - assert "error" in result - - -# ───────────────────────────────────────────── -# Blind Scan Validation -# ───────────────────────────────────────────── - -async def test_blind_scan_valid(ctx): - result = await run_blind_scan(ctx, freq_mhz=1200.0) - assert result["locked"] is True - assert result["sr_ksps"] == 20000.0 - - -async def test_blind_scan_freq_below(ctx): - result = await run_blind_scan(ctx, freq_mhz=500.0) - assert "error" in result - - -async def test_blind_scan_sr_below_min(ctx): - result = await run_blind_scan(ctx, freq_mhz=1200.0, sr_min_ksps=100) - assert "error" in result - assert "256" in result["error"] - - -async def test_blind_scan_sr_above_max(ctx): - result = await run_blind_scan(ctx, freq_mhz=1200.0, sr_max_ksps=50000) - assert "error" in result - assert "30000" in result["error"] - - -# ───────────────────────────────────────────── -# Motor Safety Tests -# ───────────────────────────────────────────── - -async def test_motor_halt(ctx, mock_device): - result = await move_dish(ctx, action="halt") - assert result["action"] == "halt" - assert result["status"] == "stopped" - assert mock_device._motor_halted is True - - -async def test_motor_east_stepped(ctx): - result = await move_dish(ctx, action="east", value=10) - assert result["steps"] == 10 - assert result["mode"] == "stepped" - - -async def test_motor_west_stepped(ctx): - result = await move_dish(ctx, action="west", value=5) - assert result["steps"] == 5 - assert result["action"] == "west" - - -async def test_motor_continuous_rejected(ctx): - """Continuous drive (steps=0) without explicit flag is rejected.""" - result = await move_dish(ctx, action="east", value=0) - assert "error" in result - assert "CONTINUOUS" in result["error"] - assert "continuous=True" in result["error"] - - -async def test_motor_continuous_with_flag(ctx): - """Continuous drive with explicit flag succeeds and starts watchdog.""" - result = await move_dish(ctx, action="west", value=0, continuous=True) - assert result["status"] == "driving" - assert result["continuous"] is True - assert result["watchdog_secs"] == MOTOR_WATCHDOG_SECS - assert "warning" in result - - -async def test_motor_steps_negative(ctx): - result = await move_dish(ctx, action="east", value=-5) - assert "error" in result - assert "0-127" in result["error"] - - -async def test_motor_steps_too_high(ctx): - result = await move_dish(ctx, action="east", value=200) - assert "error" in result - assert "0-127" in result["error"] - - -async def test_motor_gotox(ctx, mock_device): - result = await move_dish(ctx, action="gotox", value=-97.0, observer_lon=-96.8) - assert result["action"] == "gotox" - assert result["satellite_lon"] == -97.0 - assert "motor_angle_deg" in result - assert ("motor_goto_x", (-96.8, -97.0), {}) in mock_device._calls - - -async def test_motor_goto_slot(ctx, mock_device): - result = await move_dish(ctx, action="goto", value=5) - assert result["slot"] == 5 - assert result["action"] == "goto" - - -async def test_motor_goto_slot_out_of_range(ctx): - result = await move_dish(ctx, action="goto", value=300) - assert "error" in result - assert "0-255" in result["error"] - - -async def test_motor_invalid_action(ctx): - result = await move_dish(ctx, action="spin") - assert "error" in result - assert "spin" in result["error"] - - -# ───────────────────────────────────────────── -# Motor Watchdog Tests -# ───────────────────────────────────────────── - -async def test_watchdog_starts_on_continuous(ctx, bridge): - """Watchdog task is created when continuous drive starts.""" - await move_dish(ctx, action="west", value=0, continuous=True) - assert bridge._motor_watchdog is not None - assert not bridge._motor_watchdog.done() - bridge.cancel_motor_watchdog() - - -async def test_watchdog_cancelled_on_halt(ctx, bridge): - """Halt cancels the watchdog.""" - await move_dish(ctx, action="east", value=0, continuous=True) - assert bridge._motor_watchdog is not None - await move_dish(ctx, action="halt") - assert bridge._motor_watchdog is None or bridge._motor_watchdog.cancelled() - - -async def test_watchdog_fires_and_halts(ctx, bridge, mock_device): - """Watchdog auto-halts after timeout.""" - bridge.start_motor_watchdog(timeout=0.1) # 100ms for test speed - await asyncio.sleep(0.3) # Wait for watchdog to fire - assert mock_device._motor_halted is True - - -# ───────────────────────────────────────────── -# Jog Dish Tests -# ───────────────────────────────────────────── - -async def test_jog_valid(ctx): - result = await jog_dish(ctx, direction="east", steps=5) - assert result["direction"] == "east" - assert result["steps"] == 5 - assert "snr_db" in result - - -async def test_jog_too_many_steps(ctx): - result = await jog_dish(ctx, direction="east", steps=50) - assert "error" in result - assert "1-30" in result["error"] - - -async def test_jog_zero_steps(ctx): - result = await jog_dish(ctx, direction="east", steps=0) - assert "error" in result - - -async def test_jog_bad_direction(ctx): - result = await jog_dish(ctx, direction="up") - assert "error" in result - assert "east" in result["error"] - - -# ───────────────────────────────────────────── -# Store Position Tests -# ───────────────────────────────────────────── - -async def test_store_position_valid(ctx, mock_device): - result = await store_position(ctx, slot=5) - assert result["stored"] is True - assert result["slot"] == 5 - - -async def test_store_position_slot_zero(ctx): - result = await store_position(ctx, slot=0) - assert "error" in result - - -async def test_store_position_slot_too_high(ctx): - result = await store_position(ctx, slot=300) - assert "error" in result - - -# ───────────────────────────────────────────── -# LNB & I2C Tests -# ───────────────────────────────────────────── - -async def test_lnb_disable(ctx, mock_device): - result = await set_lnb_config(ctx, disable_lnb=True) - assert result["lnb_power"] == "off" - assert mock_device._lnb_on is False - - -async def test_lnb_voltage(ctx, mock_device): - result = await set_lnb_config(ctx, voltage="18V") - assert result["voltage"] == "18V" - - -async def test_lnb_tone(ctx, mock_device): - result = await set_lnb_config(ctx, tone_22khz=True) - assert result["tone_22khz"] is True - - -async def test_i2c_scan(ctx): - result = await scan_i2c_bus(ctx) - assert result["device_count"] == 3 - addresses = [d["address"] for d in result["devices"]] - assert "0x08" in addresses - assert "0x10" in addresses - assert "0x51" in addresses - - -async def test_i2c_read(ctx): - result = await read_i2c_register(ctx, slave_address=0x08, register=0x00) - assert result["value"] == 0xAB - assert result["hex"] == "0xAB" - assert "0b" in result["binary"] - - -# ───────────────────────────────────────────── -# Transport Stream Validation -# ───────────────────────────────────────────── - -async def test_ts_capture_duration_too_short(ctx): - result = await capture_transport_stream(ctx, duration_secs=0.1) - assert "error" in result - assert "0.5-30" in result["error"] - - -async def test_ts_capture_duration_too_long(ctx): - result = await capture_transport_stream(ctx, duration_secs=60.0) - assert "error" in result - - -async def test_ts_capture_valid(ctx): - """Valid TS capture returns packet count (mock device is locked).""" - result = await capture_transport_stream(ctx, duration_secs=0.5) - assert result["bytes_captured"] > 0 - assert result["packets"] > 0 - - -# ───────────────────────────────────────────── -# Frequency Identification -# ───────────────────────────────────────────── - -async def test_identify_hydrogen(ctx): - result = await identify_frequency(ctx, freq_mhz=1420.405) - assert result["in_if_range"] is True - signals = [m.get("signal", "") for m in result["matches"]] - assert any("Hydrogen" in s for s in signals) - - -async def test_identify_gps_l1(ctx): - result = await identify_frequency(ctx, freq_mhz=1575.42) - signals = [m.get("signal", "") for m in result["matches"]] - assert any("GPS L1" in s for s in signals) - - -async def test_identify_gps_l5(ctx): - result = await identify_frequency(ctx, freq_mhz=1176.45) - signals = [m.get("signal", "") for m in result["matches"]] - assert any("GPS L5" in s or "Galileo E5a" in s for s in signals) - - -async def test_identify_with_lnb(ctx): - result = await identify_frequency(ctx, freq_mhz=1200.0, lnb_lo_mhz=9750.0) - assert result["rf_freq_mhz"] == 10950.0 - assert result["lnb_lo_mhz"] == 9750.0 - - -async def test_identify_no_lnb(ctx): - result = await identify_frequency(ctx, freq_mhz=1200.0) - assert result["rf_freq_mhz"] is None - assert result["lnb_lo_mhz"] is None - - -# ───────────────────────────────────────────── -# Path Traversal Protection -# ───────────────────────────────────────────── - -async def test_compare_path_traversal(ctx): - result = await compare_surveys(ctx, old_filename="../../../etc/passwd", new_filename="ok.json") - assert "error" in result - assert "plain filename" in result["error"] - - -async def test_compare_dotdot_in_name(ctx): - result = await compare_surveys(ctx, old_filename="..hidden.json", new_filename="ok.json") - assert "error" in result - - -async def test_compare_slash_in_name(ctx): - result = await compare_surveys(ctx, old_filename="subdir/file.json", new_filename="ok.json") - assert "error" in result - assert "plain filename" in result["error"] +"""Tests for skywalker-mcp server tools, validation, and safety. + +Calls async tool functions directly with a MockContext. No real USB hardware +or MCP transport needed — tests validation, safety, and response formatting. +""" + +import asyncio + +import pytest + +from skywalker_mcp.server import ( + get_device_status as _get_device_status, + get_signal_quality as _get_signal_quality, + get_stream_diagnostics as _get_stream_diagnostics, + sweep_spectrum as _sweep_spectrum, + tune_frequency as _tune_frequency, + run_blind_scan as _run_blind_scan, + move_dish as _move_dish, + jog_dish as _jog_dish, + store_position as _store_position, + set_lnb_config as _set_lnb_config, + scan_i2c_bus as _scan_i2c_bus, + read_i2c_register as _read_i2c_register, + capture_transport_stream as _capture_transport_stream, + identify_frequency as _identify_frequency, + compare_surveys as _compare_surveys, + mcp, + MOTOR_WATCHDOG_SECS, +) + +# Unwrap FastMCP Tool objects → raw async functions for direct testing. +# @mcp.tool() wraps each function as a Tool(fn=...) Pydantic model; +# .fn gives us the original async def we can call with MockContext. +get_device_status = _get_device_status.fn +get_signal_quality = _get_signal_quality.fn +get_stream_diagnostics = _get_stream_diagnostics.fn +sweep_spectrum = _sweep_spectrum.fn +tune_frequency = _tune_frequency.fn +run_blind_scan = _run_blind_scan.fn +move_dish = _move_dish.fn +jog_dish = _jog_dish.fn +store_position = _store_position.fn +set_lnb_config = _set_lnb_config.fn +scan_i2c_bus = _scan_i2c_bus.fn +read_i2c_register = _read_i2c_register.fn +capture_transport_stream = _capture_transport_stream.fn +identify_frequency = _identify_frequency.fn +compare_surveys = _compare_surveys.fn + + +# ───────────────────────────────────────────── +# Tool Registration +# ───────────────────────────────────────────── + +def test_tool_count(): + """17 tools should be registered.""" + assert len(mcp._tool_manager._tools) == 17 + + +def test_tool_names(): + """All expected tool names are present.""" + names = set(mcp._tool_manager._tools.keys()) + expected = { + "get_device_status", "get_signal_quality", "get_stream_diagnostics", + "sweep_spectrum", "tune_frequency", "run_blind_scan", + "run_carrier_survey", "compare_surveys", "list_surveys", + "move_dish", "jog_dish", "store_position", + "set_lnb_config", "scan_i2c_bus", "read_i2c_register", + "capture_transport_stream", "identify_frequency", + } + assert expected == names + + +def test_resource_count(): + """4 resources registered.""" + assert len(mcp._resource_manager._resources) == 4 + + +def test_prompt_count(): + """2 prompts registered.""" + assert len(mcp._prompt_manager._prompts) == 2 + + +# ───────────────────────────────────────────── +# Device Status Tools +# ───────────────────────────────────────────── + +async def test_get_device_status(ctx): + result = await get_device_status(ctx) + assert "3.05.0" in result["firmware"]["version"] + assert result["usb_speed"] == "High (480 Mbps)" + assert "de ad be ef" in result["serial"] + + +async def test_get_signal_quality(ctx): + result = await get_signal_quality(ctx) + assert result["snr_db"] == 8.5 + assert result["locked"] is True + assert result["agc1"] == 1200 + + +async def test_get_stream_diagnostics(ctx): + result = await get_stream_diagnostics(ctx) + assert result["poll_count"] == 100 + assert result["overflow_count"] == 0 + + +# ───────────────────────────────────────────── +# Spectrum Sweep Validation +# ───────────────────────────────────────────── + +async def test_sweep_defaults(ctx): + result = await sweep_spectrum(ctx) + assert result["start_mhz"] == 950.0 + assert result["stop_mhz"] == 2150.0 + assert result["num_points"] > 0 + assert len(result["frequencies_mhz"]) == result["num_points"] + assert len(result["powers_db"]) == result["num_points"] + + +async def test_sweep_narrow_band(ctx): + result = await sweep_spectrum(ctx, start_mhz=1418.0, stop_mhz=1423.0, step_mhz=0.5) + assert result["num_points"] == 11 + assert result["step_mhz"] == 0.5 + + +async def test_sweep_freq_below_range(ctx): + result = await sweep_spectrum(ctx, start_mhz=800.0) + assert "error" in result + assert "950" in result["error"] + + +async def test_sweep_freq_above_range(ctx): + result = await sweep_spectrum(ctx, stop_mhz=3000.0) + assert "error" in result + assert "2150" in result["error"] + + +async def test_sweep_start_gt_stop(ctx): + result = await sweep_spectrum(ctx, start_mhz=1500.0, stop_mhz=1000.0) + assert "error" in result + assert "less than" in result["error"] + + +async def test_sweep_bad_step(ctx): + result = await sweep_spectrum(ctx, step_mhz=0.01) + assert "error" in result + assert "step_mhz" in result["error"] + + +async def test_sweep_step_too_large(ctx): + result = await sweep_spectrum(ctx, step_mhz=200.0) + assert "error" in result + + +async def test_sweep_bad_dwell(ctx): + result = await sweep_spectrum(ctx, dwell_ms=0) + assert "error" in result + assert "dwell_ms" in result["error"] + + +async def test_sweep_dwell_too_high(ctx): + result = await sweep_spectrum(ctx, dwell_ms=300) + assert "error" in result + + +# ───────────────────────────────────────────── +# Tune Frequency Validation +# ───────────────────────────────────────────── + +async def test_tune_valid(ctx): + result = await tune_frequency(ctx, freq_mhz=1420.0, symbol_rate_ksps=5000) + assert result["locked"] is True + assert result["freq_mhz"] == 1420.0 + assert result["modulation"] == "qpsk" + + +async def test_tune_below_range(ctx): + result = await tune_frequency(ctx, freq_mhz=500.0) + assert "error" in result + assert "950" in result["error"] + + +async def test_tune_above_range(ctx): + result = await tune_frequency(ctx, freq_mhz=2200.0) + assert "error" in result + + +async def test_tune_bad_sr_low(ctx): + result = await tune_frequency(ctx, freq_mhz=1200.0, symbol_rate_ksps=100) + assert "error" in result + assert "256" in result["error"] + + +async def test_tune_bad_sr_high(ctx): + result = await tune_frequency(ctx, freq_mhz=1200.0, symbol_rate_ksps=50000) + assert "error" in result + assert "30000" in result["error"] + + +async def test_tune_bad_modulation(ctx): + result = await tune_frequency(ctx, freq_mhz=1200.0, modulation="dvb-s2") + assert "error" in result + assert "dvb-s2" in result["error"] + + +async def test_tune_bad_dwell(ctx): + result = await tune_frequency(ctx, freq_mhz=1200.0, dwell_ms=0) + assert "error" in result + + +# ───────────────────────────────────────────── +# Blind Scan Validation +# ───────────────────────────────────────────── + +async def test_blind_scan_valid(ctx): + result = await run_blind_scan(ctx, freq_mhz=1200.0) + assert result["locked"] is True + assert result["sr_ksps"] == 20000.0 + + +async def test_blind_scan_freq_below(ctx): + result = await run_blind_scan(ctx, freq_mhz=500.0) + assert "error" in result + + +async def test_blind_scan_sr_below_min(ctx): + result = await run_blind_scan(ctx, freq_mhz=1200.0, sr_min_ksps=100) + assert "error" in result + assert "256" in result["error"] + + +async def test_blind_scan_sr_above_max(ctx): + result = await run_blind_scan(ctx, freq_mhz=1200.0, sr_max_ksps=50000) + assert "error" in result + assert "30000" in result["error"] + + +# ───────────────────────────────────────────── +# Motor Safety Tests +# ───────────────────────────────────────────── + +async def test_motor_halt(ctx, mock_device): + result = await move_dish(ctx, action="halt") + assert result["action"] == "halt" + assert result["status"] == "stopped" + assert mock_device._motor_halted is True + + +async def test_motor_east_stepped(ctx): + result = await move_dish(ctx, action="east", value=10) + assert result["steps"] == 10 + assert result["mode"] == "stepped" + + +async def test_motor_west_stepped(ctx): + result = await move_dish(ctx, action="west", value=5) + assert result["steps"] == 5 + assert result["action"] == "west" + + +async def test_motor_continuous_rejected(ctx): + """Continuous drive (steps=0) without explicit flag is rejected.""" + result = await move_dish(ctx, action="east", value=0) + assert "error" in result + assert "CONTINUOUS" in result["error"] + assert "continuous=True" in result["error"] + + +async def test_motor_continuous_with_flag(ctx): + """Continuous drive with explicit flag succeeds and starts watchdog.""" + result = await move_dish(ctx, action="west", value=0, continuous=True) + assert result["status"] == "driving" + assert result["continuous"] is True + assert result["watchdog_secs"] == MOTOR_WATCHDOG_SECS + assert "warning" in result + + +async def test_motor_steps_negative(ctx): + result = await move_dish(ctx, action="east", value=-5) + assert "error" in result + assert "0-127" in result["error"] + + +async def test_motor_steps_too_high(ctx): + result = await move_dish(ctx, action="east", value=200) + assert "error" in result + assert "0-127" in result["error"] + + +async def test_motor_gotox(ctx, mock_device): + result = await move_dish(ctx, action="gotox", value=-97.0, observer_lon=-96.8) + assert result["action"] == "gotox" + assert result["satellite_lon"] == -97.0 + assert "motor_angle_deg" in result + assert ("motor_goto_x", (-96.8, -97.0), {}) in mock_device._calls + + +async def test_motor_goto_slot(ctx, mock_device): + result = await move_dish(ctx, action="goto", value=5) + assert result["slot"] == 5 + assert result["action"] == "goto" + + +async def test_motor_goto_slot_out_of_range(ctx): + result = await move_dish(ctx, action="goto", value=300) + assert "error" in result + assert "0-255" in result["error"] + + +async def test_motor_invalid_action(ctx): + result = await move_dish(ctx, action="spin") + assert "error" in result + assert "spin" in result["error"] + + +# ───────────────────────────────────────────── +# Motor Watchdog Tests +# ───────────────────────────────────────────── + +async def test_watchdog_starts_on_continuous(ctx, bridge): + """Watchdog task is created when continuous drive starts.""" + await move_dish(ctx, action="west", value=0, continuous=True) + assert bridge._motor_watchdog is not None + assert not bridge._motor_watchdog.done() + bridge.cancel_motor_watchdog() + + +async def test_watchdog_cancelled_on_halt(ctx, bridge): + """Halt cancels the watchdog.""" + await move_dish(ctx, action="east", value=0, continuous=True) + assert bridge._motor_watchdog is not None + await move_dish(ctx, action="halt") + assert bridge._motor_watchdog is None or bridge._motor_watchdog.cancelled() + + +async def test_watchdog_fires_and_halts(ctx, bridge, mock_device): + """Watchdog auto-halts after timeout.""" + bridge.start_motor_watchdog(timeout=0.1) # 100ms for test speed + await asyncio.sleep(0.3) # Wait for watchdog to fire + assert mock_device._motor_halted is True + + +# ───────────────────────────────────────────── +# Jog Dish Tests +# ───────────────────────────────────────────── + +async def test_jog_valid(ctx): + result = await jog_dish(ctx, direction="east", steps=5) + assert result["direction"] == "east" + assert result["steps"] == 5 + assert "snr_db" in result + + +async def test_jog_too_many_steps(ctx): + result = await jog_dish(ctx, direction="east", steps=50) + assert "error" in result + assert "1-30" in result["error"] + + +async def test_jog_zero_steps(ctx): + result = await jog_dish(ctx, direction="east", steps=0) + assert "error" in result + + +async def test_jog_bad_direction(ctx): + result = await jog_dish(ctx, direction="up") + assert "error" in result + assert "east" in result["error"] + + +# ───────────────────────────────────────────── +# Store Position Tests +# ───────────────────────────────────────────── + +async def test_store_position_valid(ctx, mock_device): + result = await store_position(ctx, slot=5) + assert result["stored"] is True + assert result["slot"] == 5 + + +async def test_store_position_slot_zero(ctx): + result = await store_position(ctx, slot=0) + assert "error" in result + + +async def test_store_position_slot_too_high(ctx): + result = await store_position(ctx, slot=300) + assert "error" in result + + +# ───────────────────────────────────────────── +# LNB & I2C Tests +# ───────────────────────────────────────────── + +async def test_lnb_disable(ctx, mock_device): + result = await set_lnb_config(ctx, disable_lnb=True) + assert result["lnb_power"] == "off" + assert mock_device._lnb_on is False + + +async def test_lnb_voltage(ctx, mock_device): + result = await set_lnb_config(ctx, voltage="18V") + assert result["voltage"] == "18V" + + +async def test_lnb_tone(ctx, mock_device): + result = await set_lnb_config(ctx, tone_22khz=True) + assert result["tone_22khz"] is True + + +async def test_i2c_scan(ctx): + result = await scan_i2c_bus(ctx) + assert result["device_count"] == 3 + addresses = [d["address"] for d in result["devices"]] + assert "0x08" in addresses + assert "0x10" in addresses + assert "0x51" in addresses + + +async def test_i2c_read(ctx): + result = await read_i2c_register(ctx, slave_address=0x08, register=0x00) + assert result["value"] == 0xAB + assert result["hex"] == "0xAB" + assert "0b" in result["binary"] + + +# ───────────────────────────────────────────── +# Transport Stream Validation +# ───────────────────────────────────────────── + +async def test_ts_capture_duration_too_short(ctx): + result = await capture_transport_stream(ctx, duration_secs=0.1) + assert "error" in result + assert "0.5-30" in result["error"] + + +async def test_ts_capture_duration_too_long(ctx): + result = await capture_transport_stream(ctx, duration_secs=60.0) + assert "error" in result + + +async def test_ts_capture_valid(ctx): + """Valid TS capture returns packet count (mock device is locked).""" + result = await capture_transport_stream(ctx, duration_secs=0.5) + assert result["bytes_captured"] > 0 + assert result["packets"] > 0 + + +# ───────────────────────────────────────────── +# Frequency Identification +# ───────────────────────────────────────────── + +async def test_identify_hydrogen(ctx): + result = await identify_frequency(ctx, freq_mhz=1420.405) + assert result["in_if_range"] is True + signals = [m.get("signal", "") for m in result["matches"]] + assert any("Hydrogen" in s for s in signals) + + +async def test_identify_gps_l1(ctx): + result = await identify_frequency(ctx, freq_mhz=1575.42) + signals = [m.get("signal", "") for m in result["matches"]] + assert any("GPS L1" in s for s in signals) + + +async def test_identify_gps_l5(ctx): + result = await identify_frequency(ctx, freq_mhz=1176.45) + signals = [m.get("signal", "") for m in result["matches"]] + assert any("GPS L5" in s or "Galileo E5a" in s for s in signals) + + +async def test_identify_with_lnb(ctx): + result = await identify_frequency(ctx, freq_mhz=1200.0, lnb_lo_mhz=9750.0) + assert result["rf_freq_mhz"] == 10950.0 + assert result["lnb_lo_mhz"] == 9750.0 + + +async def test_identify_no_lnb(ctx): + result = await identify_frequency(ctx, freq_mhz=1200.0) + assert result["rf_freq_mhz"] is None + assert result["lnb_lo_mhz"] is None + + +# ───────────────────────────────────────────── +# Path Traversal Protection +# ───────────────────────────────────────────── + +async def test_compare_path_traversal(ctx): + result = await compare_surveys(ctx, old_filename="../../../etc/passwd", new_filename="ok.json") + assert "error" in result + assert "plain filename" in result["error"] + + +async def test_compare_dotdot_in_name(ctx): + result = await compare_surveys(ctx, old_filename="..hidden.json", new_filename="ok.json") + assert "error" in result + + +async def test_compare_slash_in_name(ctx): + result = await compare_surveys(ctx, old_filename="subdir/file.json", new_filename="ok.json") + assert "error" in result + assert "plain filename" in result["error"] diff --git a/rev2-deep-analysis.md b/rev2-deep-analysis.md index 1ab3c6c..41d9c5c 100644 --- a/rev2-deep-analysis.md +++ b/rev2-deep-analysis.md @@ -1,844 +1,844 @@ -# Genpix SkyWalker-1 Rev.2 v2.10.4 Deep Firmware Analysis - -## Executive Summary - -Rev.2 v2.10.4 has **107 functions** -- the most of any firmware version (vs 61 for v2.06, 88 for v2.13 FW1). The higher function count is driven by three factors: - -1. **Granular function decomposition**: Rev.2 breaks large operations into many small helper functions (10-30 bytes each), where v2.06 inlines them and v2.13 recombines them differently. -2. **A massive 874-byte configuration dispatcher** (`FUN_CODE_0800`) that contains an embedded copy of the main loop, making Ghidra count additional entry points as separate functions. -3. **Extra I2C/demodulator helper chains**, GPIO control primitives, and hardware-polling wait loops that exist as individual callable units. - -Rev.2 sits architecturally between v2.06 and v2.13 -- it has v2.06's INT0 USB re-enumeration behavior but already uses the same descriptor base offset (0x0E00) and stack pointer (SP=0x4F) pattern that v2.13 would adopt. It supports **27 vendor commands** (0x80-0x9A) vs 30 in v2.06/v2.13. - ---- - -## 1. Complete Function Inventory (107 Functions) - -### 1.1 Vector Table and ISR Region (0x0000-0x0055) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x0000 | `RESET_vector` | 3 | Jump to main (0x155F) | -| 0x0003 | `INT0_ISR` | 12 | INT0 handler -- USB re-enumeration (bit check + branch) | -| 0x000F | `INT0_ISR_bit_clear` | 36 | INT0 continuation -- CPUCS pulse, IRQ clear, delay | -| 0x0033 | `INT2_USB_GPIF_vector` | 3 | INT2 vector -- clears CCON.4 (PCA timer) | -| 0x0036 | `i2c_exchange_byte` | 5 | I2C byte exchange primitive | -| 0x003B | `I2C_ISR` | 8 | I2C interrupt-style handler, calls FUN_CODE_19f4 | -| 0x0043 | `INT4_FX2_vector` | 8 | INT4 vector -- sets `_0_1`, clears EXIF.4 | -| 0x004B | `INT5_FX2_vector` | 3 | INT5 vector -- empty (RETI) | -| 0x004E | `FUN_CODE_004e` | 3 | Unused vector slot -- empty (RET) | -| 0x0051 | `FUN_CODE_0051` | 2 | Unused vector slot -- empty (RET) | -| 0x0053 | `INT6_FX2_vector` | 3 | INT6 vector -- sets `_0_1`, clears EXIF.4 | - -### 1.2 Vendor Command Dispatch (0x0056-0x0318) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x0056 | `vendor_cmd_dispatch` | 342 | Vendor request dispatcher (0x80-0x9A range check, jump table at 0x0076) | -| 0x01AC | `FUN_CODE_01ac` | 361 | Vendor cmd 0x80: GET_8PSK_CONFIG -- reads config, calls FUN_CODE_1f5c | -| 0x0315 | `vendor_cmd_stall` | 2 | Stall handler (empty RET) | -| 0x0317 | `FUN_CODE_0317` | 2 | EP0 completion (empty RET) | -| 0x0319 | `FUN_CODE_0319` | 869 | Standard USB request handler (massive switch for bRequest 0x00-0x0B) | - -### 1.3 Math and Memory Operations (0x067E-0x07FE) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x067E | `FUN_CODE_067e` | 38 | Multi-mode data access dispatcher (computed jump based on address mode) | -| 0x06A4 | `FUN_CODE_06a4` | 45 | Memory read with address mode selection (IRAM/XRAM/CODE) | -| 0x06D1 | `FUN_CODE_06d1` | 18 | Memory write with address mode selection (IRAM/XRAM/paged) | -| 0x06E3 | `FUN_CODE_06e3` | 85 | 16-bit division routine (param_1!=0: 16/8 long div, else 8/8 fast div) | -| 0x0738 | `FUN_CODE_0738` | 79 | 32-bit multiplication (4-byte x 4-byte partial product accumulation) | -| 0x0787 | `FUN_CODE_0787` | 36 | 32-bit subtraction with borrow chain | -| 0x07AB | `FUN_CODE_07ab` | 38 | Table-driven function dispatcher (triplet lookup: key, addr_hi, addr_lo) | -| 0x07D1 | `FUN_CODE_07d1` | 45 | **DiSEqC byte transmit** -- 8 data bits + odd parity via P0.4 | -| 0x07FE | `FUN_CODE_07fe` | 2 | Empty stub (RET) | - -### 1.4 Configuration Dispatcher (0x0800-0x09A8) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x0800 | `FUN_CODE_0800` | 425 | **Massive config/tuning dispatcher** -- 128-entry switch on demod type, embedded main loop copy | -| 0x09A9 | `FUN_CODE_09a9` | 699 | **Main init + main loop** -- initializes hardware, enters infinite poll loop | - -### 1.5 Demodulator/BCM4500 Management (0x0C64-0x0F00) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x0C64 | `FUN_CODE_0c64` | 280 | **BCM4500 firmware loader** -- I2C block transfer with address tracking | -| 0x0D7C | `FUN_CODE_0d7c` | 128 | **GPIF/slave FIFO configuration** -- enables/disables streaming mode | -| 0x0DFC | `FUN_CODE_0dfc` | 4 | Set BANK3_R1 (simple register store) | -| 0x0F00 | `FUN_CODE_0f00` | 256 | I2C multi-byte read with parameter setup | - -### 1.6 USB Endpoint and Descriptor Setup (0x1000-0x1420) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x1000 | `FUN_CODE_1000` | 217 | USB endpoint configuration (FIFO sizes, etc.) | -| 0x10D9 | `FUN_CODE_10d9` | 201 | **USB/peripheral descriptor setup** -- IFCONFIG, GPIF, Timer2, GPIO init | -| 0x11A2 | `FUN_CODE_11a2` | 94 | Serial number / EEPROM reader | -| 0x1200 | `INT4_INT6_handler` | 184 | INT4/INT6 unified handler -- sets `_0_1` flag, clears EXIF.4 | -| 0x12B8 | `FUN_CODE_12b8` | 180 | Configuration structure init (I2C device setup) | -| 0x136C | `FUN_CODE_136c` | 180 | I2C multi-byte write with address/register params | -| 0x1420 | `FUN_CODE_1420` | 319 | Configuration update function | - -### 1.7 Main Entry and Init (0x155F-0x1670) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x155F | `main` | 71 | **RESET entry** -- clears IRAM 0x00-0x7F, sets SP=0x4F, jumps to main_init | -| 0x15A6 | `main_init` | 69 | Init data table processor (table at CODE:0B48), jumps to FUN_CODE_09a9 | -| 0x15EB | `FUN_CODE_15eb` | 133 | Descriptor table walker | -| 0x1670 | `FUN_CODE_1670` | 241 | **I2C demod write with verify** -- write, wait, read-back check | - -### 1.8 I2C Transfer and Demod Functions (0x1761-0x1B90) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x1761 | `FUN_CODE_1761` | 112 | I2C multi-byte read with address auto-increment | -| 0x17D1 | `FUN_CODE_17d1` | 44 | I2C bus wait -- polls FUN_CODE_224f until ready, with timeout | -| 0x17FD | `FUN_CODE_17fd` | 3 | Empty stub (RET) | -| 0x1800 | `FUN_CODE_1800` | 106 | GPIF/FIFO management (identical logic to v2.06/v2.13) | -| 0x186A | `FUN_CODE_186a` | 105 | Tuning/acquisition sequence | -| 0x18D3 | `FUN_CODE_18d3` | 101 | Extended tuning with polling | -| 0x1938 | `FUN_CODE_1938` | 94 | Configuration parameter write (USB setup data -> I2C) | -| 0x1996 | `FUN_CODE_1996` | 94 | Configuration parameter read (I2C -> USB EP0BUF) | -| 0x19F4 | `FUN_CODE_19f4` | 92 | I2C bus controller -- manages SDA/SCL via XRAM 0xE678 | -| 0x1A50 | `FUN_CODE_1a50` | 83 | I2C address select + start condition | -| 0x1AA3 | `FUN_CODE_1aa3` | 82 | I2C stop condition + cleanup | -| 0x1AF5 | `FUN_CODE_1af5` | 9 | I2C address write helper | -| 0x1AFE | `FUN_CODE_1afe` | 3 | Tiny helper (register move) | -| 0x1B01 | `FUN_CODE_1b01` | 67 | I2C byte-level transfer | -| 0x1B44 | `FUN_CODE_1b44` | 76 | I2C ACK/NAK handling | -| 0x1B90 | `FUN_CODE_1b90` | 74 | I2C bus reset / error recovery | - -### 1.9 Delay and DiSEqC Support (0x1BDA-0x1C65) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x1BDA | `delay_routine` | 70 | **Clock-compensated delay** -- adjusts for 12/24/48 MHz CPU clock | -| 0x1C20 | `FUN_CODE_1c20` | 69 | Configuration update (I2C register write chain) | -| 0x1C65 | `FUN_CODE_1c65` | 249 | Configuration update (extended I2C register block) | - -### 1.10 DiSEqC GPIO Bit-Bang (0x1D5E-0x1E3D) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x1D5E | `FUN_CODE_1d5e` | 59 | **DiSEqC message sender** -- iterates bytes, calls FUN_CODE_1e3d per byte | -| 0x1D99 | `FUN_CODE_1d99` | 55 | **I2C device probe with retry** -- 20 attempts, calls FUN_CODE_21e4 + FUN_CODE_2224 | -| 0x1DD0 | `FUN_CODE_1dd0` | 109 | **Demod scan** -- tries 3 I2C addresses via FUN_CODE_1670 | -| 0x1E3D | `FUN_CODE_1e3d` | 54 | **DiSEqC byte transmit wrapper** -- sets P0.2, calls FUN_CODE_136c, adds delay | - -### 1.11 I2C Bus Wait / Polling Functions (0x1E73-0x1F5C) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x1E73 | `FUN_CODE_1e73` | 54 | **I2C bus wait (3-check)** -- polls FUN_CODE_222d, FUN_CODE_2215, FUN_CODE_224f | -| 0x1EA9 | `FUN_CODE_1ea9` | 49 | **I2C bus wait (2-check)** -- polls FUN_CODE_2215, FUN_CODE_222d | -| 0x1EDA | `FUN_CODE_1eda` | 44 | **USB endpoint reset pulse** -- CPUCS bit 0 toggle with delay | -| 0x1F06 | `FUN_CODE_1f06` | 43 | **I2C completion wait** -- polls XRAM 0xE678 bit 0 with 16-bit timeout | -| 0x1F31 | `FUN_CODE_1f31` | 43 | **Descriptor version checker** -- walks descriptor chain checking byte+1==0x03 | -| 0x1F5C | `FUN_CODE_1f5c` | 41 | **LNB voltage I2C select** -- probes I2C device 0x60 or custom addr from 0xE0B6 | - -### 1.12 GPIO Control Primitives (0x1F85-0x2038) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x1F85 | `FUN_CODE_1f85` | 37 | **I2C completion wait (2-flag)** -- polls 0xE678 bits 0 and 2 | -| 0x1FBE | `FUN_CODE_1fbe` | 17 | **Microsecond delay loop** -- DPL=0, counts 0xFDA5 iterations (~600 cycles) | -| 0x1FCF | `FUN_CODE_1fcf` | 46 | **GPIO pin controller** -- sets P0.6, P0.0, P3.4 based on param bits 1-3 | -| 0x1FFD | `FUN_CODE_1ffd` | 3 | Empty stub (RET) | -| 0x2000 | `FUN_CODE_2000` | 30 | **I2C busy wait** -- polls XRAM 0xE678 bit 6 with 16-bit timeout | -| 0x201E | `FUN_CODE_201e` | 26 | **Main loop poll** -- calls FUN_CODE_1938 + FUN_CODE_1996 | -| 0x2038 | `FUN_CODE_2038` | 51 | **GPIO clock strobe** -- calls FUN_CODE_1fcf three times (setup, clock, cleanup) | - -### 1.13 I2C Register Helpers (0x206B-0x20F9) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x206B | `FUN_CODE_206b` | 25 | **I2C single-byte read** -- reads from register via FUN_CODE_0f00 | -| 0x2084 | `GPIF_INT4_INT6_handler` | 48 | Duplicate INT4/INT6 handler (sets `_0_1`, clears EXIF.4) | -| 0x20B4 | `FUN_CODE_20b4` | 23 | **I2C register write** -- writes to addr 0xE114, register 0xA0 | -| 0x20CB | `FUN_CODE_20cb` | 23 | **I2C register read** -- reads from addr 0xE114 via FUN_CODE_0f00 | -| 0x20E2 | `FUN_CODE_20e2` | 23 | **22kHz tone burst** -- P0.3 ON, 25 ticks via FUN_CODE_225f, P0.3 OFF | -| 0x20F9 | `FUN_CODE_20f9` | 67 | **GPIO mode switch** -- configures P0, P3, IPL1 based on `_0_6` flag | - -### 1.14 USB Descriptor and EP Management (0x213C-0x219F) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x213C | `FUN_CODE_213c` | 22 | **DiSEqC bit symbol** -- carrier on/off via P0.3, data via `_0_4` (P0.4) | -| 0x2152 | `FUN_CODE_2152` | 21 | **EP0BUF write** -- stores BANK3 register to EP0BUF (0xE740) | -| 0x2167 | `FUN_CODE_2167` | 19 | **EP0 flush** -- waits for EP0CS busy bit clear | -| 0x217A | `FUN_CODE_217a` | 19 | **I2C start + address** -- calls FUN_CODE_2000, then FUN_CODE_2206 | -| 0x218D | `FUN_CODE_218d` | 18 | **EP0BUF write variant** -- stores BANK3_R1 to EP0BUF | -| 0x219F | `FUN_CODE_219f` | 18 | **GPIFADR set** -- computes (param_1 << 4 | param_2 | 0x20) -> 0xE683 | - -### 1.15 LNB / Demod Control (0x21B1-0x2206) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x21B1 | `FUN_CODE_21b1` | 17 | **LNB voltage select (13/18V)** -- sets/clears P0.4, updates DAT_INTMEM_4e bit 5 | -| 0x21C2 | `FUN_CODE_21c2` | 17 | **22kHz tone enable** -- sets/clears P0.3, updates DAT_INTMEM_4e bit 4 | -| 0x21D3 | `FUN_CODE_21d3` | 17 | **DiSEqC port direction** -- sets/clears P3.6, updates DAT_INTMEM_4e bit 3 | -| 0x21E4 | `FUN_CODE_21e4` | 34 | **I2C write + start** -- calls FUN_CODE_2000 then FUN_CODE_2206(addr*2) | -| 0x2206 | `FUN_CODE_2206` | 15 | **I2C start condition** -- sets 0xE678=0x80, 0xE679=param, calls FUN_CODE_1f06 | - -### 1.16 I2C Register Read Wrappers (0x2215-0x2257) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x2215 | `FUN_CODE_2215` | 15 | **Read I2C reg 0xA8** -- calls FUN_CODE_20cb(0xA8), checks bit 0 | -| 0x2224 | `FUN_CODE_2224` | 9 | **I2C write (data only)** -- sets 0xE679=param, calls FUN_CODE_1f06 | -| 0x222D | `FUN_CODE_222d` | 10 | **Read I2C reg 0xA2** -- calls FUN_CODE_20cb(0xA2) | -| 0x223F | `USB_GPIF_handler` | 8 | INT2 handler duplicate -- clears CCON.4 | -| 0x2247 | `FUN_CODE_2247` | 8 | **Read I2C reg 0xA2 (shifted)** -- reads 0xA2, returns shifted result | -| 0x224F | `FUN_CODE_224f` | 8 | **Read I2C reg 0xA4** -- reads 0xA4, returns shifted result | -| 0x2257 | `FUN_CODE_2257` | 8 | **DiSEqC status read** -- calls FUN_CODE_206b(0x7F, 0xF9) | - -### 1.17 Timer and Tail Functions (0x225F-0x2269) - -| Address | Name | Size | Role | -|---------|------|------|------| -| 0x225F | `FUN_CODE_225f` | 6 | **Timer2 tick wait** -- polls TF2, clears on overflow (500us tick) | -| 0x2265 | `FUN_CODE_2265` | 2 | Empty stub (RET) | -| 0x2267 | `FUN_CODE_2267` | 2 | Empty stub (RET) | -| 0x2269 | `FUN_CODE_2269` | 2 | Empty stub (RET) | - ---- - -## 2. Cross-Version Function Comparison - -### 2.1 v2.06 Functions (61 total, port 8193) - -| Address | Name | -|---------|------| -| 0x0003 | INT0_vec | -| 0x0036 | FUN_CODE_0036 | -| 0x0056 | FUN_CODE_0056 | -| 0x032A | FUN_CODE_032a | -| 0x06C7 | FUN_CODE_06c7 | -| 0x071C | FUN_CODE_071c | -| 0x076B | FUN_CODE_076b | -| 0x078F | FUN_CODE_078f | -| 0x07FE | FUN_CODE_07fe | -| 0x09A7 | FUN_CODE_09a7 | -| 0x0DDD | FUN_CODE_0ddd | -| 0x10F2 | FUN_CODE_10f2 | -| 0x12EA | FUN_CODE_12ea | -| 0x13C3 | FUN_CODE_13c3 | -| 0x148E | FUN_CODE_148e | -| 0x1556 | FUN_CODE_1556 | -| 0x15E9 | FUN_CODE_15e9 | -| 0x16B8 | FUN_CODE_16b8 | -| 0x17FE | FUN_CODE_17fe | -| 0x188D | main | -| 0x1919 | FUN_CODE_1919 | -| 0x1A0E | FUN_CODE_1a0e | -| 0x1A81 | FUN_CODE_1a81 | -| 0x1AF2 | FUN_CODE_1af2 | -| 0x1BCE | FUN_CODE_1bce | -| 0x1C37 | FUN_CODE_1c37 | -| 0x1C95 | FUN_CODE_1c95 | -| 0x1CF3 | FUN_CODE_1cf3 | -| 0x1D4F | FUN_CODE_1d4f | -| 0x1DA8 | FUN_CODE_1da8 | -| 0x1DFB | FUN_CODE_1dfb | -| 0x2000 | FUN_CODE_2000 | -| 0x20C5 | FUN_CODE_20c5 | -| 0x211D | FUN_CODE_211d | -| 0x2149 | FUN_CODE_2149 | -| 0x2174 | FUN_CODE_2174 | -| 0x219F | FUN_CODE_219f | -| 0x21C8 | FUN_CODE_21c8 | -| 0x21ED | FUN_CODE_21ed | -| 0x2201 | FUN_CODE_2201 | -| 0x2212 | FUN_CODE_2212 | -| 0x2258 | FUN_CODE_2258 | -| 0x2279 | FUN_CODE_2279 | -| 0x2297 | FUN_CODE_2297 | -| 0x2314 | FUN_CODE_2314 | -| 0x2344 | FUN_CODE_2344 | -| 0x235B | FUN_CODE_235b | -| 0x23CB | FUN_CODE_23cb | -| 0x23F3 | FUN_CODE_23f3 | -| 0x2419 | FUN_CODE_2419 | -| 0x242B | FUN_CODE_242b | -| 0x243D | FUN_CODE_243d | -| 0x244E | FUN_CODE_244e | -| 0x2470 | FUN_CODE_2470 | -| 0x2492 | FUN_CODE_2492 | -| 0x24AD | FUN_CODE_24ad | -| 0x24D2 | FUN_CODE_24d2 | -| 0x24D6 | FUN_CODE_24d6 | -| 0x24D8 | FUN_CODE_24d8 | -| 0x24DA | FUN_CODE_24da | -| 0x24DC | FUN_CODE_24dc | - -### 2.2 v2.13 Functions (88 total, port 8194) - -| Address | Name | -|---------|------| -| 0x0000 | RESET_vector | -| 0x0003 | INT0_vector | -| 0x0033 | INT2_USB_GPIF_vector | -| 0x0036 | FUN_CODE_0036 | -| 0x0043 | INT4_FX2_vector | -| 0x004B | INT5_FX2_vector | -| 0x0050 | FUN_CODE_0050 | -| 0x0053 | INT6_FX2_vector | -| 0x0056 | FUN_CODE_0056 | -| 0x0211 | FUN_CODE_0211 | -| 0x034E | FUN_CODE_034e | -| 0x06D9 | FUN_CODE_06d9 | -| 0x0718 | FUN_CODE_0718 | -| 0x072A | FUN_CODE_072a | -| 0x0779 | FUN_CODE_0779 | -| 0x079D | FUN_CODE_079d | -| 0x0800 | FUN_CODE_0800 | -| 0x0CA4 | FUN_CODE_0ca4 | -| 0x0DBC | FUN_CODE_0dbc | -| 0x0EEA | FUN_CODE_0eea | -| 0x0FC7 | FUN_CODE_0fc7 | -| 0x0FFE | FUN_CODE_0ffe | -| 0x1000 | FUN_CODE_1000 | -| 0x10D9 | FUN_CODE_10d9 | -| 0x11AB | FUN_CODE_11ab | -| 0x1405 | FUN_CODE_1405 | -| 0x14B9 | FUN_CODE_14b9 | -| 0x1500 | thunk_FUN_CODE_2252 | -| 0x15B8 | FUN_CODE_15b8 | -| 0x170D | main_entry | -| 0x1799 | FUN_CODE_1799 | -| 0x1800 | FUN_CODE_1800 | -| 0x19ED | FUN_CODE_19ed | -| 0x1A5D | FUN_CODE_1a5d | -| 0x1AC6 | FUN_CODE_1ac6 | -| 0x1B2A | FUN_CODE_1b2a | -| 0x1B88 | FUN_CODE_1b88 | -| 0x1BE6 | FUN_CODE_1be6 | -| 0x1C44 | FUN_CODE_1c44 | -| 0x1CA0 | FUN_CODE_1ca0 | -| 0x1CF6 | FUN_CODE_1cf6 | -| 0x1D4B | FUN_CODE_1d4b | -| 0x1DED | FUN_CODE_1ded | -| 0x1E3C | FUN_CODE_1e3c | -| 0x1E88 | FUN_CODE_1e88 | -| 0x1F76 | FUN_CODE_1f76 | -| 0x1FE2 | FUN_CODE_1fe2 | -| 0x2031 | FUN_CODE_2031 | -| 0x2060 | FUN_CODE_2060 | -| 0x208D | FUN_CODE_208d | -| 0x20B9 | FUN_CODE_20b9 | -| 0x20E5 | FUN_CODE_20e5 | -| 0x2110 | FUN_CODE_2110 | -| 0x213B | FUN_CODE_213b | -| 0x2164 | FUN_CODE_2164 | -| 0x2189 | FUN_CODE_2189 | -| 0x219D | FUN_CODE_219d | -| 0x21D1 | FUN_CODE_21d1 | -| 0x21EC | FUN_CODE_21ec | -| 0x2206 | FUN_CODE_2206 | -| 0x2239 | FUN_CODE_2239 | -| 0x2252 | FUN_CODE_2252 | -| 0x2282 | FUN_CODE_2282 | -| 0x2299 | FUN_CODE_2299 | -| 0x22B0 | FUN_CODE_22b0 | -| 0x22F3 | FUN_CODE_22f3 | -| 0x2309 | FUN_CODE_2309 | -| 0x2331 | FUN_CODE_2331 | -| 0x2344 | FUN_CODE_2344 | -| 0x2357 | FUN_CODE_2357 | -| 0x2369 | FUN_CODE_2369 | -| 0x237B | FUN_CODE_237b | -| 0x238C | FUN_CODE_238c | -| 0x23AE | FUN_CODE_23ae | -| 0x23D0 | FUN_CODE_23d0 | -| 0x23EE | FUN_CODE_23ee | -| 0x23F7 | FUN_CODE_23f7 | -| 0x2409 | FUN_CODE_2409 | -| 0x2411 | FUN_CODE_2411 | -| 0x2419 | FUN_CODE_2419 | -| 0x2421 | FUN_CODE_2421 | -| 0x2429 | FUN_CODE_2429 | -| 0x243D | FUN_CODE_243d | -| 0x2441 | FUN_CODE_2441 | -| 0x2443 | FUN_CODE_2443 | -| 0x2445 | FUN_CODE_2445 | -| 0x2447 | FUN_CODE_2447 | -| 0x2449 | FUN_CODE_2449 | - ---- - -## 3. Functions Unique to Rev.2 - -By analyzing functional equivalence (matching by decompiled behavior, not address), the following **~40 functions exist in Rev.2 but have no direct counterpart in v2.13**: - -### 3.1 INT0 ISR Split (2 functions) - -Rev.2 splits its INT0 into two named functions that Ghidra recognizes separately: - -| Rev.2 | Purpose | v2.13 Equivalent | -|-------|---------|------------------| -| `INT0_ISR` (0x0003) | Bit check + branch to 0x000F or 0x0016 | v2.13 has `INT0_vector` -- completely different (demod polling) | -| `INT0_ISR_bit_clear` (0x000F) | The CPUCS-only path of INT0 | No equivalent -- v2.13 moved this to `FUN_CODE_2031` | - -### 3.2 Vector Stubs and Duplicate Handlers (5 functions) - -| Rev.2 | Purpose | Why Unique | -|-------|---------|------------| -| `FUN_CODE_004e` (0x004E) | Empty RET at unused vector | v2.06 doesn't have this vector defined | -| `FUN_CODE_0051` (0x0051) | Empty RET at unused vector | Same as above | -| `GPIF_INT4_INT6_handler` (0x2084) | Duplicate INT4/INT6 handler | v2.13 doesn't duplicate this handler | -| `USB_GPIF_handler` (0x223F) | Duplicate INT2 handler | v2.13 doesn't duplicate | -| `FUN_CODE_1ffd` (0x1FFD) | Empty stub | Placeholder not present in other versions | - -### 3.3 I2C Granular Primitives (12 functions) - -Rev.2 decomposes I2C operations into many small functions that v2.06 inlines and v2.13 reorganizes: - -| Rev.2 | Size | Purpose | -|-------|------|---------| -| `i2c_exchange_byte` (0x0036) | 5 | Single byte I2C exchange | -| `I2C_ISR` (0x003B) | 8 | I2C interrupt handler wrapper | -| `FUN_CODE_1af5` (0x1AF5) | 9 | I2C address write helper | -| `FUN_CODE_1afe` (0x1AFE) | 3 | Register move micro-helper | -| `FUN_CODE_1b01` (0x1B01) | 67 | I2C byte-level transfer | -| `FUN_CODE_1b44` (0x1B44) | 76 | I2C ACK/NAK handling | -| `FUN_CODE_1b90` (0x1B90) | 74 | I2C bus reset / error recovery | -| `FUN_CODE_2206` (0x2206) | 15 | I2C start condition (0xE678=0x80) | -| `FUN_CODE_2224` (0x2224) | 9 | I2C write (data only, no start) | -| `FUN_CODE_217a` (0x217A) | 19 | I2C start + address combined | -| `FUN_CODE_21e4` (0x21E4) | 34 | I2C write with start condition | -| `FUN_CODE_206b` (0x206B) | 25 | I2C single-byte read wrapper | - -### 3.4 I2C Bus Wait/Polling Variants (4 functions) - -Rev.2 has multiple wait functions that poll different combinations of I2C status flags: - -| Rev.2 | Polls | Purpose | -|-------|-------|---------| -| `FUN_CODE_1e73` (0x1E73) | regs 0xA2, 0xA8, 0xA4 | 3-register bus wait with 0x0A00 timeout | -| `FUN_CODE_1ea9` (0x1EA9) | regs 0xA8, 0xA2 | 2-register bus wait variant | -| `FUN_CODE_1f06` (0x1F06) | 0xE678 bit 0,2,1 | I2C completion with 3-flag check | -| `FUN_CODE_1f85` (0x1F85) | 0xE678 bit 0,2 | I2C completion with 2-flag check | - -In v2.06 and v2.13, these are either inlined or consolidated into fewer functions. - -### 3.5 GPIO Control and DiSEqC Helpers (6 functions) - -| Rev.2 | Purpose | -|-------|---------| -| `FUN_CODE_1fcf` (0x1FCF) | GPIO pin controller -- P0.6, P0.0, P3.4 from param bits | -| `FUN_CODE_2038` (0x2038) | GPIO clock strobe (setup/clock/cleanup) | -| `FUN_CODE_21b1` (0x21B1) | LNB voltage select (P0.4 toggle) | -| `FUN_CODE_21c2` (0x21C2) | 22kHz tone enable (P0.3 toggle) | -| `FUN_CODE_21d3` (0x21D3) | DiSEqC port direction (P3.6 toggle) | -| `FUN_CODE_20f9` (0x20F9) | GPIO mode switch (configures P0, P3, IPL1) | - -### 3.6 Demod Scan and Version Check (3 functions) - -| Rev.2 | Purpose | -|-------|---------| -| `FUN_CODE_1dd0` (0x1DD0) | Demod scan -- tries 3 I2C addresses | -| `FUN_CODE_1f31` (0x1F31) | Descriptor version checker (walks chain for byte==0x03) | -| `FUN_CODE_1d5e` (0x1D5E) | DiSEqC message sender with retry | - -### 3.7 Math Library Functions (3 functions) - -| Rev.2 | Purpose | -|-------|---------| -| `FUN_CODE_06e3` (0x06E3) | 16-bit division with remainder | -| `FUN_CODE_0738` (0x0738) | 32-bit multiplication | -| `FUN_CODE_0787` (0x0787) | 32-bit subtraction with borrow | - -### 3.8 Miscellaneous Unique Functions (5 functions) - -| Rev.2 | Purpose | -|-------|---------| -| `FUN_CODE_1fbe` (0x1FBE) | Microsecond-level busy-wait loop (~600 CPU cycles) | -| `FUN_CODE_2257` (0x2257) | DiSEqC status read helper (I2C read 0x7F, 0xF9) | -| `FUN_CODE_2265` (0x2265) | Empty stub | -| `FUN_CODE_2267` (0x2267) | Empty stub | -| `FUN_CODE_2269` (0x2269) | Empty stub | - ---- - -## 4. INT0 ISR Deep Analysis - -### 4.1 Rev.2 INT0 (0x0003-0x0031): USB Re-enumeration - -Rev.2's INT0 is a **47-byte inline ISR** that spans from address 0x0003 to 0x0031, consuming the INT0 vector slot plus the Timer0, INT1, Timer1, and UART0 vector slots (0x000B, 0x0013, 0x001B, 0x0023). - -**Disassembly:** -``` -CODE:0003 JNB 0x04, 0x000F ; If bit 0x04 (byte0.4) == 0, skip -CODE:0006 MOV DPTR, #0xE680 ; CPUCS register -CODE:0009 MOVX A, @DPTR -CODE:000A ORL A, #0x0A ; Set bits 3+1 (re-enumerate + 48MHz) -CODE:000C MOVX @DPTR, A -CODE:000D SJMP 0x0016 ; Skip the alternate path -CODE:000F MOV DPTR, #0xE680 ; CPUCS register (alternate path) -CODE:0012 MOVX A, @DPTR -CODE:0013 ORL A, #0x08 ; Set bit 3 only (re-enumerate, keep clock) -CODE:0015 MOVX @DPTR, A -CODE:0016 MOV R7, #0xDC ; Delay parameter low = 220 -CODE:0018 MOV R6, #0x05 ; Delay parameter high = 5 -CODE:001A LCALL 0x1BDA ; delay_routine(5, 0xDC) -CODE:001D MOV DPTR, #0xE65D ; EPIRQ register -CODE:0020 MOV A, #0xFF -CODE:0022 MOVX @DPTR, A ; Clear all endpoint IRQs -CODE:0023 MOV DPTR, #0xE65F ; USBIRQ register -CODE:0026 MOVX @DPTR, A ; Clear all USB IRQs -CODE:0027 ANL 0x91, #0xEF ; Clear EXIF.4 (USB interrupt pending) -CODE:002A MOV DPTR, #0xE680 ; CPUCS register -CODE:002D MOVX A, @DPTR -CODE:002E ANL A, #0xF7 ; Clear bit 3 (end re-enumeration) -CODE:0030 MOVX @DPTR, A -CODE:0031 RET -``` - -**Decompiled C equivalent:** -```c -void INT0_ISR(void) { - if (_0_4 == 0) { - CPUCS |= 0x08; // Re-enumerate only - } else { - CPUCS |= 0x0A; // Re-enumerate + set CPUCS.1 - } - delay_routine(5, 0xDC); // ~1500 Timer2 ticks - EPIRQ = 0xFF; // Clear all endpoint interrupts - USBIRQ = 0xFF; // Clear all USB interrupts - EXIF &= 0xEF; // Clear external interrupt flag - CPUCS &= 0xF7; // Clear re-enumerate bit -} -``` - -### 4.2 Comparison: v2.06 INT0 (0x0003) - -v2.06's INT0 is **functionally identical** to Rev.2's. The only differences: -- Checks bit `_0_7` instead of `_0_4` (different bit allocation in byte 0) -- Calls `FUN_CODE_1dfb` for delay (Rev.2 calls `delay_routine` at 0x1BDA) -- Same 47-byte inline ISR spanning the same vector slots - -**Disassembly comparison:** -``` -v2.06: JNB 0x07, 0x000F ; bit 7 of byte 0 -Rev.2: JNB 0x04, 0x000F ; bit 4 of byte 0 -``` - -All other instructions are byte-identical except for the delay function address (v2.06: 0x1DFB, Rev.2: 0x1BDA). - -### 4.3 Comparison: v2.13 INT0 (0x0003) - -v2.13 **completely replaces** INT0's purpose. Instead of USB re-enumeration, it performs demodulator availability polling: - -```c -void INT0_vector(void) { - for (DAT_INTMEM_37 = 0x28; DAT_INTMEM_37 != 0; DAT_INTMEM_37--) { - result = FUN_CODE_2239(0x7F); // I2C read from demod at 0x7F - if (result != 0x01) { - result = FUN_CODE_2239(0x3F); // Try alternate address 0x3F - if (result != 0x01) break; - } - } - _1_4 = (DAT_INTMEM_37 == 0); // Flag if no demod found -} -``` - -v2.13 moved the USB re-enumeration logic to `FUN_CODE_2031`, called as a normal function before the main loop. This freed INT0 for periodic demodulator health checks. - -### 4.4 Key Insight - -Rev.2 represents the transitional state: it still uses INT0 for USB re-enumeration (like v2.06) but has already restructured the rest of the codebase in ways that v2.13 would inherit. The INT0 repurposing was the last major architectural change between Rev.2 and v2.13. - ---- - -## 5. Vendor Command Coverage - -### 5.1 Rev.2 Jump Table (27 commands: 0x80-0x9A) - -The vendor command dispatcher at 0x0056 performs: `ADD A, #0x80` followed by `CJNE A, #0x1B`. This means the valid range is 0x80-0x9A (27 commands, range check `< 0x1B`). - -Jump table at CODE:0076 (54 bytes = 27 entries x 2 bytes): - -| bRequest | Jump Target | Function | Purpose | -|----------|-------------|----------|---------| -| 0x80 | 0x01AC | FUN_CODE_01ac | GET_8PSK_CONFIG | -| 0x81 | 0x0315 | vendor_cmd_stall | STALL (not implemented) | -| 0x82 | 0x0315 | vendor_cmd_stall | STALL (not implemented) | -| 0x83 | 0x01DB | FUN_CODE_01db* | I2C_WRITE | -| 0x84 | 0x01EC | FUN_CODE_01ec* | I2C_READ | -| 0x85 | 0x01FA | FUN_CODE_01fa* | ARM_TRANSFER | -| 0x86 | 0x2118 | FUN_CODE_2118* | TUNE_8PSK | -| 0x87 | 0x214C | FUN_CODE_214c* | GET_SIGNAL_STRENGTH | -| 0x88 | 0x0315 | vendor_cmd_stall | STALL (BCM4500 load via other mechanism) | -| 0x89 | 0x01BE | FUN_CODE_01be* | BOOT_8PSK | -| 0x8A | 0x21A8 | FUN_CODE_21a8* | START_INTERSIL | -| 0x8B | 0x21D7 | FUN_CODE_21d7* | SET_LNB_VOLTAGE | -| 0x8C | 0x21E9 | FUN_CODE_21e9* | SET_22KHZ_TONE | -| 0x8D | 0x21FB | FUN_CODE_21fb* | SEND_DISEQC_COMMAND | -| 0x8E | 0x0315 | vendor_cmd_stall | STALL (SET_DVB_MODE) | -| 0x8F | 0x0108 | FUN_CODE_0108* | Unknown read-back | -| 0x90 | 0x0117 | FUN_CODE_0117* | GET_SIGNAL_LOCK | -| 0x91 | 0x0138 | FUN_CODE_0138* | I2C read-back | -| 0x92 | 0x0156 | FUN_CODE_0156* | I2C read-back | -| 0x93 | 0x017D | FUN_CODE_017d* | GET_SERIAL_NUMBER | -| 0x94 | 0x21C5 | FUN_CODE_21c5* | LNB-related | -| 0x95 | 0x21ED | FUN_CODE_21ed* | Read-back function | -| 0x96 | 0x21C2 | FUN_CODE_21c2 | 22kHz tone control | -| 0x97 | 0x21CF | FUN_CODE_21cf* | Similar handler | -| 0x98 | 0x21D9 | FUN_CODE_21d9* | Similar handler | -| 0x99 | 0x0101 | FUN_CODE_0101* | Partial implementation | -| 0x9A | 0x212A | FUN_CODE_212a* | Partial implementation | - -*Note: Addresses marked with * are computed from the jump table bytes and represent targets within or called by vendor_cmd_dispatch. Some may be inline code within the FUN_CODE_0319 mega-function.* - -### 5.2 Missing Commands vs v2.06/v2.13 - -v2.06 and v2.13 both support 30 commands (0x80-0x9D, range check `< 0x1E`). Rev.2 supports only 27 (0x80-0x9A, range check `< 0x1B`). - -**The 3 missing commands are:** - -| bRequest | v2.06 | v2.13 | Rev.2 | -|----------|-------|-------|-------| -| 0x9B | STALL | STALL | **Missing** (out of range) | -| 0x9C | STALL | DELAY_COMMAND (new) | **Missing** | -| 0x9D | SET_MODE_FLAG | SET_MODE_FLAG (enhanced) | **Missing** | - -**Why they're missing:** - -1. **0x9B (reserved/STALL)**: Simply a placeholder in v2.06/v2.13. Rev.2 didn't need it since 0x9A was the highest command. - -2. **0x9C (DELAY_COMMAND)**: This was added in v2.13 to allow host-controlled tuning acquisition delays. Rev.2's tuning logic handles delays internally without host control, so this command wasn't needed yet. - -3. **0x9D (SET_MODE_FLAG)**: This is the most significant absence. In v2.06, it handles hardware revision-aware mode switching. In v2.13, it triggers conditional demodulator resets. Rev.2 handles these functions through different code paths -- the `FUN_CODE_0800` configuration dispatcher has embedded logic for mode switching that later versions extracted into a separate vendor command. - -### 5.3 Commands Present but Differently Implemented - -**0x99 and 0x9A exist in Rev.2** but appear to point to different targets than v2.13: -- Rev.2 0x99 -> 0x0101 (within the vendor dispatch region, likely a minimal read-back) -- Rev.2 0x9A -> 0x212A (in the utility function region) -- v2.13 0x99 -> 0x0317 (GET_DEMOD_STATUS, reads I2C reg 0xF9) -- v2.13 0x9A -> 0x0140 (INIT_DEMOD, 3-attempt init sequence) - -Rev.2's implementations of 0x99 and 0x9A are proto-versions of what v2.13 later fleshed out. They exist but with different (likely simpler) behavior. - ---- - -## 6. Key Function Comparisons - -### 6.1 Main Entry - -| Aspect | Rev.2 (0x155F) | v2.06 (0x188D) | v2.13 (0x170D) | -|--------|----------------|----------------|----------------| -| SP value | **0x4F** | 0x72 | **0x50** | -| IRAM clear | 0x00-0x7F | 0x00-0x7F | 0x00-0x7F | -| Init table addr | CODE:0B48 | CODE:0B46 | CODE:0B88 | -| Architecture | 2-stage (main -> main_init -> FUN_CODE_09a9) | 1-stage (main processes init table inline, jumps to FUN_CODE_09a7) | 1-stage (main_entry processes init table inline, jumps to FUN_CODE_0800) | - -**Key difference**: Rev.2 separates the RESET entry (`main`) from the init table processor (`main_init`), making them two distinct functions. v2.06 and v2.13 do both inline in a single function. This is one source of Rev.2's higher function count. - -The init table processor is identical in algorithm across all three: it reads a compressed data stream from CODE space and writes initialization values to IRAM, XRAM, and SFR spaces. The table format uses a packed header byte where: -- Bits 7:6 select the target address space -- Bits 5:0 encode the data length -- Multi-page transfers use an additional page count byte - -### 6.2 Main Loop - -All three versions have structurally identical main loops: - -```c -// Common main loop structure (all versions) -while (true) { - // Inner poll: check USB endpoints - do { - poll_usb(); // Rev.2: FUN_CODE_201e - if (vendor_request_pending) { - handle_vendor_request(); // Rev.2: FUN_CODE_0319 - clear_flag(); - } - } while (!data_ready); - - // Process data - clear_data_flag(); - while (check_ep2_status()) { - if (no_more_data) break; - } - reset_endpoints(); - sync_gpif(); -} -``` - -**Differences in main loop functions called:** - -| Role | Rev.2 | v2.06 | v2.13 | -|------|-------|-------|-------| -| USB poll | FUN_CODE_201e | FUN_CODE_2297 | FUN_CODE_21ec | -| Vendor handler | FUN_CODE_0319 | FUN_CODE_032a | FUN_CODE_034e | -| Data ready flag | FUN_CODE_2265 | FUN_CODE_24da | FUN_CODE_2445 | -| EP2 check | FUN_CODE_1faa | FUN_CODE_21ed | FUN_CODE_2189 | -| EP reset | FUN_CODE_1eda | FUN_CODE_211d | FUN_CODE_20b9 | -| GPIF sync | FUN_CODE_2267 | FUN_CODE_24dc | FUN_CODE_2447 | - -### 6.3 USB Descriptor Setup (FUN_CODE_10d9) - -Rev.2's descriptor setup matches the common pattern: - -```c -void usb_descriptor_setup(void) { - USBCS &= 0xFD; // Clear disconnect - _0_0 = 0; _0_2 = 1; // Init flags - IFCONFIG = 0x10; // Internal clock, 48MHz - REVCTL = 0xCA; - GPIFIDLECTL = 0xFF; - PORTACFG = 0x07; - - // I2C init - FUN_CODE_19f4(0); // I2C controller init - - // GPIO configuration - P0 = 0x84; // P0.7=1, P0.2=1 - IPL1 = 0x9E; - P3 = 0xE1; - - // Timer2 for DiSEqC timing - T2CON = 0x04; // Auto-reload, running - RCAP2H = 0xF8; - RCAP2L = 0x2F; // 500us tick period - - // LNB and demod init - FUN_CODE_1f5c(); // LNB voltage I2C select - FUN_CODE_12b8(); // Configuration structure init - FUN_CODE_0d7c(); // GPIF/slave FIFO config - FUN_CODE_21b1(); // LNB voltage select (P0.4) - FUN_CODE_21c2(); // 22kHz tone enable (P0.3) -} -``` - -**Compared to v2.06**: Nearly identical, but Rev.2 uses P0=0x84 where v2.06 uses different pin assignments (reflecting different PCB layout). - -**Compared to v2.13**: v2.13 adds the `INT0_vector()` demod probe call during setup. Rev.2 does not probe the demodulator during init -- it assumes the hardware is present (like v2.06). - ---- - -## 7. Function Address Shift Patterns - -### 7.1 Code Organization Comparison - -The firmware binary is organized into logical regions. Comparing equivalent functions across versions reveals systematic address shifts: - -| Region | Rev.2 Range | v2.06 Range | v2.13 Range | Shift Pattern | -|--------|-------------|-------------|-------------|---------------| -| Vectors + dispatch | 0x0000-0x0318 | 0x0003-0x032A | 0x0000-0x034E | Rev.2 slightly shorter | -| Math library | 0x067E-0x07FE | 0x06C7-0x07FE | 0x06D9-0x079D | Rev.2 earliest, all similar size | -| Config dispatcher | 0x0800-0x09A8 | 0x09A7 (single func) | 0x0800-0x0CA4 | v2.06 has no 0x0800 dispatcher | -| BCM4500/demod | 0x0C64-0x0F00 | 0x0DDD | 0x0CA4-0x0FFE | Rev.2 earlier due to shorter dispatch | -| USB endpoints | 0x1000-0x1420 | 0x10F2-0x1556 | 0x1000-0x15B8 | Consistent ~0x100 shift | -| Main entry | 0x155F-0x15EB | 0x188D-0x1919 | 0x170D-0x1800 | Rev.2 earliest (smaller code) | -| I2C + DiSEqC | 0x1670-0x1E3D | 0x16B8-0x1DFB | 0x1800-0x1E88 | Rev.2 similar to v2.06 | -| Delay + helpers | 0x1BDA-0x2038 | 0x1DFB-0x2000 | 0x14B9-0x2060 | v2.13 moved delay earlier | -| Tail functions | 0x2038-0x2269 | 0x2000-0x24DC | 0x2031-0x2449 | v2.06/v2.13 extend further | - -### 7.2 Why Rev.2 Has Code at Lower Addresses - -Rev.2's code starts earlier in several regions because: - -1. **No init table processor in main()**: Rev.2 splits main() into a 6-instruction stub (10 bytes) + separate main_init(). v2.06/v2.13 inline the init table processor (92 instructions, ~130 bytes) into main(), pushing subsequent code later. - -2. **Shorter vendor dispatch**: Rev.2 handles 27 commands (54-byte jump table) vs 30 commands (60-byte table), saving 6 bytes plus the handling code for 3 missing commands. - -3. **More compact I2C layer**: Rev.2's granular I2C functions are individually small but collectively pack tighter in the binary than v2.06's larger monolithic I2C functions. - -### 7.3 Why Rev.2 Ends at 0x2269 (Smaller Total) - -Despite having 107 functions, Rev.2's code ends at 0x226B (total ~8.7 KB) while: -- v2.06 ends at 0x24DD (total ~9.4 KB) -- v2.13 ends at 0x244B (total ~9.3 KB) - -Rev.2 is the **smallest binary** despite having the most functions. This is because: -- Many functions are tiny (2-8 bytes) -- empty stubs, simple register moves -- The granular decomposition creates more function entry points but reuses code through calls rather than duplication -- Missing vendor commands 0x9B-0x9D save ~100-200 bytes of handler code - ---- - -## 8. Why Rev.2 Has 107 Functions - -The 107 function count is driven by several factors: - -### 8.1 Granular Function Decomposition (+25-30 vs v2.06) - -Rev.2 breaks operations into many small callable units. Where v2.06 has a single I2C transfer function with inlined bus control, Rev.2 has: -- `i2c_exchange_byte` (5 bytes) -- `I2C_ISR` (8 bytes) -- `FUN_CODE_1af5` (9 bytes) -- address write -- `FUN_CODE_1afe` (3 bytes) -- register move -- `FUN_CODE_1b01` (67 bytes) -- byte transfer -- `FUN_CODE_1b44` (76 bytes) -- ACK/NAK -- `FUN_CODE_1b90` (74 bytes) -- bus reset - -That's 7 functions for what v2.06 handles in 2-3 functions. - -### 8.2 Duplicate Interrupt Handlers (+3) - -Rev.2 has `INT4_FX2_vector`, `INT6_FX2_vector`, `GPIF_INT4_INT6_handler`, AND `INT4_INT6_handler` -- four functions for the same interrupt behavior. v2.06 and v2.13 consolidate these. - -### 8.3 Empty Stubs and Placeholders (+5-6) - -Rev.2 defines explicit empty functions at: -- `FUN_CODE_004e`, `FUN_CODE_0051` (vector slots) -- `FUN_CODE_07fe`, `FUN_CODE_17fd`, `FUN_CODE_1ffd` (call targets) -- `FUN_CODE_2265`, `FUN_CODE_2267`, `FUN_CODE_2269` (tail stubs) - -These are RET-only functions that serve as placeholders for features not yet implemented or intentionally disabled. v2.06 doesn't define explicit functions at these addresses. - -### 8.4 INT0 Split (+1) - -Ghidra recognizes two functions (`INT0_ISR` and `INT0_ISR_bit_clear`) for what is logically one handler, because the branch at 0x0003 creates two entry points. - -### 8.5 The FUN_CODE_0800 Monster (+10-15 internal entry points) - -The 874-byte `FUN_CODE_0800` configuration dispatcher is the most complex function in any firmware version. It contains a 128-entry switch statement that handles different demodulator types and signal modulations. Ghidra's analysis identifies multiple internal entry points within this function as separate functions, inflating the count. This function also contains an **embedded copy of the main loop** (the `while(true)` loop at the bottom), which Ghidra may count additional entry points for. - -### 8.6 Summary Count - -| Factor | Additional Functions | -|--------|---------------------| -| Granular I2C primitives | +12 | -| I2C wait/polling variants | +4 | -| GPIO control helpers | +6 | -| Duplicate interrupt handlers | +3 | -| Empty stubs/placeholders | +8 | -| INT0 split | +1 | -| Demod scan/version check | +3 | -| Math library (separate) | +3 | -| Config dispatcher internal entries | +5 | -| **Total unique additions** | **~45** | - -Starting from v2.06's 61 functions, adding ~45 unique functions brings us close to 107. The remaining difference comes from Rev.2 having explicit function definitions where v2.06 has inline code or code that Ghidra doesn't recognize as separate functions. - ---- - -## 9. Rev.2's Position in the Firmware Timeline - -Rev.2 v2.10.4 is an intermediate development version that bridges v2.06 and v2.13: - -| Feature | v2.06 | Rev.2 v2.10.4 | v2.13 FW1 | -|---------|-------|---------------|-----------| -| INT0 purpose | USB re-enum | USB re-enum | Demod polling | -| SP value | 0x72 | **0x4F** | 0x50 | -| Init table | 0x0B46 | **0x0B48** | 0x0B88 | -| Descriptor base | 0x1200 | **0x0E00** | 0x0E00 | -| Vendor commands | 30 (0x80-0x9D) | **27 (0x80-0x9A)** | 30 (0x80-0x9D) | -| DiSEqC data pin | P0.7 | **P0.4** | P0.0 | -| Demod probe at init | No | No | Yes | -| Retry loops | No | No | Yes (20-attempt) | -| HW revision check | No | Yes (FUN_CODE_1f31) | Yes (_1_3 flag) | -| Function count | 61 | **107** | 88 | -| Binary size | ~9.4 KB | **~8.7 KB** | ~9.3 KB | -| Config dispatcher | No 0x0800 | **874-byte FUN_CODE_0800** | Yes (different) | - -Rev.2 adopted the lower stack pointer (0x4F vs v2.06's 0x72), the descriptor base at 0x0E00, and the DiSEqC data pin at P0.4 -- changes that stuck through v2.13 (with minor adjustments). However, it retained v2.06's INT0 USB re-enumeration behavior and lacked v2.13's demodulator polling, retry loops, and 3 additional vendor commands. - -The high function count reflects an engineering style favoring small, reusable functions over inline code -- a style that was partially consolidated back in v2.13 when the firmware was cleaned up for release. +# Genpix SkyWalker-1 Rev.2 v2.10.4 Deep Firmware Analysis + +## Executive Summary + +Rev.2 v2.10.4 has **107 functions** -- the most of any firmware version (vs 61 for v2.06, 88 for v2.13 FW1). The higher function count is driven by three factors: + +1. **Granular function decomposition**: Rev.2 breaks large operations into many small helper functions (10-30 bytes each), where v2.06 inlines them and v2.13 recombines them differently. +2. **A massive 874-byte configuration dispatcher** (`FUN_CODE_0800`) that contains an embedded copy of the main loop, making Ghidra count additional entry points as separate functions. +3. **Extra I2C/demodulator helper chains**, GPIO control primitives, and hardware-polling wait loops that exist as individual callable units. + +Rev.2 sits architecturally between v2.06 and v2.13 -- it has v2.06's INT0 USB re-enumeration behavior but already uses the same descriptor base offset (0x0E00) and stack pointer (SP=0x4F) pattern that v2.13 would adopt. It supports **27 vendor commands** (0x80-0x9A) vs 30 in v2.06/v2.13. + +--- + +## 1. Complete Function Inventory (107 Functions) + +### 1.1 Vector Table and ISR Region (0x0000-0x0055) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x0000 | `RESET_vector` | 3 | Jump to main (0x155F) | +| 0x0003 | `INT0_ISR` | 12 | INT0 handler -- USB re-enumeration (bit check + branch) | +| 0x000F | `INT0_ISR_bit_clear` | 36 | INT0 continuation -- CPUCS pulse, IRQ clear, delay | +| 0x0033 | `INT2_USB_GPIF_vector` | 3 | INT2 vector -- clears CCON.4 (PCA timer) | +| 0x0036 | `i2c_exchange_byte` | 5 | I2C byte exchange primitive | +| 0x003B | `I2C_ISR` | 8 | I2C interrupt-style handler, calls FUN_CODE_19f4 | +| 0x0043 | `INT4_FX2_vector` | 8 | INT4 vector -- sets `_0_1`, clears EXIF.4 | +| 0x004B | `INT5_FX2_vector` | 3 | INT5 vector -- empty (RETI) | +| 0x004E | `FUN_CODE_004e` | 3 | Unused vector slot -- empty (RET) | +| 0x0051 | `FUN_CODE_0051` | 2 | Unused vector slot -- empty (RET) | +| 0x0053 | `INT6_FX2_vector` | 3 | INT6 vector -- sets `_0_1`, clears EXIF.4 | + +### 1.2 Vendor Command Dispatch (0x0056-0x0318) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x0056 | `vendor_cmd_dispatch` | 342 | Vendor request dispatcher (0x80-0x9A range check, jump table at 0x0076) | +| 0x01AC | `FUN_CODE_01ac` | 361 | Vendor cmd 0x80: GET_8PSK_CONFIG -- reads config, calls FUN_CODE_1f5c | +| 0x0315 | `vendor_cmd_stall` | 2 | Stall handler (empty RET) | +| 0x0317 | `FUN_CODE_0317` | 2 | EP0 completion (empty RET) | +| 0x0319 | `FUN_CODE_0319` | 869 | Standard USB request handler (massive switch for bRequest 0x00-0x0B) | + +### 1.3 Math and Memory Operations (0x067E-0x07FE) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x067E | `FUN_CODE_067e` | 38 | Multi-mode data access dispatcher (computed jump based on address mode) | +| 0x06A4 | `FUN_CODE_06a4` | 45 | Memory read with address mode selection (IRAM/XRAM/CODE) | +| 0x06D1 | `FUN_CODE_06d1` | 18 | Memory write with address mode selection (IRAM/XRAM/paged) | +| 0x06E3 | `FUN_CODE_06e3` | 85 | 16-bit division routine (param_1!=0: 16/8 long div, else 8/8 fast div) | +| 0x0738 | `FUN_CODE_0738` | 79 | 32-bit multiplication (4-byte x 4-byte partial product accumulation) | +| 0x0787 | `FUN_CODE_0787` | 36 | 32-bit subtraction with borrow chain | +| 0x07AB | `FUN_CODE_07ab` | 38 | Table-driven function dispatcher (triplet lookup: key, addr_hi, addr_lo) | +| 0x07D1 | `FUN_CODE_07d1` | 45 | **DiSEqC byte transmit** -- 8 data bits + odd parity via P0.4 | +| 0x07FE | `FUN_CODE_07fe` | 2 | Empty stub (RET) | + +### 1.4 Configuration Dispatcher (0x0800-0x09A8) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x0800 | `FUN_CODE_0800` | 425 | **Massive config/tuning dispatcher** -- 128-entry switch on demod type, embedded main loop copy | +| 0x09A9 | `FUN_CODE_09a9` | 699 | **Main init + main loop** -- initializes hardware, enters infinite poll loop | + +### 1.5 Demodulator/BCM4500 Management (0x0C64-0x0F00) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x0C64 | `FUN_CODE_0c64` | 280 | **BCM4500 firmware loader** -- I2C block transfer with address tracking | +| 0x0D7C | `FUN_CODE_0d7c` | 128 | **GPIF/slave FIFO configuration** -- enables/disables streaming mode | +| 0x0DFC | `FUN_CODE_0dfc` | 4 | Set BANK3_R1 (simple register store) | +| 0x0F00 | `FUN_CODE_0f00` | 256 | I2C multi-byte read with parameter setup | + +### 1.6 USB Endpoint and Descriptor Setup (0x1000-0x1420) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x1000 | `FUN_CODE_1000` | 217 | USB endpoint configuration (FIFO sizes, etc.) | +| 0x10D9 | `FUN_CODE_10d9` | 201 | **USB/peripheral descriptor setup** -- IFCONFIG, GPIF, Timer2, GPIO init | +| 0x11A2 | `FUN_CODE_11a2` | 94 | Serial number / EEPROM reader | +| 0x1200 | `INT4_INT6_handler` | 184 | INT4/INT6 unified handler -- sets `_0_1` flag, clears EXIF.4 | +| 0x12B8 | `FUN_CODE_12b8` | 180 | Configuration structure init (I2C device setup) | +| 0x136C | `FUN_CODE_136c` | 180 | I2C multi-byte write with address/register params | +| 0x1420 | `FUN_CODE_1420` | 319 | Configuration update function | + +### 1.7 Main Entry and Init (0x155F-0x1670) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x155F | `main` | 71 | **RESET entry** -- clears IRAM 0x00-0x7F, sets SP=0x4F, jumps to main_init | +| 0x15A6 | `main_init` | 69 | Init data table processor (table at CODE:0B48), jumps to FUN_CODE_09a9 | +| 0x15EB | `FUN_CODE_15eb` | 133 | Descriptor table walker | +| 0x1670 | `FUN_CODE_1670` | 241 | **I2C demod write with verify** -- write, wait, read-back check | + +### 1.8 I2C Transfer and Demod Functions (0x1761-0x1B90) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x1761 | `FUN_CODE_1761` | 112 | I2C multi-byte read with address auto-increment | +| 0x17D1 | `FUN_CODE_17d1` | 44 | I2C bus wait -- polls FUN_CODE_224f until ready, with timeout | +| 0x17FD | `FUN_CODE_17fd` | 3 | Empty stub (RET) | +| 0x1800 | `FUN_CODE_1800` | 106 | GPIF/FIFO management (identical logic to v2.06/v2.13) | +| 0x186A | `FUN_CODE_186a` | 105 | Tuning/acquisition sequence | +| 0x18D3 | `FUN_CODE_18d3` | 101 | Extended tuning with polling | +| 0x1938 | `FUN_CODE_1938` | 94 | Configuration parameter write (USB setup data -> I2C) | +| 0x1996 | `FUN_CODE_1996` | 94 | Configuration parameter read (I2C -> USB EP0BUF) | +| 0x19F4 | `FUN_CODE_19f4` | 92 | I2C bus controller -- manages SDA/SCL via XRAM 0xE678 | +| 0x1A50 | `FUN_CODE_1a50` | 83 | I2C address select + start condition | +| 0x1AA3 | `FUN_CODE_1aa3` | 82 | I2C stop condition + cleanup | +| 0x1AF5 | `FUN_CODE_1af5` | 9 | I2C address write helper | +| 0x1AFE | `FUN_CODE_1afe` | 3 | Tiny helper (register move) | +| 0x1B01 | `FUN_CODE_1b01` | 67 | I2C byte-level transfer | +| 0x1B44 | `FUN_CODE_1b44` | 76 | I2C ACK/NAK handling | +| 0x1B90 | `FUN_CODE_1b90` | 74 | I2C bus reset / error recovery | + +### 1.9 Delay and DiSEqC Support (0x1BDA-0x1C65) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x1BDA | `delay_routine` | 70 | **Clock-compensated delay** -- adjusts for 12/24/48 MHz CPU clock | +| 0x1C20 | `FUN_CODE_1c20` | 69 | Configuration update (I2C register write chain) | +| 0x1C65 | `FUN_CODE_1c65` | 249 | Configuration update (extended I2C register block) | + +### 1.10 DiSEqC GPIO Bit-Bang (0x1D5E-0x1E3D) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x1D5E | `FUN_CODE_1d5e` | 59 | **DiSEqC message sender** -- iterates bytes, calls FUN_CODE_1e3d per byte | +| 0x1D99 | `FUN_CODE_1d99` | 55 | **I2C device probe with retry** -- 20 attempts, calls FUN_CODE_21e4 + FUN_CODE_2224 | +| 0x1DD0 | `FUN_CODE_1dd0` | 109 | **Demod scan** -- tries 3 I2C addresses via FUN_CODE_1670 | +| 0x1E3D | `FUN_CODE_1e3d` | 54 | **DiSEqC byte transmit wrapper** -- sets P0.2, calls FUN_CODE_136c, adds delay | + +### 1.11 I2C Bus Wait / Polling Functions (0x1E73-0x1F5C) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x1E73 | `FUN_CODE_1e73` | 54 | **I2C bus wait (3-check)** -- polls FUN_CODE_222d, FUN_CODE_2215, FUN_CODE_224f | +| 0x1EA9 | `FUN_CODE_1ea9` | 49 | **I2C bus wait (2-check)** -- polls FUN_CODE_2215, FUN_CODE_222d | +| 0x1EDA | `FUN_CODE_1eda` | 44 | **USB endpoint reset pulse** -- CPUCS bit 0 toggle with delay | +| 0x1F06 | `FUN_CODE_1f06` | 43 | **I2C completion wait** -- polls XRAM 0xE678 bit 0 with 16-bit timeout | +| 0x1F31 | `FUN_CODE_1f31` | 43 | **Descriptor version checker** -- walks descriptor chain checking byte+1==0x03 | +| 0x1F5C | `FUN_CODE_1f5c` | 41 | **LNB voltage I2C select** -- probes I2C device 0x60 or custom addr from 0xE0B6 | + +### 1.12 GPIO Control Primitives (0x1F85-0x2038) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x1F85 | `FUN_CODE_1f85` | 37 | **I2C completion wait (2-flag)** -- polls 0xE678 bits 0 and 2 | +| 0x1FBE | `FUN_CODE_1fbe` | 17 | **Microsecond delay loop** -- DPL=0, counts 0xFDA5 iterations (~600 cycles) | +| 0x1FCF | `FUN_CODE_1fcf` | 46 | **GPIO pin controller** -- sets P0.6, P0.0, P3.4 based on param bits 1-3 | +| 0x1FFD | `FUN_CODE_1ffd` | 3 | Empty stub (RET) | +| 0x2000 | `FUN_CODE_2000` | 30 | **I2C busy wait** -- polls XRAM 0xE678 bit 6 with 16-bit timeout | +| 0x201E | `FUN_CODE_201e` | 26 | **Main loop poll** -- calls FUN_CODE_1938 + FUN_CODE_1996 | +| 0x2038 | `FUN_CODE_2038` | 51 | **GPIO clock strobe** -- calls FUN_CODE_1fcf three times (setup, clock, cleanup) | + +### 1.13 I2C Register Helpers (0x206B-0x20F9) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x206B | `FUN_CODE_206b` | 25 | **I2C single-byte read** -- reads from register via FUN_CODE_0f00 | +| 0x2084 | `GPIF_INT4_INT6_handler` | 48 | Duplicate INT4/INT6 handler (sets `_0_1`, clears EXIF.4) | +| 0x20B4 | `FUN_CODE_20b4` | 23 | **I2C register write** -- writes to addr 0xE114, register 0xA0 | +| 0x20CB | `FUN_CODE_20cb` | 23 | **I2C register read** -- reads from addr 0xE114 via FUN_CODE_0f00 | +| 0x20E2 | `FUN_CODE_20e2` | 23 | **22kHz tone burst** -- P0.3 ON, 25 ticks via FUN_CODE_225f, P0.3 OFF | +| 0x20F9 | `FUN_CODE_20f9` | 67 | **GPIO mode switch** -- configures P0, P3, IPL1 based on `_0_6` flag | + +### 1.14 USB Descriptor and EP Management (0x213C-0x219F) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x213C | `FUN_CODE_213c` | 22 | **DiSEqC bit symbol** -- carrier on/off via P0.3, data via `_0_4` (P0.4) | +| 0x2152 | `FUN_CODE_2152` | 21 | **EP0BUF write** -- stores BANK3 register to EP0BUF (0xE740) | +| 0x2167 | `FUN_CODE_2167` | 19 | **EP0 flush** -- waits for EP0CS busy bit clear | +| 0x217A | `FUN_CODE_217a` | 19 | **I2C start + address** -- calls FUN_CODE_2000, then FUN_CODE_2206 | +| 0x218D | `FUN_CODE_218d` | 18 | **EP0BUF write variant** -- stores BANK3_R1 to EP0BUF | +| 0x219F | `FUN_CODE_219f` | 18 | **GPIFADR set** -- computes (param_1 << 4 | param_2 | 0x20) -> 0xE683 | + +### 1.15 LNB / Demod Control (0x21B1-0x2206) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x21B1 | `FUN_CODE_21b1` | 17 | **LNB voltage select (13/18V)** -- sets/clears P0.4, updates DAT_INTMEM_4e bit 5 | +| 0x21C2 | `FUN_CODE_21c2` | 17 | **22kHz tone enable** -- sets/clears P0.3, updates DAT_INTMEM_4e bit 4 | +| 0x21D3 | `FUN_CODE_21d3` | 17 | **DiSEqC port direction** -- sets/clears P3.6, updates DAT_INTMEM_4e bit 3 | +| 0x21E4 | `FUN_CODE_21e4` | 34 | **I2C write + start** -- calls FUN_CODE_2000 then FUN_CODE_2206(addr*2) | +| 0x2206 | `FUN_CODE_2206` | 15 | **I2C start condition** -- sets 0xE678=0x80, 0xE679=param, calls FUN_CODE_1f06 | + +### 1.16 I2C Register Read Wrappers (0x2215-0x2257) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x2215 | `FUN_CODE_2215` | 15 | **Read I2C reg 0xA8** -- calls FUN_CODE_20cb(0xA8), checks bit 0 | +| 0x2224 | `FUN_CODE_2224` | 9 | **I2C write (data only)** -- sets 0xE679=param, calls FUN_CODE_1f06 | +| 0x222D | `FUN_CODE_222d` | 10 | **Read I2C reg 0xA2** -- calls FUN_CODE_20cb(0xA2) | +| 0x223F | `USB_GPIF_handler` | 8 | INT2 handler duplicate -- clears CCON.4 | +| 0x2247 | `FUN_CODE_2247` | 8 | **Read I2C reg 0xA2 (shifted)** -- reads 0xA2, returns shifted result | +| 0x224F | `FUN_CODE_224f` | 8 | **Read I2C reg 0xA4** -- reads 0xA4, returns shifted result | +| 0x2257 | `FUN_CODE_2257` | 8 | **DiSEqC status read** -- calls FUN_CODE_206b(0x7F, 0xF9) | + +### 1.17 Timer and Tail Functions (0x225F-0x2269) + +| Address | Name | Size | Role | +|---------|------|------|------| +| 0x225F | `FUN_CODE_225f` | 6 | **Timer2 tick wait** -- polls TF2, clears on overflow (500us tick) | +| 0x2265 | `FUN_CODE_2265` | 2 | Empty stub (RET) | +| 0x2267 | `FUN_CODE_2267` | 2 | Empty stub (RET) | +| 0x2269 | `FUN_CODE_2269` | 2 | Empty stub (RET) | + +--- + +## 2. Cross-Version Function Comparison + +### 2.1 v2.06 Functions (61 total, port 8193) + +| Address | Name | +|---------|------| +| 0x0003 | INT0_vec | +| 0x0036 | FUN_CODE_0036 | +| 0x0056 | FUN_CODE_0056 | +| 0x032A | FUN_CODE_032a | +| 0x06C7 | FUN_CODE_06c7 | +| 0x071C | FUN_CODE_071c | +| 0x076B | FUN_CODE_076b | +| 0x078F | FUN_CODE_078f | +| 0x07FE | FUN_CODE_07fe | +| 0x09A7 | FUN_CODE_09a7 | +| 0x0DDD | FUN_CODE_0ddd | +| 0x10F2 | FUN_CODE_10f2 | +| 0x12EA | FUN_CODE_12ea | +| 0x13C3 | FUN_CODE_13c3 | +| 0x148E | FUN_CODE_148e | +| 0x1556 | FUN_CODE_1556 | +| 0x15E9 | FUN_CODE_15e9 | +| 0x16B8 | FUN_CODE_16b8 | +| 0x17FE | FUN_CODE_17fe | +| 0x188D | main | +| 0x1919 | FUN_CODE_1919 | +| 0x1A0E | FUN_CODE_1a0e | +| 0x1A81 | FUN_CODE_1a81 | +| 0x1AF2 | FUN_CODE_1af2 | +| 0x1BCE | FUN_CODE_1bce | +| 0x1C37 | FUN_CODE_1c37 | +| 0x1C95 | FUN_CODE_1c95 | +| 0x1CF3 | FUN_CODE_1cf3 | +| 0x1D4F | FUN_CODE_1d4f | +| 0x1DA8 | FUN_CODE_1da8 | +| 0x1DFB | FUN_CODE_1dfb | +| 0x2000 | FUN_CODE_2000 | +| 0x20C5 | FUN_CODE_20c5 | +| 0x211D | FUN_CODE_211d | +| 0x2149 | FUN_CODE_2149 | +| 0x2174 | FUN_CODE_2174 | +| 0x219F | FUN_CODE_219f | +| 0x21C8 | FUN_CODE_21c8 | +| 0x21ED | FUN_CODE_21ed | +| 0x2201 | FUN_CODE_2201 | +| 0x2212 | FUN_CODE_2212 | +| 0x2258 | FUN_CODE_2258 | +| 0x2279 | FUN_CODE_2279 | +| 0x2297 | FUN_CODE_2297 | +| 0x2314 | FUN_CODE_2314 | +| 0x2344 | FUN_CODE_2344 | +| 0x235B | FUN_CODE_235b | +| 0x23CB | FUN_CODE_23cb | +| 0x23F3 | FUN_CODE_23f3 | +| 0x2419 | FUN_CODE_2419 | +| 0x242B | FUN_CODE_242b | +| 0x243D | FUN_CODE_243d | +| 0x244E | FUN_CODE_244e | +| 0x2470 | FUN_CODE_2470 | +| 0x2492 | FUN_CODE_2492 | +| 0x24AD | FUN_CODE_24ad | +| 0x24D2 | FUN_CODE_24d2 | +| 0x24D6 | FUN_CODE_24d6 | +| 0x24D8 | FUN_CODE_24d8 | +| 0x24DA | FUN_CODE_24da | +| 0x24DC | FUN_CODE_24dc | + +### 2.2 v2.13 Functions (88 total, port 8194) + +| Address | Name | +|---------|------| +| 0x0000 | RESET_vector | +| 0x0003 | INT0_vector | +| 0x0033 | INT2_USB_GPIF_vector | +| 0x0036 | FUN_CODE_0036 | +| 0x0043 | INT4_FX2_vector | +| 0x004B | INT5_FX2_vector | +| 0x0050 | FUN_CODE_0050 | +| 0x0053 | INT6_FX2_vector | +| 0x0056 | FUN_CODE_0056 | +| 0x0211 | FUN_CODE_0211 | +| 0x034E | FUN_CODE_034e | +| 0x06D9 | FUN_CODE_06d9 | +| 0x0718 | FUN_CODE_0718 | +| 0x072A | FUN_CODE_072a | +| 0x0779 | FUN_CODE_0779 | +| 0x079D | FUN_CODE_079d | +| 0x0800 | FUN_CODE_0800 | +| 0x0CA4 | FUN_CODE_0ca4 | +| 0x0DBC | FUN_CODE_0dbc | +| 0x0EEA | FUN_CODE_0eea | +| 0x0FC7 | FUN_CODE_0fc7 | +| 0x0FFE | FUN_CODE_0ffe | +| 0x1000 | FUN_CODE_1000 | +| 0x10D9 | FUN_CODE_10d9 | +| 0x11AB | FUN_CODE_11ab | +| 0x1405 | FUN_CODE_1405 | +| 0x14B9 | FUN_CODE_14b9 | +| 0x1500 | thunk_FUN_CODE_2252 | +| 0x15B8 | FUN_CODE_15b8 | +| 0x170D | main_entry | +| 0x1799 | FUN_CODE_1799 | +| 0x1800 | FUN_CODE_1800 | +| 0x19ED | FUN_CODE_19ed | +| 0x1A5D | FUN_CODE_1a5d | +| 0x1AC6 | FUN_CODE_1ac6 | +| 0x1B2A | FUN_CODE_1b2a | +| 0x1B88 | FUN_CODE_1b88 | +| 0x1BE6 | FUN_CODE_1be6 | +| 0x1C44 | FUN_CODE_1c44 | +| 0x1CA0 | FUN_CODE_1ca0 | +| 0x1CF6 | FUN_CODE_1cf6 | +| 0x1D4B | FUN_CODE_1d4b | +| 0x1DED | FUN_CODE_1ded | +| 0x1E3C | FUN_CODE_1e3c | +| 0x1E88 | FUN_CODE_1e88 | +| 0x1F76 | FUN_CODE_1f76 | +| 0x1FE2 | FUN_CODE_1fe2 | +| 0x2031 | FUN_CODE_2031 | +| 0x2060 | FUN_CODE_2060 | +| 0x208D | FUN_CODE_208d | +| 0x20B9 | FUN_CODE_20b9 | +| 0x20E5 | FUN_CODE_20e5 | +| 0x2110 | FUN_CODE_2110 | +| 0x213B | FUN_CODE_213b | +| 0x2164 | FUN_CODE_2164 | +| 0x2189 | FUN_CODE_2189 | +| 0x219D | FUN_CODE_219d | +| 0x21D1 | FUN_CODE_21d1 | +| 0x21EC | FUN_CODE_21ec | +| 0x2206 | FUN_CODE_2206 | +| 0x2239 | FUN_CODE_2239 | +| 0x2252 | FUN_CODE_2252 | +| 0x2282 | FUN_CODE_2282 | +| 0x2299 | FUN_CODE_2299 | +| 0x22B0 | FUN_CODE_22b0 | +| 0x22F3 | FUN_CODE_22f3 | +| 0x2309 | FUN_CODE_2309 | +| 0x2331 | FUN_CODE_2331 | +| 0x2344 | FUN_CODE_2344 | +| 0x2357 | FUN_CODE_2357 | +| 0x2369 | FUN_CODE_2369 | +| 0x237B | FUN_CODE_237b | +| 0x238C | FUN_CODE_238c | +| 0x23AE | FUN_CODE_23ae | +| 0x23D0 | FUN_CODE_23d0 | +| 0x23EE | FUN_CODE_23ee | +| 0x23F7 | FUN_CODE_23f7 | +| 0x2409 | FUN_CODE_2409 | +| 0x2411 | FUN_CODE_2411 | +| 0x2419 | FUN_CODE_2419 | +| 0x2421 | FUN_CODE_2421 | +| 0x2429 | FUN_CODE_2429 | +| 0x243D | FUN_CODE_243d | +| 0x2441 | FUN_CODE_2441 | +| 0x2443 | FUN_CODE_2443 | +| 0x2445 | FUN_CODE_2445 | +| 0x2447 | FUN_CODE_2447 | +| 0x2449 | FUN_CODE_2449 | + +--- + +## 3. Functions Unique to Rev.2 + +By analyzing functional equivalence (matching by decompiled behavior, not address), the following **~40 functions exist in Rev.2 but have no direct counterpart in v2.13**: + +### 3.1 INT0 ISR Split (2 functions) + +Rev.2 splits its INT0 into two named functions that Ghidra recognizes separately: + +| Rev.2 | Purpose | v2.13 Equivalent | +|-------|---------|------------------| +| `INT0_ISR` (0x0003) | Bit check + branch to 0x000F or 0x0016 | v2.13 has `INT0_vector` -- completely different (demod polling) | +| `INT0_ISR_bit_clear` (0x000F) | The CPUCS-only path of INT0 | No equivalent -- v2.13 moved this to `FUN_CODE_2031` | + +### 3.2 Vector Stubs and Duplicate Handlers (5 functions) + +| Rev.2 | Purpose | Why Unique | +|-------|---------|------------| +| `FUN_CODE_004e` (0x004E) | Empty RET at unused vector | v2.06 doesn't have this vector defined | +| `FUN_CODE_0051` (0x0051) | Empty RET at unused vector | Same as above | +| `GPIF_INT4_INT6_handler` (0x2084) | Duplicate INT4/INT6 handler | v2.13 doesn't duplicate this handler | +| `USB_GPIF_handler` (0x223F) | Duplicate INT2 handler | v2.13 doesn't duplicate | +| `FUN_CODE_1ffd` (0x1FFD) | Empty stub | Placeholder not present in other versions | + +### 3.3 I2C Granular Primitives (12 functions) + +Rev.2 decomposes I2C operations into many small functions that v2.06 inlines and v2.13 reorganizes: + +| Rev.2 | Size | Purpose | +|-------|------|---------| +| `i2c_exchange_byte` (0x0036) | 5 | Single byte I2C exchange | +| `I2C_ISR` (0x003B) | 8 | I2C interrupt handler wrapper | +| `FUN_CODE_1af5` (0x1AF5) | 9 | I2C address write helper | +| `FUN_CODE_1afe` (0x1AFE) | 3 | Register move micro-helper | +| `FUN_CODE_1b01` (0x1B01) | 67 | I2C byte-level transfer | +| `FUN_CODE_1b44` (0x1B44) | 76 | I2C ACK/NAK handling | +| `FUN_CODE_1b90` (0x1B90) | 74 | I2C bus reset / error recovery | +| `FUN_CODE_2206` (0x2206) | 15 | I2C start condition (0xE678=0x80) | +| `FUN_CODE_2224` (0x2224) | 9 | I2C write (data only, no start) | +| `FUN_CODE_217a` (0x217A) | 19 | I2C start + address combined | +| `FUN_CODE_21e4` (0x21E4) | 34 | I2C write with start condition | +| `FUN_CODE_206b` (0x206B) | 25 | I2C single-byte read wrapper | + +### 3.4 I2C Bus Wait/Polling Variants (4 functions) + +Rev.2 has multiple wait functions that poll different combinations of I2C status flags: + +| Rev.2 | Polls | Purpose | +|-------|-------|---------| +| `FUN_CODE_1e73` (0x1E73) | regs 0xA2, 0xA8, 0xA4 | 3-register bus wait with 0x0A00 timeout | +| `FUN_CODE_1ea9` (0x1EA9) | regs 0xA8, 0xA2 | 2-register bus wait variant | +| `FUN_CODE_1f06` (0x1F06) | 0xE678 bit 0,2,1 | I2C completion with 3-flag check | +| `FUN_CODE_1f85` (0x1F85) | 0xE678 bit 0,2 | I2C completion with 2-flag check | + +In v2.06 and v2.13, these are either inlined or consolidated into fewer functions. + +### 3.5 GPIO Control and DiSEqC Helpers (6 functions) + +| Rev.2 | Purpose | +|-------|---------| +| `FUN_CODE_1fcf` (0x1FCF) | GPIO pin controller -- P0.6, P0.0, P3.4 from param bits | +| `FUN_CODE_2038` (0x2038) | GPIO clock strobe (setup/clock/cleanup) | +| `FUN_CODE_21b1` (0x21B1) | LNB voltage select (P0.4 toggle) | +| `FUN_CODE_21c2` (0x21C2) | 22kHz tone enable (P0.3 toggle) | +| `FUN_CODE_21d3` (0x21D3) | DiSEqC port direction (P3.6 toggle) | +| `FUN_CODE_20f9` (0x20F9) | GPIO mode switch (configures P0, P3, IPL1) | + +### 3.6 Demod Scan and Version Check (3 functions) + +| Rev.2 | Purpose | +|-------|---------| +| `FUN_CODE_1dd0` (0x1DD0) | Demod scan -- tries 3 I2C addresses | +| `FUN_CODE_1f31` (0x1F31) | Descriptor version checker (walks chain for byte==0x03) | +| `FUN_CODE_1d5e` (0x1D5E) | DiSEqC message sender with retry | + +### 3.7 Math Library Functions (3 functions) + +| Rev.2 | Purpose | +|-------|---------| +| `FUN_CODE_06e3` (0x06E3) | 16-bit division with remainder | +| `FUN_CODE_0738` (0x0738) | 32-bit multiplication | +| `FUN_CODE_0787` (0x0787) | 32-bit subtraction with borrow | + +### 3.8 Miscellaneous Unique Functions (5 functions) + +| Rev.2 | Purpose | +|-------|---------| +| `FUN_CODE_1fbe` (0x1FBE) | Microsecond-level busy-wait loop (~600 CPU cycles) | +| `FUN_CODE_2257` (0x2257) | DiSEqC status read helper (I2C read 0x7F, 0xF9) | +| `FUN_CODE_2265` (0x2265) | Empty stub | +| `FUN_CODE_2267` (0x2267) | Empty stub | +| `FUN_CODE_2269` (0x2269) | Empty stub | + +--- + +## 4. INT0 ISR Deep Analysis + +### 4.1 Rev.2 INT0 (0x0003-0x0031): USB Re-enumeration + +Rev.2's INT0 is a **47-byte inline ISR** that spans from address 0x0003 to 0x0031, consuming the INT0 vector slot plus the Timer0, INT1, Timer1, and UART0 vector slots (0x000B, 0x0013, 0x001B, 0x0023). + +**Disassembly:** +``` +CODE:0003 JNB 0x04, 0x000F ; If bit 0x04 (byte0.4) == 0, skip +CODE:0006 MOV DPTR, #0xE680 ; CPUCS register +CODE:0009 MOVX A, @DPTR +CODE:000A ORL A, #0x0A ; Set bits 3+1 (re-enumerate + 48MHz) +CODE:000C MOVX @DPTR, A +CODE:000D SJMP 0x0016 ; Skip the alternate path +CODE:000F MOV DPTR, #0xE680 ; CPUCS register (alternate path) +CODE:0012 MOVX A, @DPTR +CODE:0013 ORL A, #0x08 ; Set bit 3 only (re-enumerate, keep clock) +CODE:0015 MOVX @DPTR, A +CODE:0016 MOV R7, #0xDC ; Delay parameter low = 220 +CODE:0018 MOV R6, #0x05 ; Delay parameter high = 5 +CODE:001A LCALL 0x1BDA ; delay_routine(5, 0xDC) +CODE:001D MOV DPTR, #0xE65D ; EPIRQ register +CODE:0020 MOV A, #0xFF +CODE:0022 MOVX @DPTR, A ; Clear all endpoint IRQs +CODE:0023 MOV DPTR, #0xE65F ; USBIRQ register +CODE:0026 MOVX @DPTR, A ; Clear all USB IRQs +CODE:0027 ANL 0x91, #0xEF ; Clear EXIF.4 (USB interrupt pending) +CODE:002A MOV DPTR, #0xE680 ; CPUCS register +CODE:002D MOVX A, @DPTR +CODE:002E ANL A, #0xF7 ; Clear bit 3 (end re-enumeration) +CODE:0030 MOVX @DPTR, A +CODE:0031 RET +``` + +**Decompiled C equivalent:** +```c +void INT0_ISR(void) { + if (_0_4 == 0) { + CPUCS |= 0x08; // Re-enumerate only + } else { + CPUCS |= 0x0A; // Re-enumerate + set CPUCS.1 + } + delay_routine(5, 0xDC); // ~1500 Timer2 ticks + EPIRQ = 0xFF; // Clear all endpoint interrupts + USBIRQ = 0xFF; // Clear all USB interrupts + EXIF &= 0xEF; // Clear external interrupt flag + CPUCS &= 0xF7; // Clear re-enumerate bit +} +``` + +### 4.2 Comparison: v2.06 INT0 (0x0003) + +v2.06's INT0 is **functionally identical** to Rev.2's. The only differences: +- Checks bit `_0_7` instead of `_0_4` (different bit allocation in byte 0) +- Calls `FUN_CODE_1dfb` for delay (Rev.2 calls `delay_routine` at 0x1BDA) +- Same 47-byte inline ISR spanning the same vector slots + +**Disassembly comparison:** +``` +v2.06: JNB 0x07, 0x000F ; bit 7 of byte 0 +Rev.2: JNB 0x04, 0x000F ; bit 4 of byte 0 +``` + +All other instructions are byte-identical except for the delay function address (v2.06: 0x1DFB, Rev.2: 0x1BDA). + +### 4.3 Comparison: v2.13 INT0 (0x0003) + +v2.13 **completely replaces** INT0's purpose. Instead of USB re-enumeration, it performs demodulator availability polling: + +```c +void INT0_vector(void) { + for (DAT_INTMEM_37 = 0x28; DAT_INTMEM_37 != 0; DAT_INTMEM_37--) { + result = FUN_CODE_2239(0x7F); // I2C read from demod at 0x7F + if (result != 0x01) { + result = FUN_CODE_2239(0x3F); // Try alternate address 0x3F + if (result != 0x01) break; + } + } + _1_4 = (DAT_INTMEM_37 == 0); // Flag if no demod found +} +``` + +v2.13 moved the USB re-enumeration logic to `FUN_CODE_2031`, called as a normal function before the main loop. This freed INT0 for periodic demodulator health checks. + +### 4.4 Key Insight + +Rev.2 represents the transitional state: it still uses INT0 for USB re-enumeration (like v2.06) but has already restructured the rest of the codebase in ways that v2.13 would inherit. The INT0 repurposing was the last major architectural change between Rev.2 and v2.13. + +--- + +## 5. Vendor Command Coverage + +### 5.1 Rev.2 Jump Table (27 commands: 0x80-0x9A) + +The vendor command dispatcher at 0x0056 performs: `ADD A, #0x80` followed by `CJNE A, #0x1B`. This means the valid range is 0x80-0x9A (27 commands, range check `< 0x1B`). + +Jump table at CODE:0076 (54 bytes = 27 entries x 2 bytes): + +| bRequest | Jump Target | Function | Purpose | +|----------|-------------|----------|---------| +| 0x80 | 0x01AC | FUN_CODE_01ac | GET_8PSK_CONFIG | +| 0x81 | 0x0315 | vendor_cmd_stall | STALL (not implemented) | +| 0x82 | 0x0315 | vendor_cmd_stall | STALL (not implemented) | +| 0x83 | 0x01DB | FUN_CODE_01db* | I2C_WRITE | +| 0x84 | 0x01EC | FUN_CODE_01ec* | I2C_READ | +| 0x85 | 0x01FA | FUN_CODE_01fa* | ARM_TRANSFER | +| 0x86 | 0x2118 | FUN_CODE_2118* | TUNE_8PSK | +| 0x87 | 0x214C | FUN_CODE_214c* | GET_SIGNAL_STRENGTH | +| 0x88 | 0x0315 | vendor_cmd_stall | STALL (BCM4500 load via other mechanism) | +| 0x89 | 0x01BE | FUN_CODE_01be* | BOOT_8PSK | +| 0x8A | 0x21A8 | FUN_CODE_21a8* | START_INTERSIL | +| 0x8B | 0x21D7 | FUN_CODE_21d7* | SET_LNB_VOLTAGE | +| 0x8C | 0x21E9 | FUN_CODE_21e9* | SET_22KHZ_TONE | +| 0x8D | 0x21FB | FUN_CODE_21fb* | SEND_DISEQC_COMMAND | +| 0x8E | 0x0315 | vendor_cmd_stall | STALL (SET_DVB_MODE) | +| 0x8F | 0x0108 | FUN_CODE_0108* | Unknown read-back | +| 0x90 | 0x0117 | FUN_CODE_0117* | GET_SIGNAL_LOCK | +| 0x91 | 0x0138 | FUN_CODE_0138* | I2C read-back | +| 0x92 | 0x0156 | FUN_CODE_0156* | I2C read-back | +| 0x93 | 0x017D | FUN_CODE_017d* | GET_SERIAL_NUMBER | +| 0x94 | 0x21C5 | FUN_CODE_21c5* | LNB-related | +| 0x95 | 0x21ED | FUN_CODE_21ed* | Read-back function | +| 0x96 | 0x21C2 | FUN_CODE_21c2 | 22kHz tone control | +| 0x97 | 0x21CF | FUN_CODE_21cf* | Similar handler | +| 0x98 | 0x21D9 | FUN_CODE_21d9* | Similar handler | +| 0x99 | 0x0101 | FUN_CODE_0101* | Partial implementation | +| 0x9A | 0x212A | FUN_CODE_212a* | Partial implementation | + +*Note: Addresses marked with * are computed from the jump table bytes and represent targets within or called by vendor_cmd_dispatch. Some may be inline code within the FUN_CODE_0319 mega-function.* + +### 5.2 Missing Commands vs v2.06/v2.13 + +v2.06 and v2.13 both support 30 commands (0x80-0x9D, range check `< 0x1E`). Rev.2 supports only 27 (0x80-0x9A, range check `< 0x1B`). + +**The 3 missing commands are:** + +| bRequest | v2.06 | v2.13 | Rev.2 | +|----------|-------|-------|-------| +| 0x9B | STALL | STALL | **Missing** (out of range) | +| 0x9C | STALL | DELAY_COMMAND (new) | **Missing** | +| 0x9D | SET_MODE_FLAG | SET_MODE_FLAG (enhanced) | **Missing** | + +**Why they're missing:** + +1. **0x9B (reserved/STALL)**: Simply a placeholder in v2.06/v2.13. Rev.2 didn't need it since 0x9A was the highest command. + +2. **0x9C (DELAY_COMMAND)**: This was added in v2.13 to allow host-controlled tuning acquisition delays. Rev.2's tuning logic handles delays internally without host control, so this command wasn't needed yet. + +3. **0x9D (SET_MODE_FLAG)**: This is the most significant absence. In v2.06, it handles hardware revision-aware mode switching. In v2.13, it triggers conditional demodulator resets. Rev.2 handles these functions through different code paths -- the `FUN_CODE_0800` configuration dispatcher has embedded logic for mode switching that later versions extracted into a separate vendor command. + +### 5.3 Commands Present but Differently Implemented + +**0x99 and 0x9A exist in Rev.2** but appear to point to different targets than v2.13: +- Rev.2 0x99 -> 0x0101 (within the vendor dispatch region, likely a minimal read-back) +- Rev.2 0x9A -> 0x212A (in the utility function region) +- v2.13 0x99 -> 0x0317 (GET_DEMOD_STATUS, reads I2C reg 0xF9) +- v2.13 0x9A -> 0x0140 (INIT_DEMOD, 3-attempt init sequence) + +Rev.2's implementations of 0x99 and 0x9A are proto-versions of what v2.13 later fleshed out. They exist but with different (likely simpler) behavior. + +--- + +## 6. Key Function Comparisons + +### 6.1 Main Entry + +| Aspect | Rev.2 (0x155F) | v2.06 (0x188D) | v2.13 (0x170D) | +|--------|----------------|----------------|----------------| +| SP value | **0x4F** | 0x72 | **0x50** | +| IRAM clear | 0x00-0x7F | 0x00-0x7F | 0x00-0x7F | +| Init table addr | CODE:0B48 | CODE:0B46 | CODE:0B88 | +| Architecture | 2-stage (main -> main_init -> FUN_CODE_09a9) | 1-stage (main processes init table inline, jumps to FUN_CODE_09a7) | 1-stage (main_entry processes init table inline, jumps to FUN_CODE_0800) | + +**Key difference**: Rev.2 separates the RESET entry (`main`) from the init table processor (`main_init`), making them two distinct functions. v2.06 and v2.13 do both inline in a single function. This is one source of Rev.2's higher function count. + +The init table processor is identical in algorithm across all three: it reads a compressed data stream from CODE space and writes initialization values to IRAM, XRAM, and SFR spaces. The table format uses a packed header byte where: +- Bits 7:6 select the target address space +- Bits 5:0 encode the data length +- Multi-page transfers use an additional page count byte + +### 6.2 Main Loop + +All three versions have structurally identical main loops: + +```c +// Common main loop structure (all versions) +while (true) { + // Inner poll: check USB endpoints + do { + poll_usb(); // Rev.2: FUN_CODE_201e + if (vendor_request_pending) { + handle_vendor_request(); // Rev.2: FUN_CODE_0319 + clear_flag(); + } + } while (!data_ready); + + // Process data + clear_data_flag(); + while (check_ep2_status()) { + if (no_more_data) break; + } + reset_endpoints(); + sync_gpif(); +} +``` + +**Differences in main loop functions called:** + +| Role | Rev.2 | v2.06 | v2.13 | +|------|-------|-------|-------| +| USB poll | FUN_CODE_201e | FUN_CODE_2297 | FUN_CODE_21ec | +| Vendor handler | FUN_CODE_0319 | FUN_CODE_032a | FUN_CODE_034e | +| Data ready flag | FUN_CODE_2265 | FUN_CODE_24da | FUN_CODE_2445 | +| EP2 check | FUN_CODE_1faa | FUN_CODE_21ed | FUN_CODE_2189 | +| EP reset | FUN_CODE_1eda | FUN_CODE_211d | FUN_CODE_20b9 | +| GPIF sync | FUN_CODE_2267 | FUN_CODE_24dc | FUN_CODE_2447 | + +### 6.3 USB Descriptor Setup (FUN_CODE_10d9) + +Rev.2's descriptor setup matches the common pattern: + +```c +void usb_descriptor_setup(void) { + USBCS &= 0xFD; // Clear disconnect + _0_0 = 0; _0_2 = 1; // Init flags + IFCONFIG = 0x10; // Internal clock, 48MHz + REVCTL = 0xCA; + GPIFIDLECTL = 0xFF; + PORTACFG = 0x07; + + // I2C init + FUN_CODE_19f4(0); // I2C controller init + + // GPIO configuration + P0 = 0x84; // P0.7=1, P0.2=1 + IPL1 = 0x9E; + P3 = 0xE1; + + // Timer2 for DiSEqC timing + T2CON = 0x04; // Auto-reload, running + RCAP2H = 0xF8; + RCAP2L = 0x2F; // 500us tick period + + // LNB and demod init + FUN_CODE_1f5c(); // LNB voltage I2C select + FUN_CODE_12b8(); // Configuration structure init + FUN_CODE_0d7c(); // GPIF/slave FIFO config + FUN_CODE_21b1(); // LNB voltage select (P0.4) + FUN_CODE_21c2(); // 22kHz tone enable (P0.3) +} +``` + +**Compared to v2.06**: Nearly identical, but Rev.2 uses P0=0x84 where v2.06 uses different pin assignments (reflecting different PCB layout). + +**Compared to v2.13**: v2.13 adds the `INT0_vector()` demod probe call during setup. Rev.2 does not probe the demodulator during init -- it assumes the hardware is present (like v2.06). + +--- + +## 7. Function Address Shift Patterns + +### 7.1 Code Organization Comparison + +The firmware binary is organized into logical regions. Comparing equivalent functions across versions reveals systematic address shifts: + +| Region | Rev.2 Range | v2.06 Range | v2.13 Range | Shift Pattern | +|--------|-------------|-------------|-------------|---------------| +| Vectors + dispatch | 0x0000-0x0318 | 0x0003-0x032A | 0x0000-0x034E | Rev.2 slightly shorter | +| Math library | 0x067E-0x07FE | 0x06C7-0x07FE | 0x06D9-0x079D | Rev.2 earliest, all similar size | +| Config dispatcher | 0x0800-0x09A8 | 0x09A7 (single func) | 0x0800-0x0CA4 | v2.06 has no 0x0800 dispatcher | +| BCM4500/demod | 0x0C64-0x0F00 | 0x0DDD | 0x0CA4-0x0FFE | Rev.2 earlier due to shorter dispatch | +| USB endpoints | 0x1000-0x1420 | 0x10F2-0x1556 | 0x1000-0x15B8 | Consistent ~0x100 shift | +| Main entry | 0x155F-0x15EB | 0x188D-0x1919 | 0x170D-0x1800 | Rev.2 earliest (smaller code) | +| I2C + DiSEqC | 0x1670-0x1E3D | 0x16B8-0x1DFB | 0x1800-0x1E88 | Rev.2 similar to v2.06 | +| Delay + helpers | 0x1BDA-0x2038 | 0x1DFB-0x2000 | 0x14B9-0x2060 | v2.13 moved delay earlier | +| Tail functions | 0x2038-0x2269 | 0x2000-0x24DC | 0x2031-0x2449 | v2.06/v2.13 extend further | + +### 7.2 Why Rev.2 Has Code at Lower Addresses + +Rev.2's code starts earlier in several regions because: + +1. **No init table processor in main()**: Rev.2 splits main() into a 6-instruction stub (10 bytes) + separate main_init(). v2.06/v2.13 inline the init table processor (92 instructions, ~130 bytes) into main(), pushing subsequent code later. + +2. **Shorter vendor dispatch**: Rev.2 handles 27 commands (54-byte jump table) vs 30 commands (60-byte table), saving 6 bytes plus the handling code for 3 missing commands. + +3. **More compact I2C layer**: Rev.2's granular I2C functions are individually small but collectively pack tighter in the binary than v2.06's larger monolithic I2C functions. + +### 7.3 Why Rev.2 Ends at 0x2269 (Smaller Total) + +Despite having 107 functions, Rev.2's code ends at 0x226B (total ~8.7 KB) while: +- v2.06 ends at 0x24DD (total ~9.4 KB) +- v2.13 ends at 0x244B (total ~9.3 KB) + +Rev.2 is the **smallest binary** despite having the most functions. This is because: +- Many functions are tiny (2-8 bytes) -- empty stubs, simple register moves +- The granular decomposition creates more function entry points but reuses code through calls rather than duplication +- Missing vendor commands 0x9B-0x9D save ~100-200 bytes of handler code + +--- + +## 8. Why Rev.2 Has 107 Functions + +The 107 function count is driven by several factors: + +### 8.1 Granular Function Decomposition (+25-30 vs v2.06) + +Rev.2 breaks operations into many small callable units. Where v2.06 has a single I2C transfer function with inlined bus control, Rev.2 has: +- `i2c_exchange_byte` (5 bytes) +- `I2C_ISR` (8 bytes) +- `FUN_CODE_1af5` (9 bytes) -- address write +- `FUN_CODE_1afe` (3 bytes) -- register move +- `FUN_CODE_1b01` (67 bytes) -- byte transfer +- `FUN_CODE_1b44` (76 bytes) -- ACK/NAK +- `FUN_CODE_1b90` (74 bytes) -- bus reset + +That's 7 functions for what v2.06 handles in 2-3 functions. + +### 8.2 Duplicate Interrupt Handlers (+3) + +Rev.2 has `INT4_FX2_vector`, `INT6_FX2_vector`, `GPIF_INT4_INT6_handler`, AND `INT4_INT6_handler` -- four functions for the same interrupt behavior. v2.06 and v2.13 consolidate these. + +### 8.3 Empty Stubs and Placeholders (+5-6) + +Rev.2 defines explicit empty functions at: +- `FUN_CODE_004e`, `FUN_CODE_0051` (vector slots) +- `FUN_CODE_07fe`, `FUN_CODE_17fd`, `FUN_CODE_1ffd` (call targets) +- `FUN_CODE_2265`, `FUN_CODE_2267`, `FUN_CODE_2269` (tail stubs) + +These are RET-only functions that serve as placeholders for features not yet implemented or intentionally disabled. v2.06 doesn't define explicit functions at these addresses. + +### 8.4 INT0 Split (+1) + +Ghidra recognizes two functions (`INT0_ISR` and `INT0_ISR_bit_clear`) for what is logically one handler, because the branch at 0x0003 creates two entry points. + +### 8.5 The FUN_CODE_0800 Monster (+10-15 internal entry points) + +The 874-byte `FUN_CODE_0800` configuration dispatcher is the most complex function in any firmware version. It contains a 128-entry switch statement that handles different demodulator types and signal modulations. Ghidra's analysis identifies multiple internal entry points within this function as separate functions, inflating the count. This function also contains an **embedded copy of the main loop** (the `while(true)` loop at the bottom), which Ghidra may count additional entry points for. + +### 8.6 Summary Count + +| Factor | Additional Functions | +|--------|---------------------| +| Granular I2C primitives | +12 | +| I2C wait/polling variants | +4 | +| GPIO control helpers | +6 | +| Duplicate interrupt handlers | +3 | +| Empty stubs/placeholders | +8 | +| INT0 split | +1 | +| Demod scan/version check | +3 | +| Math library (separate) | +3 | +| Config dispatcher internal entries | +5 | +| **Total unique additions** | **~45** | + +Starting from v2.06's 61 functions, adding ~45 unique functions brings us close to 107. The remaining difference comes from Rev.2 having explicit function definitions where v2.06 has inline code or code that Ghidra doesn't recognize as separate functions. + +--- + +## 9. Rev.2's Position in the Firmware Timeline + +Rev.2 v2.10.4 is an intermediate development version that bridges v2.06 and v2.13: + +| Feature | v2.06 | Rev.2 v2.10.4 | v2.13 FW1 | +|---------|-------|---------------|-----------| +| INT0 purpose | USB re-enum | USB re-enum | Demod polling | +| SP value | 0x72 | **0x4F** | 0x50 | +| Init table | 0x0B46 | **0x0B48** | 0x0B88 | +| Descriptor base | 0x1200 | **0x0E00** | 0x0E00 | +| Vendor commands | 30 (0x80-0x9D) | **27 (0x80-0x9A)** | 30 (0x80-0x9D) | +| DiSEqC data pin | P0.7 | **P0.4** | P0.0 | +| Demod probe at init | No | No | Yes | +| Retry loops | No | No | Yes (20-attempt) | +| HW revision check | No | Yes (FUN_CODE_1f31) | Yes (_1_3 flag) | +| Function count | 61 | **107** | 88 | +| Binary size | ~9.4 KB | **~8.7 KB** | ~9.3 KB | +| Config dispatcher | No 0x0800 | **874-byte FUN_CODE_0800** | Yes (different) | + +Rev.2 adopted the lower stack pointer (0x4F vs v2.06's 0x72), the descriptor base at 0x0E00, and the DiSEqC data pin at P0.4 -- changes that stuck through v2.13 (with minor adjustments). However, it retained v2.06's INT0 USB re-enumeration behavior and lacked v2.13's demodulator polling, retry loops, and 3 additional vendor commands. + +The high function count reflects an engineering style favoring small, reusable functions over inline code -- a style that was partially consolidated back in v2.13 when the firmware was cleaned up for release. diff --git a/site/.dockerignore b/site/.dockerignore index 35917c9..9c97bbd 100644 --- a/site/.dockerignore +++ b/site/.dockerignore @@ -1,3 +1,3 @@ -node_modules -dist -.env +node_modules +dist +.env diff --git a/site/Caddyfile b/site/Caddyfile index 49b66df..a7b876d 100644 --- a/site/Caddyfile +++ b/site/Caddyfile @@ -1,11 +1,11 @@ -:80 { - root * /srv - encode gzip - - handle /health { - respond "ok" 200 - } - - try_files {path} {path}/index.html {path}/ /index.html - file_server -} +:80 { + root * /srv + encode gzip + + handle /health { + respond "ok" 200 + } + + try_files {path} {path}/index.html {path}/ /index.html + file_server +} diff --git a/site/Dockerfile b/site/Dockerfile index d91b16e..8348762 100644 --- a/site/Dockerfile +++ b/site/Dockerfile @@ -1,24 +1,24 @@ -# ── base: shared dependency install ────────────────────────────── -FROM node:20-slim AS base -WORKDIR /app -COPY package.json package-lock.json ./ -RUN npm ci -COPY . . - -# ── dev: Astro dev server with HMR ───────────────────────────── -FROM base AS dev -ENV HOST=0.0.0.0 -EXPOSE 4321 -CMD ["npx", "astro", "dev", "--host", "0.0.0.0", "--port", "4321"] - -# ── build: static site generation ─────────────────────────────── -FROM base AS build -RUN npx astro build - -# ── prod: Caddy serving static files ─────────────────────────── -FROM caddy:2-alpine AS prod -COPY Caddyfile /etc/caddy/Caddyfile -COPY --from=build /app/dist /srv -EXPOSE 80 -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget -qO- http://127.0.0.1:80/health || exit 1 +# ── base: shared dependency install ────────────────────────────── +FROM node:20-slim AS base +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY . . + +# ── dev: Astro dev server with HMR ───────────────────────────── +FROM base AS dev +ENV HOST=0.0.0.0 +EXPOSE 4321 +CMD ["npx", "astro", "dev", "--host", "0.0.0.0", "--port", "4321"] + +# ── build: static site generation ─────────────────────────────── +FROM base AS build +RUN npx astro build + +# ── prod: Caddy serving static files ─────────────────────────── +FROM caddy:2-alpine AS prod +COPY Caddyfile /etc/caddy/Caddyfile +COPY --from=build /app/dist /srv +EXPOSE 80 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -qO- http://127.0.0.1:80/health || exit 1 diff --git a/site/Makefile b/site/Makefile index bb64530..5fdda4a 100644 --- a/site/Makefile +++ b/site/Makefile @@ -1,27 +1,27 @@ -.PHONY: up down logs rebuild dev prod clean - -up: - docker compose up -d --build - -down: - docker compose down - -logs: - docker compose logs -f - -rebuild: - docker compose down - docker compose up -d --build - -dev: - @sed -i 's/^APP_ENV=.*/APP_ENV=dev/' .env - @sed -i 's/^APP_PORT=.*/APP_PORT=4321/' .env - $(MAKE) rebuild - -prod: - @sed -i 's/^APP_ENV=.*/APP_ENV=prod/' .env - @sed -i 's/^APP_PORT=.*/APP_PORT=80/' .env - $(MAKE) rebuild - -clean: - docker compose down --rmi local -v +.PHONY: up down logs rebuild dev prod clean + +up: + docker compose up -d --build + +down: + docker compose down + +logs: + docker compose logs -f + +rebuild: + docker compose down + docker compose up -d --build + +dev: + @sed -i 's/^APP_ENV=.*/APP_ENV=dev/' .env + @sed -i 's/^APP_PORT=.*/APP_PORT=4321/' .env + $(MAKE) rebuild + +prod: + @sed -i 's/^APP_ENV=.*/APP_ENV=prod/' .env + @sed -i 's/^APP_PORT=.*/APP_PORT=80/' .env + $(MAKE) rebuild + +clean: + docker compose down --rmi local -v diff --git a/site/astro.config.mjs b/site/astro.config.mjs index b7ef0fd..ba39956 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -1,149 +1,149 @@ -import { defineConfig } from 'astro/config'; -import starlight from '@astrojs/starlight'; -import starlightImageZoom from 'starlight-image-zoom'; -import starlightLinksValidator from 'starlight-links-validator'; - -export default defineConfig({ - telemetry: false, - devToolbar: { - enabled: false, - }, - server: { - host: '0.0.0.0', - port: 4321, - }, - vite: { - server: { - allowedHosts: [process.env.PUBLIC_DOMAIN, 'localhost'].filter(Boolean), - ...(process.env.VITE_HMR_HOST && { - hmr: { - host: process.env.VITE_HMR_HOST, - protocol: 'wss', - clientPort: 443, - }, - }), - }, - }, - integrations: [ - starlight({ - title: 'SkyWalker-1 Docs', - description: - 'Reverse-engineered documentation for the Genpix SkyWalker-1 DVB-S USB 2.0 satellite receiver', - plugins: [starlightImageZoom(), starlightLinksValidator()], - social: [ - { - icon: 'external', - label: 'Git Repository', - href: 'https://git.supported.systems/warehack.ing/skywalker-1', - }, - ], - customCss: ['./src/styles/custom.css'], - sidebar: [ - { - label: 'Hardware', - items: [ - { label: 'Overview', slug: 'hardware/overview' }, - { label: 'GPIO Pin Map', slug: 'hardware/gpio-pinmap' }, - { label: 'RF Specifications', slug: 'hardware/rf-specifications' }, - { label: 'RF Coverage', slug: 'hardware/rf-coverage' }, - ], - }, - { - label: 'USB Interface', - items: [ - { label: 'Interface', slug: 'usb/interface' }, - { label: 'Vendor Commands', slug: 'usb/vendor-commands' }, - { label: 'Boot Sequence', slug: 'usb/boot-sequence' }, - { label: 'Config Status', slug: 'usb/config-status' }, - ], - }, - { - label: 'BCM4500', - items: [ - { label: 'Register Map', slug: 'bcm4500/register-map' }, - { label: 'Demodulator', slug: 'bcm4500/demodulator' }, - { label: 'Tuning Protocol', slug: 'bcm4500/tuning-protocol' }, - { label: 'GPIF Streaming', slug: 'bcm4500/gpif-streaming' }, - { label: 'Signal Monitoring', slug: 'bcm4500/signal-monitoring' }, - ], - }, - { - label: 'LNB & DiSEqC', - items: [ - { label: 'LNB Control', slug: 'lnb-diseqc/lnb-control' }, - { label: 'DiSEqC Protocol', slug: 'lnb-diseqc/diseqc-protocol' }, - { label: 'Legacy Switch', slug: 'lnb-diseqc/legacy-switch' }, - ], - }, - { - label: 'I\u00B2C Bus', - items: [ - { label: 'Bus Architecture', slug: 'i2c/bus-architecture' }, - { - label: 'STOP Corruption Bug', - slug: 'i2c/stop-corruption-bug', - }, - ], - }, - { - label: 'Firmware', - items: [ - { - label: 'Version Comparison', - slug: 'firmware/version-comparison', - }, - { label: 'Rev.2 Analysis', slug: 'firmware/rev2-analysis' }, - { label: 'Kernel FW01', slug: 'firmware/kernel-fw01' }, - { label: 'FW2.13 Variants', slug: 'firmware/fw213-variants' }, - { label: 'Custom v3.01', slug: 'firmware/custom-v301' }, - { label: 'Custom v3.02', slug: 'firmware/custom-v302' }, - { label: 'Custom v3.05', slug: 'firmware/custom-v305' }, - { label: 'Storage Formats', slug: 'firmware/storage-formats' }, - ], - }, - { - label: 'Driver', - items: [ - { label: 'Linux Kernel', slug: 'driver/linux-kernel' }, - { label: 'DVB-S2', slug: 'driver/dvb-s2' }, - ], - }, - { - label: 'Tools', - items: [ - { label: 'Firmware Loader', slug: 'tools/firmware-loader' }, - { label: 'Tuning', slug: 'tools/tuning' }, - { label: 'SkyWalker RF Tool', slug: 'tools/skywalker' }, - { label: 'SkyWalker TUI', slug: 'tools/tui' }, - { label: 'Motor Control', slug: 'tools/motor' }, - { label: 'Carrier Survey', slug: 'tools/survey' }, - { label: 'EEPROM Utilities', slug: 'tools/eeprom-utilities' }, - { label: 'Debugging', slug: 'tools/debugging' }, - { label: 'TS Analyzer', slug: 'tools/ts-analyzer' }, - { label: 'Spectrum Analysis', slug: 'tools/spectrum-analysis' }, - { label: 'Hydrogen 21 cm', slug: 'tools/h21cm' }, - { label: 'RF Test Bench', slug: 'tools/rf-testbench' }, - { label: 'Beacon Logger', slug: 'tools/beacon-logger' }, - { label: 'Arc Survey', slug: 'tools/arc-survey' }, - { label: 'MCP Server', slug: 'tools/mcp-server' }, - { label: 'Safety Testing', slug: 'tools/safety-testing' }, - ], - }, - { - label: 'Guides', - items: [ - { label: 'Applications & Use Cases', slug: 'guides/applications' }, - { label: 'QO-100 DATV Reception', slug: 'guides/qo100-datv' }, - { label: "Experimenter's Roadmap", slug: 'guides/experimenter-roadmap' }, - ], - }, - { - label: 'Reference', - items: [ - { label: 'Sources', slug: 'reference/master-reference' }, - ], - }, - ], - }), - ], -}); +import { defineConfig } from 'astro/config'; +import starlight from '@astrojs/starlight'; +import starlightImageZoom from 'starlight-image-zoom'; +import starlightLinksValidator from 'starlight-links-validator'; + +export default defineConfig({ + telemetry: false, + devToolbar: { + enabled: false, + }, + server: { + host: '0.0.0.0', + port: 4321, + }, + vite: { + server: { + allowedHosts: [process.env.PUBLIC_DOMAIN, 'localhost'].filter(Boolean), + ...(process.env.VITE_HMR_HOST && { + hmr: { + host: process.env.VITE_HMR_HOST, + protocol: 'wss', + clientPort: 443, + }, + }), + }, + }, + integrations: [ + starlight({ + title: 'SkyWalker-1 Docs', + description: + 'Reverse-engineered documentation for the Genpix SkyWalker-1 DVB-S USB 2.0 satellite receiver', + plugins: [starlightImageZoom(), starlightLinksValidator()], + social: [ + { + icon: 'external', + label: 'Git Repository', + href: 'https://git.supported.systems/warehack.ing/skywalker-1', + }, + ], + customCss: ['./src/styles/custom.css'], + sidebar: [ + { + label: 'Hardware', + items: [ + { label: 'Overview', slug: 'hardware/overview' }, + { label: 'GPIO Pin Map', slug: 'hardware/gpio-pinmap' }, + { label: 'RF Specifications', slug: 'hardware/rf-specifications' }, + { label: 'RF Coverage', slug: 'hardware/rf-coverage' }, + ], + }, + { + label: 'USB Interface', + items: [ + { label: 'Interface', slug: 'usb/interface' }, + { label: 'Vendor Commands', slug: 'usb/vendor-commands' }, + { label: 'Boot Sequence', slug: 'usb/boot-sequence' }, + { label: 'Config Status', slug: 'usb/config-status' }, + ], + }, + { + label: 'BCM4500', + items: [ + { label: 'Register Map', slug: 'bcm4500/register-map' }, + { label: 'Demodulator', slug: 'bcm4500/demodulator' }, + { label: 'Tuning Protocol', slug: 'bcm4500/tuning-protocol' }, + { label: 'GPIF Streaming', slug: 'bcm4500/gpif-streaming' }, + { label: 'Signal Monitoring', slug: 'bcm4500/signal-monitoring' }, + ], + }, + { + label: 'LNB & DiSEqC', + items: [ + { label: 'LNB Control', slug: 'lnb-diseqc/lnb-control' }, + { label: 'DiSEqC Protocol', slug: 'lnb-diseqc/diseqc-protocol' }, + { label: 'Legacy Switch', slug: 'lnb-diseqc/legacy-switch' }, + ], + }, + { + label: 'I\u00B2C Bus', + items: [ + { label: 'Bus Architecture', slug: 'i2c/bus-architecture' }, + { + label: 'STOP Corruption Bug', + slug: 'i2c/stop-corruption-bug', + }, + ], + }, + { + label: 'Firmware', + items: [ + { + label: 'Version Comparison', + slug: 'firmware/version-comparison', + }, + { label: 'Rev.2 Analysis', slug: 'firmware/rev2-analysis' }, + { label: 'Kernel FW01', slug: 'firmware/kernel-fw01' }, + { label: 'FW2.13 Variants', slug: 'firmware/fw213-variants' }, + { label: 'Custom v3.01', slug: 'firmware/custom-v301' }, + { label: 'Custom v3.02', slug: 'firmware/custom-v302' }, + { label: 'Custom v3.05', slug: 'firmware/custom-v305' }, + { label: 'Storage Formats', slug: 'firmware/storage-formats' }, + ], + }, + { + label: 'Driver', + items: [ + { label: 'Linux Kernel', slug: 'driver/linux-kernel' }, + { label: 'DVB-S2', slug: 'driver/dvb-s2' }, + ], + }, + { + label: 'Tools', + items: [ + { label: 'Firmware Loader', slug: 'tools/firmware-loader' }, + { label: 'Tuning', slug: 'tools/tuning' }, + { label: 'SkyWalker RF Tool', slug: 'tools/skywalker' }, + { label: 'SkyWalker TUI', slug: 'tools/tui' }, + { label: 'Motor Control', slug: 'tools/motor' }, + { label: 'Carrier Survey', slug: 'tools/survey' }, + { label: 'EEPROM Utilities', slug: 'tools/eeprom-utilities' }, + { label: 'Debugging', slug: 'tools/debugging' }, + { label: 'TS Analyzer', slug: 'tools/ts-analyzer' }, + { label: 'Spectrum Analysis', slug: 'tools/spectrum-analysis' }, + { label: 'Hydrogen 21 cm', slug: 'tools/h21cm' }, + { label: 'RF Test Bench', slug: 'tools/rf-testbench' }, + { label: 'Beacon Logger', slug: 'tools/beacon-logger' }, + { label: 'Arc Survey', slug: 'tools/arc-survey' }, + { label: 'MCP Server', slug: 'tools/mcp-server' }, + { label: 'Safety Testing', slug: 'tools/safety-testing' }, + ], + }, + { + label: 'Guides', + items: [ + { label: 'Applications & Use Cases', slug: 'guides/applications' }, + { label: 'QO-100 DATV Reception', slug: 'guides/qo100-datv' }, + { label: "Experimenter's Roadmap", slug: 'guides/experimenter-roadmap' }, + ], + }, + { + label: 'Reference', + items: [ + { label: 'Sources', slug: 'reference/master-reference' }, + ], + }, + ], + }), + ], +}); diff --git a/site/docker-compose.yml b/site/docker-compose.yml index fb7174a..6bcec3b 100644 --- a/site/docker-compose.yml +++ b/site/docker-compose.yml @@ -1,31 +1,31 @@ -services: - docs: - build: - context: . - target: ${APP_ENV:-dev} - container_name: skywalker-1-docs - restart: unless-stopped - environment: - - PUBLIC_DOMAIN=${PUBLIC_DOMAIN} - - VITE_HMR_HOST=${VITE_HMR_HOST} - networks: - - caddy - labels: - caddy: ${PUBLIC_DOMAIN} - caddy.reverse_proxy: "{{upstreams ${APP_PORT:-4321}}}" - caddy.reverse_proxy.flush_interval: "-1" - caddy.reverse_proxy.transport: http - caddy.reverse_proxy.transport.read_timeout: "0" - caddy.reverse_proxy.transport.write_timeout: "0" - caddy.reverse_proxy.transport.keepalive: 5m - caddy.reverse_proxy.transport.keepalive_idle_conns: "10" - caddy.reverse_proxy.stream_timeout: 24h - caddy.reverse_proxy.stream_close_delay: 5s - volumes: - - ./src:/app/src - - ./public:/app/public - - ./astro.config.mjs:/app/astro.config.mjs - -networks: - caddy: - external: true +services: + docs: + build: + context: . + target: ${APP_ENV:-dev} + container_name: skywalker-1-docs + restart: unless-stopped + environment: + - PUBLIC_DOMAIN=${PUBLIC_DOMAIN} + - VITE_HMR_HOST=${VITE_HMR_HOST} + networks: + - caddy + labels: + caddy: ${PUBLIC_DOMAIN} + caddy.reverse_proxy: "{{upstreams ${APP_PORT:-4321}}}" + caddy.reverse_proxy.flush_interval: "-1" + caddy.reverse_proxy.transport: http + caddy.reverse_proxy.transport.read_timeout: "0" + caddy.reverse_proxy.transport.write_timeout: "0" + caddy.reverse_proxy.transport.keepalive: 5m + caddy.reverse_proxy.transport.keepalive_idle_conns: "10" + caddy.reverse_proxy.stream_timeout: 24h + caddy.reverse_proxy.stream_close_delay: 5s + volumes: + - ./src:/app/src + - ./public:/app/public + - ./astro.config.mjs:/app/astro.config.mjs + +networks: + caddy: + external: true diff --git a/site/package-lock.json b/site/package-lock.json index da8d9fa..1cc5a21 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -1,7465 +1,7465 @@ -{ - "name": "skywalker-1-docs", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "skywalker-1-docs", - "version": "0.0.1", - "dependencies": { - "@astrojs/starlight": "^0.37.6", - "astro": "^5.17.2", - "sharp": "^0.33.0", - "starlight-image-zoom": "^0.13.2", - "starlight-links-validator": "^0.19.2" - } - }, - "node_modules/@astrojs/compiler": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", - "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==", - "license": "MIT" - }, - "node_modules/@astrojs/internal-helpers": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz", - "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==", - "license": "MIT" - }, - "node_modules/@astrojs/markdown-remark": { - "version": "6.3.10", - "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.10.tgz", - "integrity": "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==", - "license": "MIT", - "dependencies": { - "@astrojs/internal-helpers": "0.7.5", - "@astrojs/prism": "3.3.0", - "github-slugger": "^2.0.0", - "hast-util-from-html": "^2.0.3", - "hast-util-to-text": "^4.0.2", - "import-meta-resolve": "^4.2.0", - "js-yaml": "^4.1.1", - "mdast-util-definitions": "^6.0.0", - "rehype-raw": "^7.0.0", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.1", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.2", - "remark-smartypants": "^3.0.2", - "shiki": "^3.19.0", - "smol-toml": "^1.5.2", - "unified": "^11.0.5", - "unist-util-remove-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.2", - "vfile": "^6.0.3" - } - }, - "node_modules/@astrojs/mdx": { - "version": "4.3.13", - "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.13.tgz", - "integrity": "sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==", - "license": "MIT", - "dependencies": { - "@astrojs/markdown-remark": "6.3.10", - "@mdx-js/mdx": "^3.1.1", - "acorn": "^8.15.0", - "es-module-lexer": "^1.7.0", - "estree-util-visit": "^2.0.0", - "hast-util-to-html": "^9.0.5", - "piccolore": "^0.1.3", - "rehype-raw": "^7.0.0", - "remark-gfm": "^4.0.1", - "remark-smartypants": "^3.0.2", - "source-map": "^0.7.6", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.3" - }, - "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" - }, - "peerDependencies": { - "astro": "^5.0.0" - } - }, - "node_modules/@astrojs/prism": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", - "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", - "license": "MIT", - "dependencies": { - "prismjs": "^1.30.0" - }, - "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" - } - }, - "node_modules/@astrojs/sitemap": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.0.tgz", - "integrity": "sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA==", - "license": "MIT", - "dependencies": { - "sitemap": "^8.0.2", - "stream-replace-string": "^2.0.0", - "zod": "^3.25.76" - } - }, - "node_modules/@astrojs/starlight": { - "version": "0.37.6", - "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.37.6.tgz", - "integrity": "sha512-wQrKwH431q+8FsLBnNQeG+R36TMtEGxTQ2AuiVpcx9APcazvL3n7wVW8mMmYyxX0POjTnxlcWPkdMGR3Yj1L+w==", - "license": "MIT", - "dependencies": { - "@astrojs/markdown-remark": "^6.3.1", - "@astrojs/mdx": "^4.2.3", - "@astrojs/sitemap": "^3.3.0", - "@pagefind/default-ui": "^1.3.0", - "@types/hast": "^3.0.4", - "@types/js-yaml": "^4.0.9", - "@types/mdast": "^4.0.4", - "astro-expressive-code": "^0.41.1", - "bcp-47": "^2.1.0", - "hast-util-from-html": "^2.0.1", - "hast-util-select": "^6.0.2", - "hast-util-to-string": "^3.0.0", - "hastscript": "^9.0.0", - "i18next": "^23.11.5", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "magic-string": "^0.30.17", - "mdast-util-directive": "^3.0.0", - "mdast-util-to-markdown": "^2.1.0", - "mdast-util-to-string": "^4.0.0", - "pagefind": "^1.3.0", - "rehype": "^13.0.1", - "rehype-format": "^5.0.0", - "remark-directive": "^3.0.0", - "ultrahtml": "^1.6.0", - "unified": "^11.0.5", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.2" - }, - "peerDependencies": { - "astro": "^5.5.0" - } - }, - "node_modules/@astrojs/telemetry": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", - "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", - "license": "MIT", - "dependencies": { - "ci-info": "^4.2.0", - "debug": "^4.4.0", - "dlv": "^1.1.3", - "dset": "^3.1.4", - "is-docker": "^3.0.0", - "is-wsl": "^3.1.0", - "which-pm-runs": "^1.1.0" - }, - "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@capsizecss/unpack": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.0.tgz", - "integrity": "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==", - "license": "MIT", - "dependencies": { - "fontkitten": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ctrl/tinycolor": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", - "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@expressive-code/core": { - "version": "0.41.6", - "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.6.tgz", - "integrity": "sha512-FvJQP+hG0jWi/FLBSmvHInDqWR7jNANp9PUDjdMqSshHb0y7sxx3vHuoOr6SgXjWw+MGLqorZyPQ0aAlHEok6g==", - "license": "MIT", - "dependencies": { - "@ctrl/tinycolor": "^4.0.4", - "hast-util-select": "^6.0.2", - "hast-util-to-html": "^9.0.1", - "hast-util-to-text": "^4.0.1", - "hastscript": "^9.0.0", - "postcss": "^8.4.38", - "postcss-nested": "^6.0.1", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.1" - } - }, - "node_modules/@expressive-code/plugin-frames": { - "version": "0.41.6", - "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.41.6.tgz", - "integrity": "sha512-d+hkSYXIQot6fmYnOmWAM+7TNWRv/dhfjMsNq+mIZz8Tb4mPHOcgcfZeEM5dV9TDL0ioQNvtcqQNuzA1sRPjxg==", - "license": "MIT", - "dependencies": { - "@expressive-code/core": "^0.41.6" - } - }, - "node_modules/@expressive-code/plugin-shiki": { - "version": "0.41.6", - "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.41.6.tgz", - "integrity": "sha512-Y6zmKBmsIUtWTzdefqlzm/h9Zz0Rc4gNdt2GTIH7fhHH2I9+lDYCa27BDwuBhjqcos6uK81Aca9dLUC4wzN+ng==", - "license": "MIT", - "dependencies": { - "@expressive-code/core": "^0.41.6", - "shiki": "^3.2.2" - } - }, - "node_modules/@expressive-code/plugin-text-markers": { - "version": "0.41.6", - "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.41.6.tgz", - "integrity": "sha512-PBFa1wGyYzRExMDzBmAWC6/kdfG1oLn4pLpBeTfIRrALPjcGA/59HP3e7q9J0Smk4pC7U+lWkA2LHR8FYV8U7Q==", - "license": "MIT", - "dependencies": { - "@expressive-code/core": "^0.41.6" - } - }, - "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", - "cpu": [ - "ppc64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", - "cpu": [ - "riscv64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", - "cpu": [ - "ppc64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", - "cpu": [ - "riscv64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@mdx-js/mdx": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", - "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdx": "^2.0.0", - "acorn": "^8.0.0", - "collapse-white-space": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-util-scope": "^1.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "markdown-extensions": "^2.0.0", - "recma-build-jsx": "^1.0.0", - "recma-jsx": "^1.0.0", - "recma-stringify": "^1.0.0", - "rehype-recma": "^1.0.0", - "remark-mdx": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "source-map": "^0.7.0", - "unified": "^11.0.0", - "unist-util-position-from-estree": "^2.0.0", - "unist-util-stringify-position": "^4.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@oslojs/encoding": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", - "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", - "license": "MIT" - }, - "node_modules/@pagefind/darwin-arm64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", - "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@pagefind/darwin-x64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", - "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@pagefind/default-ui": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.4.0.tgz", - "integrity": "sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==", - "license": "MIT" - }, - "node_modules/@pagefind/freebsd-x64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", - "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@pagefind/linux-arm64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", - "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@pagefind/linux-x64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", - "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@pagefind/windows-x64": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", - "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@shikijs/core": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz", - "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.22.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.5" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz", - "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.22.0", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^4.3.4" - } - }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", - "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.22.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, - "node_modules/@shikijs/langs": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", - "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.22.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", - "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.22.0" - } - }, - "node_modules/@shikijs/types": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", - "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", - "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "license": "MIT" - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdx": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/nlcst": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", - "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/node": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", - "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/picomatch": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-3.0.2.tgz", - "integrity": "sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA==", - "license": "MIT" - }, - "node_modules/@types/sax": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", - "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/array-iterate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", - "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/astring": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", - "license": "MIT", - "bin": { - "astring": "bin/astring" - } - }, - "node_modules/astro": { - "version": "5.17.2", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.17.2.tgz", - "integrity": "sha512-7jnMqGo53hOQNwo1N/wqeOvUp8wwW/p+DeerSjSkHNx8L/1mhy6P7rVo7EhdmF8DpKqw0tl/B5Fx1WcIzg1ysA==", - "license": "MIT", - "dependencies": { - "@astrojs/compiler": "^2.13.0", - "@astrojs/internal-helpers": "0.7.5", - "@astrojs/markdown-remark": "6.3.10", - "@astrojs/telemetry": "3.3.0", - "@capsizecss/unpack": "^4.0.0", - "@oslojs/encoding": "^1.1.0", - "@rollup/pluginutils": "^5.3.0", - "acorn": "^8.15.0", - "aria-query": "^5.3.2", - "axobject-query": "^4.1.0", - "boxen": "8.0.1", - "ci-info": "^4.3.1", - "clsx": "^2.1.1", - "common-ancestor-path": "^1.0.1", - "cookie": "^1.1.1", - "cssesc": "^3.0.0", - "debug": "^4.4.3", - "deterministic-object-hash": "^2.0.2", - "devalue": "^5.6.2", - "diff": "^8.0.3", - "dlv": "^1.1.3", - "dset": "^3.1.4", - "es-module-lexer": "^1.7.0", - "esbuild": "^0.27.0", - "estree-walker": "^3.0.3", - "flattie": "^1.1.1", - "fontace": "~0.4.0", - "github-slugger": "^2.0.0", - "html-escaper": "3.0.3", - "http-cache-semantics": "^4.2.0", - "import-meta-resolve": "^4.2.0", - "js-yaml": "^4.1.1", - "magic-string": "^0.30.21", - "magicast": "^0.5.1", - "mrmime": "^2.0.1", - "neotraverse": "^0.6.18", - "p-limit": "^6.2.0", - "p-queue": "^8.1.1", - "package-manager-detector": "^1.6.0", - "piccolore": "^0.1.3", - "picomatch": "^4.0.3", - "prompts": "^2.4.2", - "rehype": "^13.0.2", - "semver": "^7.7.3", - "shiki": "^3.21.0", - "smol-toml": "^1.6.0", - "svgo": "^4.0.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tsconfck": "^3.1.6", - "ultrahtml": "^1.6.0", - "unifont": "~0.7.3", - "unist-util-visit": "^5.0.0", - "unstorage": "^1.17.4", - "vfile": "^6.0.3", - "vite": "^6.4.1", - "vitefu": "^1.1.1", - "xxhash-wasm": "^1.1.0", - "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.3", - "zod": "^3.25.76", - "zod-to-json-schema": "^3.25.1", - "zod-to-ts": "^1.2.0" - }, - "bin": { - "astro": "astro.js" - }, - "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/astrodotbuild" - }, - "optionalDependencies": { - "sharp": "^0.34.0" - } - }, - "node_modules/astro-expressive-code": { - "version": "0.41.6", - "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.6.tgz", - "integrity": "sha512-l47tb1uhmVIebHUkw+HEPtU/av0G4O8Q34g2cbkPvC7/e9ZhANcjUUciKt9Hp6gSVDdIuXBBLwJQn2LkeGMOAw==", - "license": "MIT", - "dependencies": { - "rehype-expressive-code": "^0.41.6" - }, - "peerDependencies": { - "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" - } - }, - "node_modules/astro/node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/astro/node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/astro/node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/astro/node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/astro/node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" - } - }, - "node_modules/astro/node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, - "node_modules/astro/node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" - } - }, - "node_modules/astro/node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" - } - }, - "node_modules/astro/node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/astro/node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@img/colour": "^1.0.0", - "detect-libc": "^2.1.2", - "semver": "^7.7.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "license": "MIT" - }, - "node_modules/bcp-47": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", - "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/bcp-47-match": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", - "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/common-ancestor-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", - "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", - "license": "ISC" - }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cookie-es": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", - "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", - "license": "MIT" - }, - "node_modules/crossws": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", - "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", - "license": "MIT", - "dependencies": { - "uncrypto": "^0.1.3" - } - }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-selector-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", - "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, - "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "license": "CC0-1.0" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/destr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/deterministic-object-hash": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", - "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", - "license": "MIT", - "dependencies": { - "base-64": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/devalue": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", - "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", - "license": "MIT" - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/diff": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/direction": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", - "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", - "license": "MIT", - "bin": { - "direction": "cli.js" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "license": "MIT" - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dset": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", - "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "license": "MIT" - }, - "node_modules/esast-util-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", - "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", - "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-util-attach-comments": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", - "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-build-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", - "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-walker": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-scope": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", - "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-to-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", - "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "astring": "^1.8.0", - "source-map": "^0.7.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-visit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", - "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT" - }, - "node_modules/expressive-code": { - "version": "0.41.6", - "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.6.tgz", - "integrity": "sha512-W/5+IQbrpCIM5KGLjO35wlp1NCwDOOVQb+PAvzEoGkW1xjGM807ZGfBKptNWH6UECvt6qgmLyWolCMYKh7eQmA==", - "license": "MIT", - "dependencies": { - "@expressive-code/core": "^0.41.6", - "@expressive-code/plugin-frames": "^0.41.6", - "@expressive-code/plugin-shiki": "^0.41.6", - "@expressive-code/plugin-text-markers": "^0.41.6" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/flattie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", - "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/fontace": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", - "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", - "license": "MIT", - "dependencies": { - "fontkitten": "^1.0.2" - } - }, - "node_modules/fontkitten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.2.tgz", - "integrity": "sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q==", - "license": "MIT", - "dependencies": { - "tiny-inflate": "^1.0.3" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/github-slugger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", - "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", - "license": "ISC" - }, - "node_modules/h3": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", - "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", - "license": "MIT", - "dependencies": { - "cookie-es": "^1.2.2", - "crossws": "^0.3.5", - "defu": "^6.1.4", - "destr": "^2.0.5", - "iron-webcrypto": "^1.2.1", - "node-mock-http": "^1.0.4", - "radix3": "^1.1.2", - "ufo": "^1.6.3", - "uncrypto": "^0.1.3" - } - }, - "node_modules/has-flag": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", - "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hast-util-embedded": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", - "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-is-element": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-format": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", - "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-minify-whitespace": "^1.0.0", - "hast-util-phrasing": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "html-whitespace-sensitive-tag-names": "^3.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-html": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", - "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.1.0", - "hast-util-from-parse5": "^8.0.0", - "parse5": "^7.0.0", - "vfile": "^6.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-has-property": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", - "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-is-body-ok-link": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", - "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-is-element": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", - "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-minify-whitespace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", - "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-phrasing": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", - "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-has-property": "^3.0.0", - "hast-util-is-body-ok-link": "^3.0.0", - "hast-util-is-element": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-select": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", - "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "bcp-47-match": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "css-selector-parser": "^3.0.0", - "devlop": "^1.0.0", - "direction": "^2.0.0", - "hast-util-has-property": "^3.0.0", - "hast-util-to-string": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "nth-check": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-estree": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", - "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-attach-comments": "^3.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", - "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", - "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-string": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", - "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-text": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", - "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "unist-util-find-after": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/html-escaper": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", - "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/html-whitespace-sensitive-tag-names": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", - "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause" - }, - "node_modules/i18next": { - "version": "23.16.8", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", - "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.2" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/inline-style-parser": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", - "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", - "license": "MIT" - }, - "node_modules/iron-webcrypto": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", - "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/brc-dd" - } - }, - "node_modules/is-absolute-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", - "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-arrayish": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", - "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", - "license": "MIT" - }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "node_modules/markdown-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", - "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdast-util-definitions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", - "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-directive": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", - "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", - "license": "CC0-1.0" - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", - "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", - "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", - "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", - "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", - "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", - "license": "MIT", - "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", - "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", - "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", - "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/neotraverse": { - "version": "0.6.18", - "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", - "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/nlcst-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", - "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/node-fetch-native": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "license": "MIT" - }, - "node_modules/node-mock-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", - "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/ofetch": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", - "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", - "license": "MIT", - "dependencies": { - "destr": "^2.0.5", - "node-fetch-native": "^1.6.7", - "ufo": "^1.6.1" - } - }, - "node_modules/ohash": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "license": "MIT" - }, - "node_modules/oniguruma-parser": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", - "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", - "license": "MIT" - }, - "node_modules/oniguruma-to-es": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", - "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", - "license": "MIT", - "dependencies": { - "oniguruma-parser": "^0.12.1", - "regex": "^6.0.1", - "regex-recursion": "^6.0.2" - } - }, - "node_modules/p-limit": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", - "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", - "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^5.0.1", - "p-timeout": "^6.1.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", - "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-manager-detector": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", - "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", - "license": "MIT" - }, - "node_modules/pagefind": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", - "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", - "license": "MIT", - "bin": { - "pagefind": "lib/runner/bin.cjs" - }, - "optionalDependencies": { - "@pagefind/darwin-arm64": "1.4.0", - "@pagefind/darwin-x64": "1.4.0", - "@pagefind/freebsd-x64": "1.4.0", - "@pagefind/linux-arm64": "1.4.0", - "@pagefind/linux-x64": "1.4.0", - "@pagefind/windows-x64": "1.4.0" - } - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, - "node_modules/parse-latin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", - "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "@types/unist": "^3.0.0", - "nlcst-to-string": "^4.0.0", - "unist-util-modify-children": "^4.0.0", - "unist-util-visit-children": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/piccolore": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", - "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", - "license": "ISC" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/prismjs": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/radix3": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", - "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/recma-build-jsx": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", - "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-jsx": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", - "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", - "license": "MIT", - "dependencies": { - "acorn-jsx": "^5.0.0", - "estree-util-to-js": "^2.0.0", - "recma-parse": "^1.0.0", - "recma-stringify": "^1.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/recma-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", - "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "esast-util-from-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-stringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", - "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-to-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", - "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "license": "MIT" - }, - "node_modules/rehype": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", - "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "rehype-parse": "^9.0.0", - "rehype-stringify": "^10.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-expressive-code": { - "version": "0.41.6", - "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.41.6.tgz", - "integrity": "sha512-aBMX8kxPtjmDSFUdZlAWJkMvsQ4ZMASfee90JWIAV8tweltXLzkWC3q++43ToTelI8ac5iC0B3/S/Cl4Ql1y2g==", - "license": "MIT", - "dependencies": { - "expressive-code": "^0.41.6" - } - }, - "node_modules/rehype-format": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", - "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-format": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-parse": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", - "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-from-html": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-raw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", - "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-recma": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", - "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "hast-util-to-estree": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-stringify": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", - "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-to-html": "^9.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-directive": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", - "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-directive": "^3.0.0", - "micromark-extension-directive": "^3.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-mdx": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", - "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", - "license": "MIT", - "dependencies": { - "mdast-util-mdx": "^3.0.0", - "micromark-extension-mdxjs": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-smartypants": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", - "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", - "license": "MIT", - "dependencies": { - "retext": "^9.0.0", - "retext-smartypants": "^6.0.0", - "unified": "^11.0.4", - "unist-util-visit": "^5.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/retext": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", - "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "retext-latin": "^4.0.0", - "retext-stringify": "^4.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/retext-latin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", - "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "parse-latin": "^7.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/retext-smartypants": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", - "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "nlcst-to-string": "^4.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/retext-stringify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", - "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "nlcst-to-string": "^4.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/shiki": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", - "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==", - "license": "MIT", - "dependencies": { - "@shikijs/core": "3.22.0", - "@shikijs/engine-javascript": "3.22.0", - "@shikijs/engine-oniguruma": "3.22.0", - "@shikijs/langs": "3.22.0", - "@shikijs/themes": "3.22.0", - "@shikijs/types": "3.22.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", - "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/sitemap": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.2.tgz", - "integrity": "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==", - "license": "MIT", - "dependencies": { - "@types/node": "^17.0.5", - "@types/sax": "^1.2.1", - "arg": "^5.0.0", - "sax": "^1.4.1" - }, - "bin": { - "sitemap": "dist/cli.js" - }, - "engines": { - "node": ">=14.0.0", - "npm": ">=6.0.0" - } - }, - "node_modules/sitemap/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "license": "MIT" - }, - "node_modules/smol-toml": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", - "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 18" - }, - "funding": { - "url": "https://github.com/sponsors/cyyynthia" - } - }, - "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/starlight-image-zoom": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/starlight-image-zoom/-/starlight-image-zoom-0.13.2.tgz", - "integrity": "sha512-fDJrx+UZXhkbhEeXKoRogTKAYtrYVJPw6wmSUI3nHUTA0vuRM6EI//2Z8bzv3Ecvz0pHKD1vAxtS01mLyessBA==", - "license": "MIT", - "dependencies": { - "mdast-util-mdx-jsx": "^3.1.3", - "rehype-raw": "^7.0.0", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@astrojs/starlight": ">=0.32.0" - } - }, - "node_modules/starlight-links-validator": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/starlight-links-validator/-/starlight-links-validator-0.19.2.tgz", - "integrity": "sha512-IHeK3R78fsmv53VfRkGbXkwK1CQEUBHM9QPzBEyoAxjZ/ssi5gjV+F4oNNUppTR48iPp+lEY0MTAmvkX7yNnkw==", - "license": "MIT", - "dependencies": { - "@types/picomatch": "^3.0.1", - "github-slugger": "^2.0.0", - "hast-util-from-html": "^2.0.3", - "hast-util-has-property": "^3.0.0", - "is-absolute-url": "^4.0.1", - "kleur": "^4.1.5", - "mdast-util-mdx-jsx": "^3.1.3", - "mdast-util-to-string": "^4.0.0", - "picomatch": "^4.0.2", - "terminal-link": "^5.0.0", - "unist-util-visit": "^5.0.0" - }, - "engines": { - "node": ">=18.17.1" - }, - "peerDependencies": { - "@astrojs/starlight": ">=0.32.0", - "astro": ">=5.1.5" - } - }, - "node_modules/starlight-links-validator/node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/stream-replace-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", - "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", - "license": "MIT" - }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/style-to-js": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", - "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", - "license": "MIT", - "dependencies": { - "style-to-object": "1.0.14" - } - }, - "node_modules/style-to-object": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", - "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.7" - } - }, - "node_modules/supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-hyperlinks": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.4.0.tgz", - "integrity": "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==", - "license": "MIT", - "dependencies": { - "has-flag": "^5.0.1", - "supports-color": "^10.2.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" - } - }, - "node_modules/svgo": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", - "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", - "license": "MIT", - "dependencies": { - "commander": "^11.1.0", - "css-select": "^5.1.0", - "css-tree": "^3.0.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.1.1", - "sax": "^1.4.1" - }, - "bin": { - "svgo": "bin/svgo.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/terminal-link": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-5.0.0.tgz", - "integrity": "sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "supports-hyperlinks": "^4.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/tsconfck": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", - "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true - }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "license": "MIT" - }, - "node_modules/ultrahtml": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", - "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", - "license": "MIT" - }, - "node_modules/uncrypto": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", - "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unifont": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.3.tgz", - "integrity": "sha512-b0GtQzKCyuSHGsfj5vyN8st7muZ6VCI4XD4vFlr7Uy1rlWVYxC3npnfk8MyreHxJYrz1ooLDqDzFe9XqQTlAhA==", - "license": "MIT", - "dependencies": { - "css-tree": "^3.1.0", - "ofetch": "^1.5.1", - "ohash": "^2.0.11" - } - }, - "node_modules/unist-util-find-after": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", - "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-modify-children": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", - "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "array-iterate": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", - "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", - "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-children": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", - "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unstorage": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", - "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", - "license": "MIT", - "dependencies": { - "anymatch": "^3.1.3", - "chokidar": "^5.0.0", - "destr": "^2.0.5", - "h3": "^1.15.5", - "lru-cache": "^11.2.0", - "node-fetch-native": "^1.6.7", - "ofetch": "^1.5.1", - "ufo": "^1.6.3" - }, - "peerDependencies": { - "@azure/app-configuration": "^1.8.0", - "@azure/cosmos": "^4.2.0", - "@azure/data-tables": "^13.3.0", - "@azure/identity": "^4.6.0", - "@azure/keyvault-secrets": "^4.9.0", - "@azure/storage-blob": "^12.26.0", - "@capacitor/preferences": "^6 || ^7 || ^8", - "@deno/kv": ">=0.9.0", - "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", - "@planetscale/database": "^1.19.0", - "@upstash/redis": "^1.34.3", - "@vercel/blob": ">=0.27.1", - "@vercel/functions": "^2.2.12 || ^3.0.0", - "@vercel/kv": "^1 || ^2 || ^3", - "aws4fetch": "^1.0.20", - "db0": ">=0.2.1", - "idb-keyval": "^6.2.1", - "ioredis": "^5.4.2", - "uploadthing": "^7.4.4" - }, - "peerDependenciesMeta": { - "@azure/app-configuration": { - "optional": true - }, - "@azure/cosmos": { - "optional": true - }, - "@azure/data-tables": { - "optional": true - }, - "@azure/identity": { - "optional": true - }, - "@azure/keyvault-secrets": { - "optional": true - }, - "@azure/storage-blob": { - "optional": true - }, - "@capacitor/preferences": { - "optional": true - }, - "@deno/kv": { - "optional": true - }, - "@netlify/blobs": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/blob": { - "optional": true - }, - "@vercel/functions": { - "optional": true - }, - "@vercel/kv": { - "optional": true - }, - "aws4fetch": { - "optional": true - }, - "db0": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "uploadthing": { - "optional": true - } - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/vitefu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/which-pm-runs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", - "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/xxhash-wasm": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", - "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", - "license": "MIT" - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yocto-spinner": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", - "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25 || ^4" - } - }, - "node_modules/zod-to-ts": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", - "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", - "peerDependencies": { - "typescript": "^4.9.4 || ^5.0.2", - "zod": "^3" - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} +{ + "name": "skywalker-1-docs", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "skywalker-1-docs", + "version": "0.0.1", + "dependencies": { + "@astrojs/starlight": "^0.37.6", + "astro": "^5.17.2", + "sharp": "^0.33.0", + "starlight-image-zoom": "^0.13.2", + "starlight-links-validator": "^0.19.2" + } + }, + "node_modules/@astrojs/compiler": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", + "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz", + "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==", + "license": "MIT" + }, + "node_modules/@astrojs/markdown-remark": { + "version": "6.3.10", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.10.tgz", + "integrity": "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/prism": "3.3.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "shiki": "^3.19.0", + "smol-toml": "^1.5.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.13.tgz", + "integrity": "sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "6.3.10", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.15.0", + "es-module-lexer": "^1.7.0", + "estree-util-visit": "^2.0.0", + "hast-util-to-html": "^9.0.5", + "piccolore": "^0.1.3", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "astro": "^5.0.0" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", + "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.0.tgz", + "integrity": "sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA==", + "license": "MIT", + "dependencies": { + "sitemap": "^8.0.2", + "stream-replace-string": "^2.0.0", + "zod": "^3.25.76" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.37.6", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.37.6.tgz", + "integrity": "sha512-wQrKwH431q+8FsLBnNQeG+R36TMtEGxTQ2AuiVpcx9APcazvL3n7wVW8mMmYyxX0POjTnxlcWPkdMGR3Yj1L+w==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "^6.3.1", + "@astrojs/mdx": "^4.2.3", + "@astrojs/sitemap": "^3.3.0", + "@pagefind/default-ui": "^1.3.0", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.41.1", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.1", + "hast-util-select": "^6.0.2", + "hast-util-to-string": "^3.0.0", + "hastscript": "^9.0.0", + "i18next": "^23.11.5", + "js-yaml": "^4.1.0", + "klona": "^2.0.6", + "magic-string": "^0.30.17", + "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.3.0", + "rehype": "^13.0.1", + "rehype-format": "^5.0.0", + "remark-directive": "^3.0.0", + "ultrahtml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" + }, + "peerDependencies": { + "astro": "^5.5.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", + "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.2.0", + "debug": "^4.4.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.0.tgz", + "integrity": "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.6.tgz", + "integrity": "sha512-FvJQP+hG0jWi/FLBSmvHInDqWR7jNANp9PUDjdMqSshHb0y7sxx3vHuoOr6SgXjWw+MGLqorZyPQ0aAlHEok6g==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.41.6.tgz", + "integrity": "sha512-d+hkSYXIQot6fmYnOmWAM+7TNWRv/dhfjMsNq+mIZz8Tb4mPHOcgcfZeEM5dV9TDL0ioQNvtcqQNuzA1sRPjxg==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.6" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.41.6.tgz", + "integrity": "sha512-Y6zmKBmsIUtWTzdefqlzm/h9Zz0Rc4gNdt2GTIH7fhHH2I9+lDYCa27BDwuBhjqcos6uK81Aca9dLUC4wzN+ng==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.6", + "shiki": "^3.2.2" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.41.6.tgz", + "integrity": "sha512-PBFa1wGyYzRExMDzBmAWC6/kdfG1oLn4pLpBeTfIRrALPjcGA/59HP3e7q9J0Smk4pC7U+lWkA2LHR8FYV8U7Q==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.6" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", + "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", + "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.4.0.tgz", + "integrity": "sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==", + "license": "MIT" + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", + "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", + "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", + "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", + "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz", + "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz", + "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", + "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", + "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", + "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", + "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/picomatch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-3.0.2.tgz", + "integrity": "sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA==", + "license": "MIT" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "5.17.2", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.17.2.tgz", + "integrity": "sha512-7jnMqGo53hOQNwo1N/wqeOvUp8wwW/p+DeerSjSkHNx8L/1mhy6P7rVo7EhdmF8DpKqw0tl/B5Fx1WcIzg1ysA==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.13.0", + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/markdown-remark": "6.3.10", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^4.0.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "acorn": "^8.15.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.3.1", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^1.1.1", + "cssesc": "^3.0.0", + "debug": "^4.4.3", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.6.2", + "diff": "^8.0.3", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.7.0", + "esbuild": "^0.27.0", + "estree-walker": "^3.0.3", + "flattie": "^1.1.1", + "fontace": "~0.4.0", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.1", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "p-limit": "^6.2.0", + "p-queue": "^8.1.1", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.3", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.7.3", + "shiki": "^3.21.0", + "smol-toml": "^1.6.0", + "svgo": "^4.0.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tsconfck": "^3.1.6", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.3", + "unist-util-visit": "^5.0.0", + "unstorage": "^1.17.4", + "vfile": "^6.0.3", + "vite": "^6.4.1", + "vitefu": "^1.1.1", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "yocto-spinner": "^0.2.3", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.25.1", + "zod-to-ts": "^1.2.0" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.6.tgz", + "integrity": "sha512-l47tb1uhmVIebHUkw+HEPtU/av0G4O8Q34g2cbkPvC7/e9ZhANcjUUciKt9Hp6gSVDdIuXBBLwJQn2LkeGMOAw==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.41.6" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" + } + }, + "node_modules/astro/node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-selector-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", + "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "license": "MIT", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", + "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.6.tgz", + "integrity": "sha512-W/5+IQbrpCIM5KGLjO35wlp1NCwDOOVQb+PAvzEoGkW1xjGM807ZGfBKptNWH6UECvt6qgmLyWolCMYKh7eQmA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.6", + "@expressive-code/plugin-frames": "^0.41.6", + "@expressive-code/plugin-shiki": "^0.41.6", + "@expressive-code/plugin-text-markers": "^0.41.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.2.tgz", + "integrity": "sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", + "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/has-flag": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", + "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", + "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", + "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.4.0", + "@pagefind/darwin-x64": "1.4.0", + "@pagefind/freebsd-x64": "1.4.0", + "@pagefind/linux-arm64": "1.4.0", + "@pagefind/linux-x64": "1.4.0", + "@pagefind/windows-x64": "1.4.0" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.41.6.tgz", + "integrity": "sha512-aBMX8kxPtjmDSFUdZlAWJkMvsQ4ZMASfee90JWIAV8tweltXLzkWC3q++43ToTelI8ac5iC0B3/S/Cl4Ql1y2g==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.41.6" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shiki": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", + "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.22.0", + "@shikijs/engine-javascript": "3.22.0", + "@shikijs/engine-oniguruma": "3.22.0", + "@shikijs/langs": "3.22.0", + "@shikijs/themes": "3.22.0", + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.2.tgz", + "integrity": "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", + "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/starlight-image-zoom": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/starlight-image-zoom/-/starlight-image-zoom-0.13.2.tgz", + "integrity": "sha512-fDJrx+UZXhkbhEeXKoRogTKAYtrYVJPw6wmSUI3nHUTA0vuRM6EI//2Z8bzv3Ecvz0pHKD1vAxtS01mLyessBA==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx-jsx": "^3.1.3", + "rehype-raw": "^7.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.32.0" + } + }, + "node_modules/starlight-links-validator": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/starlight-links-validator/-/starlight-links-validator-0.19.2.tgz", + "integrity": "sha512-IHeK3R78fsmv53VfRkGbXkwK1CQEUBHM9QPzBEyoAxjZ/ssi5gjV+F4oNNUppTR48iPp+lEY0MTAmvkX7yNnkw==", + "license": "MIT", + "dependencies": { + "@types/picomatch": "^3.0.1", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-has-property": "^3.0.0", + "is-absolute-url": "^4.0.1", + "kleur": "^4.1.5", + "mdast-util-mdx-jsx": "^3.1.3", + "mdast-util-to-string": "^4.0.0", + "picomatch": "^4.0.2", + "terminal-link": "^5.0.0", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=18.17.1" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.32.0", + "astro": ">=5.1.5" + } + }, + "node_modules/starlight-links-validator/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-hyperlinks": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.4.0.tgz", + "integrity": "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==", + "license": "MIT", + "dependencies": { + "has-flag": "^5.0.1", + "supports-color": "^10.2.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/svgo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.4.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/terminal-link": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-5.0.0.tgz", + "integrity": "sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^4.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.3.tgz", + "integrity": "sha512-b0GtQzKCyuSHGsfj5vyN8st7muZ6VCI4XD4vFlr7Uy1rlWVYxC3npnfk8MyreHxJYrz1ooLDqDzFe9XqQTlAhA==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ofetch": "^1.5.1", + "ohash": "^2.0.11" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", + "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.5", + "lru-cache": "^11.2.0", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-spinner": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", + "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, + "node_modules/zod-to-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", + "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", + "peerDependencies": { + "typescript": "^4.9.4 || ^5.0.2", + "zod": "^3" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/site/package.json b/site/package.json index 0b91813..b31a9fd 100644 --- a/site/package.json +++ b/site/package.json @@ -1,19 +1,19 @@ -{ - "name": "skywalker-1-docs", - "type": "module", - "version": "0.0.1", - "scripts": { - "dev": "astro dev", - "start": "astro dev", - "build": "astro build", - "preview": "astro preview", - "astro": "astro" - }, - "dependencies": { - "astro": "^5.17.2", - "@astrojs/starlight": "^0.37.6", - "sharp": "^0.33.0", - "starlight-image-zoom": "^0.13.2", - "starlight-links-validator": "^0.19.2" - } -} +{ + "name": "skywalker-1-docs", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "astro": "^5.17.2", + "@astrojs/starlight": "^0.37.6", + "sharp": "^0.33.0", + "starlight-image-zoom": "^0.13.2", + "starlight-links-validator": "^0.19.2" + } +} diff --git a/site/src/content.config.ts b/site/src/content.config.ts index 7c4d287..31b7476 100644 --- a/site/src/content.config.ts +++ b/site/src/content.config.ts @@ -1,6 +1,6 @@ -import { defineCollection } from 'astro:content'; -import { docsSchema } from '@astrojs/starlight/schema'; - -export const collections = { - docs: defineCollection({ schema: docsSchema() }), -}; +import { defineCollection } from 'astro:content'; +import { docsSchema } from '@astrojs/starlight/schema'; + +export const collections = { + docs: defineCollection({ schema: docsSchema() }), +}; diff --git a/site/src/content/docs/bcm4500/demodulator.mdx b/site/src/content/docs/bcm4500/demodulator.mdx index 5a531fe..531cad1 100644 --- a/site/src/content/docs/bcm4500/demodulator.mdx +++ b/site/src/content/docs/bcm4500/demodulator.mdx @@ -1,230 +1,230 @@ ---- -title: BCM4500 Demodulator Interface -description: Broadcom BCM4500 I2C addressing, register access protocol, and indirect register programming. ---- - -import { Tabs, TabItem, Badge, Aside, Steps } from '@astrojs/starlight/components'; - -The Broadcom BCM4500 is a 128-pin MQFP satellite demodulator that handles RF demodulation, forward error correction, and MPEG-2 transport stream output. The FX2 communicates with it exclusively over the shared I2C bus using an indirect register protocol. - -See the [Register Map](/bcm4500/register-map/) for a consolidated lookup of all BCM4500 and FX2 register addresses referenced on this page. - -## I2C Addressing - -| Parameter | Value | -|-----------|-------| -| 7-bit I2C address | 0x08 | -| 8-bit write address | 0x10 | -| 8-bit read address | 0x11 | -| Bus speed | 400 kHz | -| FX2 I2C controller registers | I2CS (0xE678), I2DAT (0xE679), I2CTL (0xE67A) | -| Alternate probe addresses (v2.13) | 0x3F, 0x7F | - -The custom firmware and kernel driver use the 7-bit address 0x08. The stock firmware writes `addr << 1` = 0x10 for write and `(addr << 1) | 1` = 0x11 for read, following standard I2C convention. - - - -## Direct Registers - -These registers are accessed via standard I2C write/read to the BCM4500's device address: - -| Register | Function | Used By | -|----------|----------|---------| -| 0xA2 | Status register (polled for readiness during boot) | Boot probe, signal strength | -| 0xA4 | Lock/ready register; bit 5 (0x20) = signal locked | Signal lock check | -| 0xA6 | Indirect page/address select | Indirect register protocol | -| 0xA7 | Indirect data register (read/write) | Indirect register protocol | -| 0xA8 | Indirect command register | Indirect register protocol | -| 0xF9 | Demod status (v2.13 only) | GET_DEMOD_STATUS (0x99) / INT0 polling | - -## Indirect Register Protocol - -The BCM4500 uses an indirect register access scheme through three directly-addressable registers (0xA6, 0xA7, 0xA8). All tuning configuration, initialization data, and signal monitoring are performed through this protocol. - -### Indirect Write Sequence - - -1. **Page select** -- I2C WRITE to 0x08, register 0xA6 with the page number (typically 0x00). - -2. **Data write** -- I2C WRITE to 0x08, register 0xA7 with N data bytes. The auto-increment feature allows writing multiple bytes in a single I2C transaction. - -3. **Execute** -- I2C WRITE to 0x08, register 0xA8 with value 0x03 (indirect write command). - -4. **Poll completion** -- I2C READ register 0xA8 until bit 0 clears (command complete). - -5. **Verify** -- Optionally I2C READ register 0xA7 to read back and compare. - - -```c title="Indirect Write Protocol" -// Step 1: Page select -I2C WRITE: [START] [0x10] [0xA6] [0x00] [STOP] - -// Step 2: Data write (multi-byte, auto-increment) -I2C WRITE: [START] [0x10] [0xA7] [data0] [data1] ... [dataN] [STOP] - -// Step 3: Execute indirect write -I2C WRITE: [START] [0x10] [0xA8] [0x03] [STOP] - -// Step 4: Poll until complete -I2C READ: [START] [0x10] [0xA8] [Sr] [0x11] [result] [STOP] - // Repeat until (result & 0x01) == 0 -``` - -### Indirect Read Sequence - - -1. **Address select** -- I2C WRITE to 0x08, register 0xA6 with the target register address. - -2. **Placeholder write** -- I2C WRITE to 0x08, register 0xA7 with value 0x00. - -3. **Execute** -- I2C WRITE to 0x08, register 0xA8 with value 0x01 (indirect read command). - -4. **Wait** -- Short delay (~1 ms) for the BCM4500 to fetch the data. - -5. **Read result** -- I2C READ from 0x08, register 0xA7 to get the result byte. - - -### Auto-Increment Behavior - -The BCM4500's data register (0xA7) supports auto-increment for multi-byte writes within a single I2C transaction. When writing N data bytes to 0xA7 without issuing STOP between bytes, the BCM4500 internally advances its data buffer pointer: - -``` -I2C transaction (single write, multiple data bytes): - START -> 0x10 (write) -> 0xA7 (reg) -> data[0] -> data[1] -> ... -> data[N-1] -> STOP -``` - -The firmware uses this for initialization blocks and tuning data, reducing I2C bus overhead compared to byte-by-byte writes. - -### Stock Firmware Init Block Write Pattern - -The stock firmware uses a specific pattern for writing initialization blocks, extracted from `FUN_CODE_0ddd`: - -```c title="Init Block Write Sequence" -// 1. Page select = 0 -I2C WRITE: [0x10] [0xA6] [0x00] - -// 2. Multi-byte data (auto-increment) -I2C WRITE: [0x10] [0xA7] [data0] [data1] ... [dataN] - -// 3. Trailing zero (stock firmware quirk) -I2C WRITE: [0x10] [0xA7] [0x00] - -// 4. Commit indirect write -I2C WRITE: [0x10] [0xA8] [0x03] - -// 5. Poll for completion -I2C READ: [0xA8] until bit 0 clear -``` - - - -## Demodulator Scan - -### Tuning Retry (All Firmware Versions) - -The tune function wraps the BCM4500 I2C programming sequence in a two-level retry loop: - -- **Inner loop**: tries up to 3 different I2C address configurations per attempt. This supports hardware variants where the BCM4500 may appear at different bus addresses. -- **Outer loop**: retries the entire inner scan up to 3 times. - -This yields up to **3 × 3 = 9 total I2C programming attempts** per tune command. The v2.13 firmware adds a 20-attempt retry with checksum verification on each individual I2C write within the programming step. - -### Boot-Time Probe (v2.13 Only) - -The v2.13 firmware repurposed the INT0 interrupt handler (vector at `CODE:0003`) from USB re-enumeration to demodulator availability detection. The handler runs once during boot, before the main loop: - -```c title="INT0 handler (v2.13) — demod probe" -void INT0_vector(void) { - for (counter = 0x28; counter != 0; counter--) { // 40 iterations - byte result = I2C_read(0x7F); // Probe first alternate address - if (result != 0x01) { - result = I2C_read(0x3F); // Probe second alternate address - if (result != 0x01) break; // Got a response — demod present - } - } - no_demod_flag = (counter == 0); // True if all 40 attempts failed -} -``` - -If neither address responds after 40 iterations, `no_demod_flag` is set. This flag causes the firmware to skip all tuning attempts, avoiding I2C bus hangs on boards where the demodulator is absent or unpopulated. - - - -### `no_demod_flag` Behavior - -When the boot probe fails (all 40 attempts exhausted): - -1. `no_demod_flag` is set to a non-zero value -2. The firmware enters its normal main loop but skips BCM4500 initialization -3. `TUNE_8PSK` (0x86) commands are accepted but silently fail (no I2C transactions issued) -4. `GET_SIGNAL_LOCK` (0x90) returns 0x00 (unlocked) -5. The device remains functional for USB communication but cannot tune - -This graceful degradation allows the host software to detect the condition via persistent lock failure rather than USB timeouts. - -## FEC Architecture - -The BCM4500 contains two FEC decoder paths: - - - - -**Advanced Modulation Turbo FEC Decoder** - -Iterative turbo code decoder with the following code rates: - -| Modulation | Supported Rates | -|-----------|----------------| -| QPSK | 1/4, 1/2, 3/4 | -| 8PSK | 2/3, 3/4, 5/6, 8/9 | -| 16QAM | 3/4 | - -Outer code: Reed-Solomon (t=10). - -These turbo codes are Broadcom/EchoStar proprietary and are not part of any open standard. They were used by Dish Network for high-definition satellite broadcasts. - - - - -**Legacy DVB/DIRECTV/DCII-Compliant FEC Decoder** - -Concatenated decoder with: - -| Stage | Type | Rates | -|-------|------|-------| -| Inner | Viterbi (convolutional code) | 1/2, 2/3, 3/4, 5/6, 7/8 | -| Outer | Reed-Solomon | Standard DVB-S parameters | - -This decoder handles standard DVB-S QPSK, DVB-S BPSK, DSS QPSK, and all Digicipher II modes. - - - - - - -## Modulation Mode Constants - -The firmware uses a 10-entry dispatch table for modulation types. See [Tuning Protocol](/bcm4500/tuning-protocol/) for the full tuning sequence. - -| Index | Modulation | FEC Path | -|-------|-----------|----------| -| 0 | DVB-S QPSK | Legacy (Viterbi + RS) | -| 1 | Turbo QPSK | Turbo | -| 2 | Turbo 8PSK | Turbo | -| 3 | Turbo 16QAM | Turbo | -| 4 | DCII Combo | Legacy | -| 5 | DCII I-stream | Legacy | -| 6 | DCII Q-stream | Legacy | -| 7 | DCII Offset QPSK | Legacy | -| 8 | DSS QPSK | Legacy (Viterbi + RS) | -| 9 | DVB-S BPSK | Legacy (Viterbi + RS) | - -Indices 8 and 9 (DSS and DVB BPSK) share the same firmware handler. The FEC lookup uses the same table as DVB-S QPSK but ORs the result with 0x80 to distinguish them. +--- +title: BCM4500 Demodulator Interface +description: Broadcom BCM4500 I2C addressing, register access protocol, and indirect register programming. +--- + +import { Tabs, TabItem, Badge, Aside, Steps } from '@astrojs/starlight/components'; + +The Broadcom BCM4500 is a 128-pin MQFP satellite demodulator that handles RF demodulation, forward error correction, and MPEG-2 transport stream output. The FX2 communicates with it exclusively over the shared I2C bus using an indirect register protocol. + +See the [Register Map](/bcm4500/register-map/) for a consolidated lookup of all BCM4500 and FX2 register addresses referenced on this page. + +## I2C Addressing + +| Parameter | Value | +|-----------|-------| +| 7-bit I2C address | 0x08 | +| 8-bit write address | 0x10 | +| 8-bit read address | 0x11 | +| Bus speed | 400 kHz | +| FX2 I2C controller registers | I2CS (0xE678), I2DAT (0xE679), I2CTL (0xE67A) | +| Alternate probe addresses (v2.13) | 0x3F, 0x7F | + +The custom firmware and kernel driver use the 7-bit address 0x08. The stock firmware writes `addr << 1` = 0x10 for write and `(addr << 1) | 1` = 0x11 for read, following standard I2C convention. + + + +## Direct Registers + +These registers are accessed via standard I2C write/read to the BCM4500's device address: + +| Register | Function | Used By | +|----------|----------|---------| +| 0xA2 | Status register (polled for readiness during boot) | Boot probe, signal strength | +| 0xA4 | Lock/ready register; bit 5 (0x20) = signal locked | Signal lock check | +| 0xA6 | Indirect page/address select | Indirect register protocol | +| 0xA7 | Indirect data register (read/write) | Indirect register protocol | +| 0xA8 | Indirect command register | Indirect register protocol | +| 0xF9 | Demod status (v2.13 only) | GET_DEMOD_STATUS (0x99) / INT0 polling | + +## Indirect Register Protocol + +The BCM4500 uses an indirect register access scheme through three directly-addressable registers (0xA6, 0xA7, 0xA8). All tuning configuration, initialization data, and signal monitoring are performed through this protocol. + +### Indirect Write Sequence + + +1. **Page select** -- I2C WRITE to 0x08, register 0xA6 with the page number (typically 0x00). + +2. **Data write** -- I2C WRITE to 0x08, register 0xA7 with N data bytes. The auto-increment feature allows writing multiple bytes in a single I2C transaction. + +3. **Execute** -- I2C WRITE to 0x08, register 0xA8 with value 0x03 (indirect write command). + +4. **Poll completion** -- I2C READ register 0xA8 until bit 0 clears (command complete). + +5. **Verify** -- Optionally I2C READ register 0xA7 to read back and compare. + + +```c title="Indirect Write Protocol" +// Step 1: Page select +I2C WRITE: [START] [0x10] [0xA6] [0x00] [STOP] + +// Step 2: Data write (multi-byte, auto-increment) +I2C WRITE: [START] [0x10] [0xA7] [data0] [data1] ... [dataN] [STOP] + +// Step 3: Execute indirect write +I2C WRITE: [START] [0x10] [0xA8] [0x03] [STOP] + +// Step 4: Poll until complete +I2C READ: [START] [0x10] [0xA8] [Sr] [0x11] [result] [STOP] + // Repeat until (result & 0x01) == 0 +``` + +### Indirect Read Sequence + + +1. **Address select** -- I2C WRITE to 0x08, register 0xA6 with the target register address. + +2. **Placeholder write** -- I2C WRITE to 0x08, register 0xA7 with value 0x00. + +3. **Execute** -- I2C WRITE to 0x08, register 0xA8 with value 0x01 (indirect read command). + +4. **Wait** -- Short delay (~1 ms) for the BCM4500 to fetch the data. + +5. **Read result** -- I2C READ from 0x08, register 0xA7 to get the result byte. + + +### Auto-Increment Behavior + +The BCM4500's data register (0xA7) supports auto-increment for multi-byte writes within a single I2C transaction. When writing N data bytes to 0xA7 without issuing STOP between bytes, the BCM4500 internally advances its data buffer pointer: + +``` +I2C transaction (single write, multiple data bytes): + START -> 0x10 (write) -> 0xA7 (reg) -> data[0] -> data[1] -> ... -> data[N-1] -> STOP +``` + +The firmware uses this for initialization blocks and tuning data, reducing I2C bus overhead compared to byte-by-byte writes. + +### Stock Firmware Init Block Write Pattern + +The stock firmware uses a specific pattern for writing initialization blocks, extracted from `FUN_CODE_0ddd`: + +```c title="Init Block Write Sequence" +// 1. Page select = 0 +I2C WRITE: [0x10] [0xA6] [0x00] + +// 2. Multi-byte data (auto-increment) +I2C WRITE: [0x10] [0xA7] [data0] [data1] ... [dataN] + +// 3. Trailing zero (stock firmware quirk) +I2C WRITE: [0x10] [0xA7] [0x00] + +// 4. Commit indirect write +I2C WRITE: [0x10] [0xA8] [0x03] + +// 5. Poll for completion +I2C READ: [0xA8] until bit 0 clear +``` + + + +## Demodulator Scan + +### Tuning Retry (All Firmware Versions) + +The tune function wraps the BCM4500 I2C programming sequence in a two-level retry loop: + +- **Inner loop**: tries up to 3 different I2C address configurations per attempt. This supports hardware variants where the BCM4500 may appear at different bus addresses. +- **Outer loop**: retries the entire inner scan up to 3 times. + +This yields up to **3 × 3 = 9 total I2C programming attempts** per tune command. The v2.13 firmware adds a 20-attempt retry with checksum verification on each individual I2C write within the programming step. + +### Boot-Time Probe (v2.13 Only) + +The v2.13 firmware repurposed the INT0 interrupt handler (vector at `CODE:0003`) from USB re-enumeration to demodulator availability detection. The handler runs once during boot, before the main loop: + +```c title="INT0 handler (v2.13) — demod probe" +void INT0_vector(void) { + for (counter = 0x28; counter != 0; counter--) { // 40 iterations + byte result = I2C_read(0x7F); // Probe first alternate address + if (result != 0x01) { + result = I2C_read(0x3F); // Probe second alternate address + if (result != 0x01) break; // Got a response — demod present + } + } + no_demod_flag = (counter == 0); // True if all 40 attempts failed +} +``` + +If neither address responds after 40 iterations, `no_demod_flag` is set. This flag causes the firmware to skip all tuning attempts, avoiding I2C bus hangs on boards where the demodulator is absent or unpopulated. + + + +### `no_demod_flag` Behavior + +When the boot probe fails (all 40 attempts exhausted): + +1. `no_demod_flag` is set to a non-zero value +2. The firmware enters its normal main loop but skips BCM4500 initialization +3. `TUNE_8PSK` (0x86) commands are accepted but silently fail (no I2C transactions issued) +4. `GET_SIGNAL_LOCK` (0x90) returns 0x00 (unlocked) +5. The device remains functional for USB communication but cannot tune + +This graceful degradation allows the host software to detect the condition via persistent lock failure rather than USB timeouts. + +## FEC Architecture + +The BCM4500 contains two FEC decoder paths: + + + + +**Advanced Modulation Turbo FEC Decoder** + +Iterative turbo code decoder with the following code rates: + +| Modulation | Supported Rates | +|-----------|----------------| +| QPSK | 1/4, 1/2, 3/4 | +| 8PSK | 2/3, 3/4, 5/6, 8/9 | +| 16QAM | 3/4 | + +Outer code: Reed-Solomon (t=10). + +These turbo codes are Broadcom/EchoStar proprietary and are not part of any open standard. They were used by Dish Network for high-definition satellite broadcasts. + + + + +**Legacy DVB/DIRECTV/DCII-Compliant FEC Decoder** + +Concatenated decoder with: + +| Stage | Type | Rates | +|-------|------|-------| +| Inner | Viterbi (convolutional code) | 1/2, 2/3, 3/4, 5/6, 7/8 | +| Outer | Reed-Solomon | Standard DVB-S parameters | + +This decoder handles standard DVB-S QPSK, DVB-S BPSK, DSS QPSK, and all Digicipher II modes. + + + + + + +## Modulation Mode Constants + +The firmware uses a 10-entry dispatch table for modulation types. See [Tuning Protocol](/bcm4500/tuning-protocol/) for the full tuning sequence. + +| Index | Modulation | FEC Path | +|-------|-----------|----------| +| 0 | DVB-S QPSK | Legacy (Viterbi + RS) | +| 1 | Turbo QPSK | Turbo | +| 2 | Turbo 8PSK | Turbo | +| 3 | Turbo 16QAM | Turbo | +| 4 | DCII Combo | Legacy | +| 5 | DCII I-stream | Legacy | +| 6 | DCII Q-stream | Legacy | +| 7 | DCII Offset QPSK | Legacy | +| 8 | DSS QPSK | Legacy (Viterbi + RS) | +| 9 | DVB-S BPSK | Legacy (Viterbi + RS) | + +Indices 8 and 9 (DSS and DVB BPSK) share the same firmware handler. The FEC lookup uses the same table as DVB-S QPSK but ORs the result with 0x80 to distinguish them. diff --git a/site/src/content/docs/bcm4500/gpif-streaming.mdx b/site/src/content/docs/bcm4500/gpif-streaming.mdx index e0b4a52..3f03496 100644 --- a/site/src/content/docs/bcm4500/gpif-streaming.mdx +++ b/site/src/content/docs/bcm4500/gpif-streaming.mdx @@ -1,241 +1,241 @@ ---- -title: GPIF Streaming -description: GPIF engine configuration, waveform descriptors, and transport stream data path from BCM4500 to USB host. ---- - -import { Tabs, TabItem, Badge, Aside, Steps } from '@astrojs/starlight/components'; - -The GPIF (General Programmable Interface) engine in the Cypress FX2 provides a hardware-managed data path from the BCM4500 demodulator to the USB host. After initial setup, no firmware intervention occurs in the data path -- the GPIF engine reads transport stream data directly into the EP2 FIFO, and the AUTOIN mechanism automatically commits full packets to the USB controller. - -## Data Flow Architecture - -``` - Cypress FX2 (CY7C68013A) - +-----------------------------+ - | | -BCM4500 P3.5 TS_EN | GPIF Engine EP2 FIFO | USB 2.0 HS -Demodulator <-----------------+ (Master Read) (AUTOIN) +------------> Host - (I2C:0x08) GPIF Data Bus | 0xE4xx wfm 4x buf | EP2 (0x82) - -------------------> CTL/RDY pins 8-bit | Bulk IN - 8-bit parallel TS | | 7 URBs x 8KB - +-----------------------------+ -``` - - - -## Key Register Configuration - -All values are identical across the three stock firmware versions (v2.06, Rev.2 v2.10, v2.13): - -| Register | Address | Value | Function | -|----------|---------|-------|----------| -| IFCONFIG | 0xE601 | 0xEE | Internal 48 MHz clock, GPIF master, async, debug output | -| EP2FIFOCFG | 0xE618 | 0x0C | AUTOIN=1, ZEROLENIN=1, 8-bit data path | -| REVCTL | 0xE60B | 0x03 | NOAUTOARM + SKIPCOMMIT | -| CPUCS | 0xE600 | bits [4:3]=10 | 48 MHz CPU clock | -| FLOWSTATEA | 0xE668 | OR 0x09 | FSEN (flow state enable) + FS[3] | -| GPIFIE | 0xE65C | OR 0x3D | Waveform, TC, DONE, FIFO flag, WF2 interrupts | - -### IFCONFIG Decode (0xEE = 1110_1110) - -| Bit | Name | Value | Meaning | -|-----|------|-------|---------| -| 7 | IFCLKSRC | 1 | Internal clock source | -| 6 | 3048MHZ | 1 | 48 MHz IFCLK frequency | -| 5 | IFCLKOE | 1 | IFCLK pin drives output (clock to BCM4500) | -| 4 | IFCLKPOL | 0 | Non-inverted clock polarity | -| 3 | ASYNC | 1 | Asynchronous GPIF (RDY pin handshaking) | -| 2 | GSTATE | 1 | Debug state output on PORTE | -| 1:0 | IFCFG | 10 | GPIF internal master mode | - -The FX2 operates as a GPIF master, reading data from the BCM4500's parallel transport stream output. Asynchronous mode means the GPIF uses RDY pin handshaking rather than clock-edge sampling. - -### EP2FIFOCFG Decode (0x0C = 0000_1100) - -| Bit | Name | Value | Meaning | -|-----|------|-------|---------| -| 4 | INFM1 | 0 | Packet count not decremented | -| 3 | AUTOIN | 1 | Auto-commit IN packets when FIFO buffer full | -| 2 | ZEROLENIN | 1 | Allow zero-length IN packets | -| 1 | (reserved) | 0 | -- | -| 0 | WORDWIDE | 0 | 8-bit data path (not 16-bit) | - -The AUTOIN bit is critical: when the GPIF fills an EP2 FIFO buffer to the configured packet size, the FX2 hardware automatically arms the buffer for USB transfer. - -## GPIF Waveform Configuration - -The GPIF waveform descriptors occupy 128 bytes at 0xE400-0xE47F and are loaded from a compressed init table during firmware startup. The waveform programs a straightforward read cycle: - -``` -States 0-5: CTL outputs = 0x01 (control line asserted), 1 IFCLK each -State 6: CTL = 0x07, length = 0 (idle/terminate) -Opcode: 0x00 (SDP = sample data point) -Output: 0xF0 (FIFO write flags) -``` - -This encodes a simple "assert read strobe, capture data, de-assert" cycle that reads one byte per GPIF transaction from the BCM4500's parallel port into the EP2 FIFO. - -## FIFO Reset Sequence - -All endpoint FIFOs are reset during initialization using the Cypress-prescribed procedure: - -```c title="FIFO Reset (FUN_CODE_10d9)" -FIFORESET = 0x80; // NAKALL: NAK all host transfers during reset -// 3-NOP SYNCDELAY -FIFORESET = 0x02; // Reset EP2 FIFO -// 3-NOP SYNCDELAY -FIFORESET = 0x04; // Reset EP4 FIFO -// 3-NOP SYNCDELAY -FIFORESET = 0x06; // Reset EP6 FIFO -// 3-NOP SYNCDELAY -FIFORESET = 0x08; // Reset EP8 FIFO -// 3-NOP SYNCDELAY -FIFORESET = 0x00; // Release NAKALL -``` - -The triple-NOP delays (mandatory SYNCDELAY) between writes are required by the FX2 architecture: XRAM register writes take 2 cycles to propagate, and back-to-back writes to the same register need at least 3 instruction cycles. - -## ARM_TRANSFER Command (0x85) - -The host issues vendor command 0x85 to start or stop the MPEG-2 transport stream. The handler checks the [configuration status byte](/usb/config-status/) bit 0 (demodulator active) before proceeding. - - - - -When `wValue=1` and the demodulator is active: - - -1. **Set streaming flag** -- config_byte bit 7 = 1 (bmArmed). - -2. **Load GPIF transaction count** -- GPIFTCB3:2 = 0x8000 (2 GB, effectively infinite). - -3. **Reset address and byte count** -- Clear GPIF address registers and EP2 FIFO byte count. - -4. **Assert TS_EN** -- P3.5 LOW (BCM4500 transport stream output enabled). - -5. **Wait for initial GPIF transaction** -- Poll GPIFTRIG bit 7 (DONE) until set. - -6. **De-assert TS_EN** -- P3.5 HIGH. - -7. **Trigger continuous GPIF read** -- GPIFTRIG = 0x04 (read direction, EP2 select). - -8. **Set streaming indicator** -- P0.7 LOW. - - -```asm title="Start Streaming (Rev.2 at CODE:0D84)" -ORL 0x4e, #0x80 ; config_byte |= 0x80 (streaming flag) -MOV DPTR, #0xE630 ; GPIFTCB3 -MOV A, #0x80 -MOVX @DPTR, A ; GPIFTCB3 = 0x80 (huge transaction count) -; ... address/FIFO reset ... -ANL 0xb0, #0xDF ; P3 &= 0xDF -> P3.5 = 0 (TS_EN assert) -; Poll GPIFTRIG.DONE ... -ORL 0xb0, #0x20 ; P3 |= 0x20 -> P3.5 = 1 -MOV 0xbb, #0x04 ; GPIFTRIG = 0x04 (read EP2) -ANL 0x80, #0x7F ; P0 &= 0x7F -> P0.7 = 0 (streaming) -``` - - - - -When `wValue=0` and currently streaming (config_byte bit 7 set): - - -1. **Set stopped indicator** -- P0.7 HIGH. - -2. **Force-flush buffer** -- EP2FIFOBCH = 0xFF (skip current FIFO packet). - -3. **Wait for GPIF idle** -- Poll GPIFTRIG bit 7 (DONE) until set. - -4. **Discard partial packet** -- OUTPKTEND = 0x82 (skip bit set, EP2 select). - -5. **Clear streaming flag** -- config_byte bit 7 = 0. - -6. **De-assert control lines** -- P3 bits 7:5 = 1 (all BCM4500 controls idle). - - -```asm title="Stop Streaming (Rev.2 at CODE:0DE1)" -ORL 0x80, #0x80 ; P0 |= 0x80 -> P0.7 = 1 (stopped) -MOV DPTR, #0xE6F5 ; EP2FIFOBCH -MOV A, #0xFF -MOVX @DPTR, A ; Force flush -; Poll GPIFTRIG.DONE ... -MOV DPTR, #0xE648 ; OUTPKTEND -MOV A, #0x82 ; Skip=1, EP2 -MOVX @DPTR, A ; Discard partial packet -ANL 0x4e, #0x7F ; config_byte &= 0x7F (clear streaming) -ORL 0xb0, #0xE0 ; P3 |= 0xE0 (de-assert all controls) -``` - - - - -## Interrupt Handling - -INT4 and INT6 (GPIF/FIFO events) share a common handler that sets a software flag and clears EXIF.4: - -```asm title="GPIF Interrupt Handler (Rev.2 at CODE:2084)" -PUSH A -PUSH DPH -PUSH DPL -SETB 0x01 ; Set GPIF event flag (_0_1) -ANL 0x91, #0xEF ; Clear EXIF.4 (INT4/INT6 IRQ flag) -MOV DPTR, #0xE65D ; GPIFIRQ -MOV A, #0x01 -MOVX @DPTR, A ; Clear GPIFIRQ bit 0 -POP DPL -POP DPH -POP A -RETI -``` - -The main loop polls this flag, enters CPU idle mode (PCON.0) between events, and checks EP2CS for buffer availability before re-arming the GPIF. The FLOWSTATE engine (FSEN=1) automatically re-triggers GPIF transactions when EP2 buffers become available. - -## GPIF Interrupt Enable (GPIFIE) - -| Bit | Name | Enabled | Purpose | -|-----|------|---------|---------| -| 0 | GPIFWF | Yes | Waveform completion interrupt | -| 1 | (reserved) | No | -- | -| 2 | GPIFTCEXP | Yes | Transaction count expired | -| 3 | GPIFGPIFDONE | Yes | GPIF operation done | -| 4 | GPIFFF | Yes | FIFO flag interrupt | -| 5 | GPIFWF2 | Yes | Waveform 2 completion | - -## Throughput Analysis - -| Metric | Value | -|--------|-------| -| USB 2.0 HS bulk (theoretical) | 480 Mbps | -| USB 2.0 HS bulk (practical) | ~280 Mbps (~35 MB/s) | -| GPIF engine (theoretical) | 48 MHz x 8 bits = 384 Mbps | -| Typical DVB-S TS rate | 1--5 MB/s | -| Maximum DVB-S2 rate (hypothetical) | ~7.25 MB/s (58 Mbps) | - -The USB/GPIF path has approximately 5x headroom even at the maximum theoretical data rate. The bottleneck for all supported modes is the satellite link, not the USB data path. - -## Timing - -| Parameter | Value | -|-----------|-------| -| GPIF clock | 48 MHz internal | -| CPU clock | 48 MHz | -| GPIF mode | Asynchronous (RDY pin handshaking) | -| NOP delays | 3 NOPs between XRAM writes (~62.5 ns at 48 MHz) | -| EP2 buffer commit | Automatic via AUTOIN on FIFO fullness | -| GPIF re-trigger | Automatic via FLOWSTATE when EP2 buffer space available | - -## Cross-Version Comparison - -The GPIF streaming path is functionally identical across all three firmware versions. Only code addresses differ due to recompilation: - -| Aspect | v2.06 | v2.13 FW1 | Rev.2 v2.10 | -|--------|-------|-----------|-------------| -| ARM_TRANSFER handler | CODE:0110 | CODE:0110 | CODE:00FA | -| GPIF control function | CODE:1919 | CODE:1800 | CODE:0D7C | -| Config byte IRAM | 0x6D | 0x4F | 0x4E | -| EP2FIFOCFG value | 0x0C | 0x0C | 0x0C | -| IFCONFIG value | 0xEE | 0xEE | 0xEE | -| FLOWSTATEA setting | OR 0x09 | OR 0x09 | OR 0x09 | +--- +title: GPIF Streaming +description: GPIF engine configuration, waveform descriptors, and transport stream data path from BCM4500 to USB host. +--- + +import { Tabs, TabItem, Badge, Aside, Steps } from '@astrojs/starlight/components'; + +The GPIF (General Programmable Interface) engine in the Cypress FX2 provides a hardware-managed data path from the BCM4500 demodulator to the USB host. After initial setup, no firmware intervention occurs in the data path -- the GPIF engine reads transport stream data directly into the EP2 FIFO, and the AUTOIN mechanism automatically commits full packets to the USB controller. + +## Data Flow Architecture + +``` + Cypress FX2 (CY7C68013A) + +-----------------------------+ + | | +BCM4500 P3.5 TS_EN | GPIF Engine EP2 FIFO | USB 2.0 HS +Demodulator <-----------------+ (Master Read) (AUTOIN) +------------> Host + (I2C:0x08) GPIF Data Bus | 0xE4xx wfm 4x buf | EP2 (0x82) + -------------------> CTL/RDY pins 8-bit | Bulk IN + 8-bit parallel TS | | 7 URBs x 8KB + +-----------------------------+ +``` + + + +## Key Register Configuration + +All values are identical across the three stock firmware versions (v2.06, Rev.2 v2.10, v2.13): + +| Register | Address | Value | Function | +|----------|---------|-------|----------| +| IFCONFIG | 0xE601 | 0xEE | Internal 48 MHz clock, GPIF master, async, debug output | +| EP2FIFOCFG | 0xE618 | 0x0C | AUTOIN=1, ZEROLENIN=1, 8-bit data path | +| REVCTL | 0xE60B | 0x03 | NOAUTOARM + SKIPCOMMIT | +| CPUCS | 0xE600 | bits [4:3]=10 | 48 MHz CPU clock | +| FLOWSTATEA | 0xE668 | OR 0x09 | FSEN (flow state enable) + FS[3] | +| GPIFIE | 0xE65C | OR 0x3D | Waveform, TC, DONE, FIFO flag, WF2 interrupts | + +### IFCONFIG Decode (0xEE = 1110_1110) + +| Bit | Name | Value | Meaning | +|-----|------|-------|---------| +| 7 | IFCLKSRC | 1 | Internal clock source | +| 6 | 3048MHZ | 1 | 48 MHz IFCLK frequency | +| 5 | IFCLKOE | 1 | IFCLK pin drives output (clock to BCM4500) | +| 4 | IFCLKPOL | 0 | Non-inverted clock polarity | +| 3 | ASYNC | 1 | Asynchronous GPIF (RDY pin handshaking) | +| 2 | GSTATE | 1 | Debug state output on PORTE | +| 1:0 | IFCFG | 10 | GPIF internal master mode | + +The FX2 operates as a GPIF master, reading data from the BCM4500's parallel transport stream output. Asynchronous mode means the GPIF uses RDY pin handshaking rather than clock-edge sampling. + +### EP2FIFOCFG Decode (0x0C = 0000_1100) + +| Bit | Name | Value | Meaning | +|-----|------|-------|---------| +| 4 | INFM1 | 0 | Packet count not decremented | +| 3 | AUTOIN | 1 | Auto-commit IN packets when FIFO buffer full | +| 2 | ZEROLENIN | 1 | Allow zero-length IN packets | +| 1 | (reserved) | 0 | -- | +| 0 | WORDWIDE | 0 | 8-bit data path (not 16-bit) | + +The AUTOIN bit is critical: when the GPIF fills an EP2 FIFO buffer to the configured packet size, the FX2 hardware automatically arms the buffer for USB transfer. + +## GPIF Waveform Configuration + +The GPIF waveform descriptors occupy 128 bytes at 0xE400-0xE47F and are loaded from a compressed init table during firmware startup. The waveform programs a straightforward read cycle: + +``` +States 0-5: CTL outputs = 0x01 (control line asserted), 1 IFCLK each +State 6: CTL = 0x07, length = 0 (idle/terminate) +Opcode: 0x00 (SDP = sample data point) +Output: 0xF0 (FIFO write flags) +``` + +This encodes a simple "assert read strobe, capture data, de-assert" cycle that reads one byte per GPIF transaction from the BCM4500's parallel port into the EP2 FIFO. + +## FIFO Reset Sequence + +All endpoint FIFOs are reset during initialization using the Cypress-prescribed procedure: + +```c title="FIFO Reset (FUN_CODE_10d9)" +FIFORESET = 0x80; // NAKALL: NAK all host transfers during reset +// 3-NOP SYNCDELAY +FIFORESET = 0x02; // Reset EP2 FIFO +// 3-NOP SYNCDELAY +FIFORESET = 0x04; // Reset EP4 FIFO +// 3-NOP SYNCDELAY +FIFORESET = 0x06; // Reset EP6 FIFO +// 3-NOP SYNCDELAY +FIFORESET = 0x08; // Reset EP8 FIFO +// 3-NOP SYNCDELAY +FIFORESET = 0x00; // Release NAKALL +``` + +The triple-NOP delays (mandatory SYNCDELAY) between writes are required by the FX2 architecture: XRAM register writes take 2 cycles to propagate, and back-to-back writes to the same register need at least 3 instruction cycles. + +## ARM_TRANSFER Command (0x85) + +The host issues vendor command 0x85 to start or stop the MPEG-2 transport stream. The handler checks the [configuration status byte](/usb/config-status/) bit 0 (demodulator active) before proceeding. + + + + +When `wValue=1` and the demodulator is active: + + +1. **Set streaming flag** -- config_byte bit 7 = 1 (bmArmed). + +2. **Load GPIF transaction count** -- GPIFTCB3:2 = 0x8000 (2 GB, effectively infinite). + +3. **Reset address and byte count** -- Clear GPIF address registers and EP2 FIFO byte count. + +4. **Assert TS_EN** -- P3.5 LOW (BCM4500 transport stream output enabled). + +5. **Wait for initial GPIF transaction** -- Poll GPIFTRIG bit 7 (DONE) until set. + +6. **De-assert TS_EN** -- P3.5 HIGH. + +7. **Trigger continuous GPIF read** -- GPIFTRIG = 0x04 (read direction, EP2 select). + +8. **Set streaming indicator** -- P0.7 LOW. + + +```asm title="Start Streaming (Rev.2 at CODE:0D84)" +ORL 0x4e, #0x80 ; config_byte |= 0x80 (streaming flag) +MOV DPTR, #0xE630 ; GPIFTCB3 +MOV A, #0x80 +MOVX @DPTR, A ; GPIFTCB3 = 0x80 (huge transaction count) +; ... address/FIFO reset ... +ANL 0xb0, #0xDF ; P3 &= 0xDF -> P3.5 = 0 (TS_EN assert) +; Poll GPIFTRIG.DONE ... +ORL 0xb0, #0x20 ; P3 |= 0x20 -> P3.5 = 1 +MOV 0xbb, #0x04 ; GPIFTRIG = 0x04 (read EP2) +ANL 0x80, #0x7F ; P0 &= 0x7F -> P0.7 = 0 (streaming) +``` + + + + +When `wValue=0` and currently streaming (config_byte bit 7 set): + + +1. **Set stopped indicator** -- P0.7 HIGH. + +2. **Force-flush buffer** -- EP2FIFOBCH = 0xFF (skip current FIFO packet). + +3. **Wait for GPIF idle** -- Poll GPIFTRIG bit 7 (DONE) until set. + +4. **Discard partial packet** -- OUTPKTEND = 0x82 (skip bit set, EP2 select). + +5. **Clear streaming flag** -- config_byte bit 7 = 0. + +6. **De-assert control lines** -- P3 bits 7:5 = 1 (all BCM4500 controls idle). + + +```asm title="Stop Streaming (Rev.2 at CODE:0DE1)" +ORL 0x80, #0x80 ; P0 |= 0x80 -> P0.7 = 1 (stopped) +MOV DPTR, #0xE6F5 ; EP2FIFOBCH +MOV A, #0xFF +MOVX @DPTR, A ; Force flush +; Poll GPIFTRIG.DONE ... +MOV DPTR, #0xE648 ; OUTPKTEND +MOV A, #0x82 ; Skip=1, EP2 +MOVX @DPTR, A ; Discard partial packet +ANL 0x4e, #0x7F ; config_byte &= 0x7F (clear streaming) +ORL 0xb0, #0xE0 ; P3 |= 0xE0 (de-assert all controls) +``` + + + + +## Interrupt Handling + +INT4 and INT6 (GPIF/FIFO events) share a common handler that sets a software flag and clears EXIF.4: + +```asm title="GPIF Interrupt Handler (Rev.2 at CODE:2084)" +PUSH A +PUSH DPH +PUSH DPL +SETB 0x01 ; Set GPIF event flag (_0_1) +ANL 0x91, #0xEF ; Clear EXIF.4 (INT4/INT6 IRQ flag) +MOV DPTR, #0xE65D ; GPIFIRQ +MOV A, #0x01 +MOVX @DPTR, A ; Clear GPIFIRQ bit 0 +POP DPL +POP DPH +POP A +RETI +``` + +The main loop polls this flag, enters CPU idle mode (PCON.0) between events, and checks EP2CS for buffer availability before re-arming the GPIF. The FLOWSTATE engine (FSEN=1) automatically re-triggers GPIF transactions when EP2 buffers become available. + +## GPIF Interrupt Enable (GPIFIE) + +| Bit | Name | Enabled | Purpose | +|-----|------|---------|---------| +| 0 | GPIFWF | Yes | Waveform completion interrupt | +| 1 | (reserved) | No | -- | +| 2 | GPIFTCEXP | Yes | Transaction count expired | +| 3 | GPIFGPIFDONE | Yes | GPIF operation done | +| 4 | GPIFFF | Yes | FIFO flag interrupt | +| 5 | GPIFWF2 | Yes | Waveform 2 completion | + +## Throughput Analysis + +| Metric | Value | +|--------|-------| +| USB 2.0 HS bulk (theoretical) | 480 Mbps | +| USB 2.0 HS bulk (practical) | ~280 Mbps (~35 MB/s) | +| GPIF engine (theoretical) | 48 MHz x 8 bits = 384 Mbps | +| Typical DVB-S TS rate | 1--5 MB/s | +| Maximum DVB-S2 rate (hypothetical) | ~7.25 MB/s (58 Mbps) | + +The USB/GPIF path has approximately 5x headroom even at the maximum theoretical data rate. The bottleneck for all supported modes is the satellite link, not the USB data path. + +## Timing + +| Parameter | Value | +|-----------|-------| +| GPIF clock | 48 MHz internal | +| CPU clock | 48 MHz | +| GPIF mode | Asynchronous (RDY pin handshaking) | +| NOP delays | 3 NOPs between XRAM writes (~62.5 ns at 48 MHz) | +| EP2 buffer commit | Automatic via AUTOIN on FIFO fullness | +| GPIF re-trigger | Automatic via FLOWSTATE when EP2 buffer space available | + +## Cross-Version Comparison + +The GPIF streaming path is functionally identical across all three firmware versions. Only code addresses differ due to recompilation: + +| Aspect | v2.06 | v2.13 FW1 | Rev.2 v2.10 | +|--------|-------|-----------|-------------| +| ARM_TRANSFER handler | CODE:0110 | CODE:0110 | CODE:00FA | +| GPIF control function | CODE:1919 | CODE:1800 | CODE:0D7C | +| Config byte IRAM | 0x6D | 0x4F | 0x4E | +| EP2FIFOCFG value | 0x0C | 0x0C | 0x0C | +| IFCONFIG value | 0xEE | 0xEE | 0xEE | +| FLOWSTATEA setting | OR 0x09 | OR 0x09 | OR 0x09 | diff --git a/site/src/content/docs/bcm4500/register-map.mdx b/site/src/content/docs/bcm4500/register-map.mdx index f62e195..67a8e0c 100644 --- a/site/src/content/docs/bcm4500/register-map.mdx +++ b/site/src/content/docs/bcm4500/register-map.mdx @@ -1,241 +1,241 @@ ---- -title: BCM4500 Register Map -description: Definitive register lookup for BCM4500 direct registers, indirect registers, FX2 XRAM/IRAM locations, and I2C controller addresses. ---- - -import { Badge, Aside } from '@astrojs/starlight/components'; - - - -This page consolidates every known register address used by the SkyWalker-1 hardware into a single lookup reference. - -## BCM4500 Direct Registers - -These registers are accessed via standard I2C read/write to the BCM4500 at 7-bit address **0x08** (write byte 0x10, read byte 0x11). - -| Address | Name | R/W | Function | -|---------|------|-----|----------| -| 0xA2 | Status | R | Readiness status. Returns 0x02 when powered on, no signal locked. Polled during boot probe and [signal strength readback](/bcm4500/signal-monitoring/). | -| 0xA4 | Lock | R | Signal lock indicator. Bit 5 (mask 0x20) = signal locked. Read by `GET_SIGNAL_LOCK` (0x90). The kernel treats any non-zero value as locked. | -| 0xA6 | Indirect Page | W | Page/address select for the [indirect register protocol](/bcm4500/demodulator/#indirect-register-protocol). Typically written with 0x00 (page 0). | -| 0xA7 | Indirect Data | R/W | Data register for indirect reads and writes. Supports auto-increment for multi-byte writes within a single I2C transaction. | -| 0xA8 | Indirect Command | R/W | Command register. Write 0x01 = indirect read, 0x03 = indirect write. Read to poll: bit 0 clear = command complete. | -| 0xF9 | Demod Status | R | Extended demodulator status. Read by `GET_DEMOD_STATUS` (0x99) in v2.13 firmware. Also polled by the v2.13 INT0 handler. Not accessed by v2.06 or Rev.2. | - -### Indirect Register Triad - -Registers 0xA6, 0xA7, and 0xA8 form a triad that provides access to the BCM4500's internal register space. The sequence is always: select page (0xA6) → write/read data (0xA7) → execute command (0xA8) → poll 0xA8 for completion. - -See [Demodulator — Indirect Register Protocol](/bcm4500/demodulator/#indirect-register-protocol) for the full byte-level I2C sequences. - -## Indirect Registers (Page 0) - -These are the BCM4500 internal registers accessed through the 0xA6/0xA7/0xA8 indirect protocol. All known registers are on page 0x00. - -### Signal Quality - -| Register | Size | Function | Read By | -|----------|------|----------|---------| -| 0x00--0x01 | 2 bytes | SNR value (16-bit, little-endian). Bytes 0--1 of `GET_SIGNAL_STRENGTH` (0x87) response. | [Signal Monitoring](/bcm4500/signal-monitoring/) | -| 0x02--0x05 | 4 bytes | AGC and diagnostic data. Bytes 2--5 of `GET_SIGNAL_STRENGTH` (0x87) response. | [Signal Monitoring](/bcm4500/signal-monitoring/) | - -### Initialization Blocks - -Three blocks written during BCM4500 firmware load. The first byte of each block is the starting register address. - -| Block | Start Reg | Length | Data (hex) | Purpose | -|-------|-----------|--------|------------|---------| -| 0 | 0x06 | 7 bytes | `06 0b 17 38 9f d9 80` | Primary demod configuration | -| 1 | 0x07 | 8 bytes | `07 09 39 4f 00 65 b7 10` | Secondary demod configuration | -| 2 | 0x0F | 3 bytes | `0f 0c 09` | Final demod configuration | - - - -### Tuning Configuration - -After modulation dispatch, the [tuning protocol](/bcm4500/tuning-protocol/) writes frequency, symbol rate, FEC, and modulation parameters into BCM4500 page 0 registers via the indirect write protocol. The exact target register range depends on the configuration data length (typically 12--18 bytes starting at register 0x00). - -## FX2 XRAM Locations - -External RAM (XRAM) addresses used by the FX2 microcontroller for tuning state and configuration. These are **not** BCM4500 registers — they are FX2 memory locations that hold data destined for or read from the demodulator. - -### Modulation Configuration - -| Address | Name | Function | -|---------|------|----------| -| 0xE0EB | FEC Code Rate | Looked up from the FEC table for the active modulation. DCII modes use fixed value 0xFC. DSS/BPSK modes OR the lookup with 0x80. | -| 0xE0EC | Modulation Type | 0x09 for DVB-S, Turbo, DSS, and BPSK modes. DCII modes load from the DCII lookup table. | -| 0xE0F5 | Demod Mode | 0x10 for most modes. DCII variants use 0x10 (combo), 0x11 (offset QPSK), 0x12 (I-stream), or 0x16 (Q-stream). | -| 0xE0F6 | Turbo Flag | 0x00 = standard FEC. 0x01 = turbo FEC (QPSK/8PSK/16QAM turbo modes). | - -### Tuning Parameters - -| Address | Size | Content | Source | -|---------|------|---------|--------| -| 0xE0CB--0xE0CE | 4 bytes | Symbol rate (big-endian) | Byte-reversed from EP0BUF[0--3] during `TUNE_8PSK` | -| 0xE0DB--0xE0DE | 4 bytes | IF frequency (big-endian) | Byte-reversed from EP0BUF[4--7] during `TUNE_8PSK` | - -### FEC Rate Lookup Tables - -Populated at boot from CODE-space initialization tables. Indexed by the FEC rate byte from the `TUNE_8PSK` command payload. - -| Base Address | Modulation | Max Index | Rates | -|-------------|-----------|-----------|-------| -| 0xE0B1 | Turbo 8PSK | 5 | Turbo-specific code rates | -| 0xE0B7 | Turbo QPSK | 5 | Turbo-specific code rates | -| 0xE0BC | Turbo 16QAM | 1 | Single code rate | -| 0xE0BD | DCII (all variants) | 9 | Combined code + modulation values | -| 0xE0F9 | DVB-S QPSK / DSS / BPSK | 7 | 1/2, 2/3, 3/4, 5/6, 7/8, auto, none | - -### FW2/FW3 External Configuration - -| Address | Size | Content | -|---------|------|---------| -| 0xE080--0xE08E | 15 bytes | External calibration data loaded by [FW2 and FW3](/firmware/fw213-variants/) into demod registers at 0xE6C0--0xE6CD. Not used by FW1 (hardcoded config). | - -### USB Endpoint Buffer - -| Address | Size | Content | -|---------|------|---------| -| 0xE740--0xE749 | 10 bytes | EP0BUF — USB control transfer buffer. Contains the raw `TUNE_8PSK` payload before parsing. | - -## FX2 IRAM by Firmware Version - -Internal RAM (IRAM) addresses vary between firmware versions due to different stack pointer placement. This table maps the key locations for each version. - -### Stack and Status - -| Location | v2.06 | Rev.2 v2.10 | v2.13 FW1/FW2 | v2.13 FW3 | -|----------|-------|-------------|---------------|-----------| -| Stack pointer (SP) | 0x72 | 0x4F | 0x50 | 0x52 | -| Config status byte | 0x6D | 0x4E | 0x4F | 0x51 | -| I2C buffer high | -- | 0x48 | 0x48 | 0x4A | -| I2C buffer low | -- | 0x49 | 0x49 | 0x4B | - -The config status byte is returned by `GET_8PSK_CONFIG` (0x80). See [Config Status](/usb/config-status/) for bit definitions. - -### Tuning Parameter Storage - -| Location | Address | Function | -|----------|---------|----------| -| Modulation type | 0x4D | Copied from EP0BUF[8] during `TUNE_8PSK` parsing | -| FEC rate index | 0x4F | Copied from EP0BUF[9] during `TUNE_8PSK` parsing | - - - -### Custom Firmware (v3.01+) - -The custom firmware built with SDCC + fx2lib uses C variables instead of fixed IRAM addresses. The compiler manages allocation, so addresses are not guaranteed stable across builds. Key variables: - -| Variable | Type | Purpose | -|----------|------|---------| -| `config_status` | `volatile BYTE` | Configuration status byte | -| `boot_stage` | `volatile BYTE` | Boot progress (0x00 = not started, 0xFF = complete) | -| `i2c_buf[16]` | `__xdata BYTE` | I2C scratch buffer for writes | -| `i2c_rd[8]` | `__xdata BYTE` | I2C scratch buffer for reads | -| `tm_result[10]` | `__xdata BYTE` | Tune monitor result buffer | - -## FX2 USB Controller Registers - -Core FX2LP registers used for USB control transfers, CPU management, and peripheral configuration. - -### CPU and Peripheral Config - -| Register | Address | Function | -|----------|---------|----------| -| CPUCS | 0xE600 | CPU control/status. Write 0x01 to halt, 0x00 to run. Bits 4:3 select clock speed (10 = 48 MHz). | -| IFCONFIG | 0xE601 | Interface configuration. Value 0xEE = internal 48 MHz clock, GPIF master, async mode. | -| REVCTL | 0xE60B | Revision control. Value 0x03 = NOAUTOARM + SKIPCOMMIT (required for manual EP management). | -| EP2FIFOCFG | 0xE618 | EP2 FIFO configuration. Value 0x0C = AUTOIN + ZEROLENIN, 8-bit data bus. | - -### USB Setup Data (SETUPDAT) - -Populated by the FX2 hardware when a SETUP packet arrives on EP0. The vendor command dispatcher reads `SETUPDAT[1]` to determine the command. - -| Register | Address | Content | -|----------|---------|---------| -| SETUPDAT[0] | 0xE6B8 | bmRequestType (0x40 = vendor OUT, 0xC0 = vendor IN) | -| SETUPDAT[1] | 0xE6B9 | bRequest (vendor command ID: 0x80--0xB9) | -| SETUPDAT[2] | 0xE6BA | wValueL | -| SETUPDAT[3] | 0xE6BB | wValueH | -| SETUPDAT[4] | 0xE6BC | wIndexL | -| SETUPDAT[5] | 0xE6BD | wIndexH | -| SETUPDAT[6] | 0xE6BE | wLengthL | -| SETUPDAT[7] | 0xE6BF | wLengthH | - -### EP0 Buffer - -| Register | Address | Function | -|----------|---------|----------| -| EP0BCH | 0xE68A | EP0 byte count high. | -| EP0BCL | 0xE68B | EP0 byte count low. Writing this register arms the EP0 IN transfer. | -| EP0BUF | 0xE740 | EP0 data buffer start (64 bytes). Contains [TUNE_8PSK](/bcm4500/tuning-protocol/) payload bytes 0xE740--0xE749. | - -### LNB Control (XRAM) - -| Address | Function | -|---------|----------| -| 0xE0B6 | LNB voltage control register. Written by `SET_LNB_VOLTAGE` (0x8B) in the custom firmware. | - -## FX2 I2C Controller - -The Cypress FX2LP's built-in I2C master controller uses three hardware registers in the SFR-mapped XRAM space. - -| Register | Address | Function | -|----------|---------|----------| -| I2CS | 0xE678 | Control/Status. Bit fields: DONE (bit 0), ACK (bit 1), BERR (bit 2), ID (bits 4:3), LASTRD (bit 5), STOP (bit 6), START (bit 7). | -| I2DAT | 0xE679 | Data register. Write to transmit a byte, read to receive. First write after START sends the slave address byte. | -| I2CTL | 0xE67A | Control register. Bit 0 = 400 kHz mode (set by all firmware versions at init). Bit 1 = STOPIE (stop interrupt enable). | - -### I2C Bus Addresses - -| 7-bit Address | 8-bit Write | 8-bit Read | Device | -|--------------|-------------|------------|--------| -| 0x08 | 0x10 | 0x11 | BCM4500 demodulator (operating address) | -| 0x10 | 0x20 | 0x21 | Tuner / LNB controller | -| 0x51 | 0xA2 | 0xA3 | Configuration EEPROM (serial number, calibration, firmware storage) | -| 0x3F | 0x7E | 0x7F | BCM4500 alternate probe address (v2.13 boot detection only) | -| 0x7F | 0xFE | 0xFF | BCM4500 alternate probe address (v2.13 boot detection only) | - -See [I2C Bus Architecture](/i2c/bus-architecture/) for bus topology and the [STOP Corruption Bug](/i2c/stop-corruption-bug/) for the spurious STOP issue affecting the FX2 controller. - -## Cross-Reference Index - -Every documentation page that references specific registers or memory addresses: - -### BCM4500 Direct Registers - -| Register | Pages | -|----------|-------| -| 0xA2 (Status) | [Demodulator](/bcm4500/demodulator/), [Signal Monitoring](/bcm4500/signal-monitoring/), [Version Comparison](/firmware/version-comparison/) | -| 0xA4 (Lock) | [Demodulator](/bcm4500/demodulator/), [Signal Monitoring](/bcm4500/signal-monitoring/), [Tuning Protocol](/bcm4500/tuning-protocol/) | -| 0xA6/0xA7/0xA8 (Indirect) | [Demodulator](/bcm4500/demodulator/), [Tuning Protocol](/bcm4500/tuning-protocol/), [Signal Monitoring](/bcm4500/signal-monitoring/) | -| 0xF9 (Demod Status) | [Demodulator](/bcm4500/demodulator/), [Signal Monitoring](/bcm4500/signal-monitoring/), [Version Comparison](/firmware/version-comparison/) | - -### FX2 XRAM - -| Address Range | Pages | -|--------------|-------| -| 0xE0B1--0xE0F9 (FEC tables) | [Tuning Protocol](/bcm4500/tuning-protocol/) | -| 0xE0CB--0xE0DE (Tune params) | [Tuning Protocol](/bcm4500/tuning-protocol/) | -| 0xE0EB--0xE0F6 (Mod config) | [Tuning Protocol](/bcm4500/tuning-protocol/) | -| 0xE080--0xE08E (FW2/FW3 cal) | [FW2.13 Variants](/firmware/fw213-variants/) | - -### FX2 IRAM - -| Address | Pages | -|---------|-------| -| Config status (version-dependent) | [Config Status](/usb/config-status/), [Version Comparison](/firmware/version-comparison/), [FW2.13 Variants](/firmware/fw213-variants/) | -| SP, I2C buffers | [FW2.13 Variants](/firmware/fw213-variants/), [Version Comparison](/firmware/version-comparison/) | - -### I2C Controller - -| Register | Pages | -|----------|-------| -| I2CS/I2DAT/I2CTL (0xE678--0xE67A) | [I2C Bus Architecture](/i2c/bus-architecture/), [STOP Corruption Bug](/i2c/stop-corruption-bug/), [Demodulator](/bcm4500/demodulator/) | +--- +title: BCM4500 Register Map +description: Definitive register lookup for BCM4500 direct registers, indirect registers, FX2 XRAM/IRAM locations, and I2C controller addresses. +--- + +import { Badge, Aside } from '@astrojs/starlight/components'; + + + +This page consolidates every known register address used by the SkyWalker-1 hardware into a single lookup reference. + +## BCM4500 Direct Registers + +These registers are accessed via standard I2C read/write to the BCM4500 at 7-bit address **0x08** (write byte 0x10, read byte 0x11). + +| Address | Name | R/W | Function | +|---------|------|-----|----------| +| 0xA2 | Status | R | Readiness status. Returns 0x02 when powered on, no signal locked. Polled during boot probe and [signal strength readback](/bcm4500/signal-monitoring/). | +| 0xA4 | Lock | R | Signal lock indicator. Bit 5 (mask 0x20) = signal locked. Read by `GET_SIGNAL_LOCK` (0x90). The kernel treats any non-zero value as locked. | +| 0xA6 | Indirect Page | W | Page/address select for the [indirect register protocol](/bcm4500/demodulator/#indirect-register-protocol). Typically written with 0x00 (page 0). | +| 0xA7 | Indirect Data | R/W | Data register for indirect reads and writes. Supports auto-increment for multi-byte writes within a single I2C transaction. | +| 0xA8 | Indirect Command | R/W | Command register. Write 0x01 = indirect read, 0x03 = indirect write. Read to poll: bit 0 clear = command complete. | +| 0xF9 | Demod Status | R | Extended demodulator status. Read by `GET_DEMOD_STATUS` (0x99) in v2.13 firmware. Also polled by the v2.13 INT0 handler. Not accessed by v2.06 or Rev.2. | + +### Indirect Register Triad + +Registers 0xA6, 0xA7, and 0xA8 form a triad that provides access to the BCM4500's internal register space. The sequence is always: select page (0xA6) → write/read data (0xA7) → execute command (0xA8) → poll 0xA8 for completion. + +See [Demodulator — Indirect Register Protocol](/bcm4500/demodulator/#indirect-register-protocol) for the full byte-level I2C sequences. + +## Indirect Registers (Page 0) + +These are the BCM4500 internal registers accessed through the 0xA6/0xA7/0xA8 indirect protocol. All known registers are on page 0x00. + +### Signal Quality + +| Register | Size | Function | Read By | +|----------|------|----------|---------| +| 0x00--0x01 | 2 bytes | SNR value (16-bit, little-endian). Bytes 0--1 of `GET_SIGNAL_STRENGTH` (0x87) response. | [Signal Monitoring](/bcm4500/signal-monitoring/) | +| 0x02--0x05 | 4 bytes | AGC and diagnostic data. Bytes 2--5 of `GET_SIGNAL_STRENGTH` (0x87) response. | [Signal Monitoring](/bcm4500/signal-monitoring/) | + +### Initialization Blocks + +Three blocks written during BCM4500 firmware load. The first byte of each block is the starting register address. + +| Block | Start Reg | Length | Data (hex) | Purpose | +|-------|-----------|--------|------------|---------| +| 0 | 0x06 | 7 bytes | `06 0b 17 38 9f d9 80` | Primary demod configuration | +| 1 | 0x07 | 8 bytes | `07 09 39 4f 00 65 b7 10` | Secondary demod configuration | +| 2 | 0x0F | 3 bytes | `0f 0c 09` | Final demod configuration | + + + +### Tuning Configuration + +After modulation dispatch, the [tuning protocol](/bcm4500/tuning-protocol/) writes frequency, symbol rate, FEC, and modulation parameters into BCM4500 page 0 registers via the indirect write protocol. The exact target register range depends on the configuration data length (typically 12--18 bytes starting at register 0x00). + +## FX2 XRAM Locations + +External RAM (XRAM) addresses used by the FX2 microcontroller for tuning state and configuration. These are **not** BCM4500 registers — they are FX2 memory locations that hold data destined for or read from the demodulator. + +### Modulation Configuration + +| Address | Name | Function | +|---------|------|----------| +| 0xE0EB | FEC Code Rate | Looked up from the FEC table for the active modulation. DCII modes use fixed value 0xFC. DSS/BPSK modes OR the lookup with 0x80. | +| 0xE0EC | Modulation Type | 0x09 for DVB-S, Turbo, DSS, and BPSK modes. DCII modes load from the DCII lookup table. | +| 0xE0F5 | Demod Mode | 0x10 for most modes. DCII variants use 0x10 (combo), 0x11 (offset QPSK), 0x12 (I-stream), or 0x16 (Q-stream). | +| 0xE0F6 | Turbo Flag | 0x00 = standard FEC. 0x01 = turbo FEC (QPSK/8PSK/16QAM turbo modes). | + +### Tuning Parameters + +| Address | Size | Content | Source | +|---------|------|---------|--------| +| 0xE0CB--0xE0CE | 4 bytes | Symbol rate (big-endian) | Byte-reversed from EP0BUF[0--3] during `TUNE_8PSK` | +| 0xE0DB--0xE0DE | 4 bytes | IF frequency (big-endian) | Byte-reversed from EP0BUF[4--7] during `TUNE_8PSK` | + +### FEC Rate Lookup Tables + +Populated at boot from CODE-space initialization tables. Indexed by the FEC rate byte from the `TUNE_8PSK` command payload. + +| Base Address | Modulation | Max Index | Rates | +|-------------|-----------|-----------|-------| +| 0xE0B1 | Turbo 8PSK | 5 | Turbo-specific code rates | +| 0xE0B7 | Turbo QPSK | 5 | Turbo-specific code rates | +| 0xE0BC | Turbo 16QAM | 1 | Single code rate | +| 0xE0BD | DCII (all variants) | 9 | Combined code + modulation values | +| 0xE0F9 | DVB-S QPSK / DSS / BPSK | 7 | 1/2, 2/3, 3/4, 5/6, 7/8, auto, none | + +### FW2/FW3 External Configuration + +| Address | Size | Content | +|---------|------|---------| +| 0xE080--0xE08E | 15 bytes | External calibration data loaded by [FW2 and FW3](/firmware/fw213-variants/) into demod registers at 0xE6C0--0xE6CD. Not used by FW1 (hardcoded config). | + +### USB Endpoint Buffer + +| Address | Size | Content | +|---------|------|---------| +| 0xE740--0xE749 | 10 bytes | EP0BUF — USB control transfer buffer. Contains the raw `TUNE_8PSK` payload before parsing. | + +## FX2 IRAM by Firmware Version + +Internal RAM (IRAM) addresses vary between firmware versions due to different stack pointer placement. This table maps the key locations for each version. + +### Stack and Status + +| Location | v2.06 | Rev.2 v2.10 | v2.13 FW1/FW2 | v2.13 FW3 | +|----------|-------|-------------|---------------|-----------| +| Stack pointer (SP) | 0x72 | 0x4F | 0x50 | 0x52 | +| Config status byte | 0x6D | 0x4E | 0x4F | 0x51 | +| I2C buffer high | -- | 0x48 | 0x48 | 0x4A | +| I2C buffer low | -- | 0x49 | 0x49 | 0x4B | + +The config status byte is returned by `GET_8PSK_CONFIG` (0x80). See [Config Status](/usb/config-status/) for bit definitions. + +### Tuning Parameter Storage + +| Location | Address | Function | +|----------|---------|----------| +| Modulation type | 0x4D | Copied from EP0BUF[8] during `TUNE_8PSK` parsing | +| FEC rate index | 0x4F | Copied from EP0BUF[9] during `TUNE_8PSK` parsing | + + + +### Custom Firmware (v3.01+) + +The custom firmware built with SDCC + fx2lib uses C variables instead of fixed IRAM addresses. The compiler manages allocation, so addresses are not guaranteed stable across builds. Key variables: + +| Variable | Type | Purpose | +|----------|------|---------| +| `config_status` | `volatile BYTE` | Configuration status byte | +| `boot_stage` | `volatile BYTE` | Boot progress (0x00 = not started, 0xFF = complete) | +| `i2c_buf[16]` | `__xdata BYTE` | I2C scratch buffer for writes | +| `i2c_rd[8]` | `__xdata BYTE` | I2C scratch buffer for reads | +| `tm_result[10]` | `__xdata BYTE` | Tune monitor result buffer | + +## FX2 USB Controller Registers + +Core FX2LP registers used for USB control transfers, CPU management, and peripheral configuration. + +### CPU and Peripheral Config + +| Register | Address | Function | +|----------|---------|----------| +| CPUCS | 0xE600 | CPU control/status. Write 0x01 to halt, 0x00 to run. Bits 4:3 select clock speed (10 = 48 MHz). | +| IFCONFIG | 0xE601 | Interface configuration. Value 0xEE = internal 48 MHz clock, GPIF master, async mode. | +| REVCTL | 0xE60B | Revision control. Value 0x03 = NOAUTOARM + SKIPCOMMIT (required for manual EP management). | +| EP2FIFOCFG | 0xE618 | EP2 FIFO configuration. Value 0x0C = AUTOIN + ZEROLENIN, 8-bit data bus. | + +### USB Setup Data (SETUPDAT) + +Populated by the FX2 hardware when a SETUP packet arrives on EP0. The vendor command dispatcher reads `SETUPDAT[1]` to determine the command. + +| Register | Address | Content | +|----------|---------|---------| +| SETUPDAT[0] | 0xE6B8 | bmRequestType (0x40 = vendor OUT, 0xC0 = vendor IN) | +| SETUPDAT[1] | 0xE6B9 | bRequest (vendor command ID: 0x80--0xB9) | +| SETUPDAT[2] | 0xE6BA | wValueL | +| SETUPDAT[3] | 0xE6BB | wValueH | +| SETUPDAT[4] | 0xE6BC | wIndexL | +| SETUPDAT[5] | 0xE6BD | wIndexH | +| SETUPDAT[6] | 0xE6BE | wLengthL | +| SETUPDAT[7] | 0xE6BF | wLengthH | + +### EP0 Buffer + +| Register | Address | Function | +|----------|---------|----------| +| EP0BCH | 0xE68A | EP0 byte count high. | +| EP0BCL | 0xE68B | EP0 byte count low. Writing this register arms the EP0 IN transfer. | +| EP0BUF | 0xE740 | EP0 data buffer start (64 bytes). Contains [TUNE_8PSK](/bcm4500/tuning-protocol/) payload bytes 0xE740--0xE749. | + +### LNB Control (XRAM) + +| Address | Function | +|---------|----------| +| 0xE0B6 | LNB voltage control register. Written by `SET_LNB_VOLTAGE` (0x8B) in the custom firmware. | + +## FX2 I2C Controller + +The Cypress FX2LP's built-in I2C master controller uses three hardware registers in the SFR-mapped XRAM space. + +| Register | Address | Function | +|----------|---------|----------| +| I2CS | 0xE678 | Control/Status. Bit fields: DONE (bit 0), ACK (bit 1), BERR (bit 2), ID (bits 4:3), LASTRD (bit 5), STOP (bit 6), START (bit 7). | +| I2DAT | 0xE679 | Data register. Write to transmit a byte, read to receive. First write after START sends the slave address byte. | +| I2CTL | 0xE67A | Control register. Bit 0 = 400 kHz mode (set by all firmware versions at init). Bit 1 = STOPIE (stop interrupt enable). | + +### I2C Bus Addresses + +| 7-bit Address | 8-bit Write | 8-bit Read | Device | +|--------------|-------------|------------|--------| +| 0x08 | 0x10 | 0x11 | BCM4500 demodulator (operating address) | +| 0x10 | 0x20 | 0x21 | Tuner / LNB controller | +| 0x51 | 0xA2 | 0xA3 | Configuration EEPROM (serial number, calibration, firmware storage) | +| 0x3F | 0x7E | 0x7F | BCM4500 alternate probe address (v2.13 boot detection only) | +| 0x7F | 0xFE | 0xFF | BCM4500 alternate probe address (v2.13 boot detection only) | + +See [I2C Bus Architecture](/i2c/bus-architecture/) for bus topology and the [STOP Corruption Bug](/i2c/stop-corruption-bug/) for the spurious STOP issue affecting the FX2 controller. + +## Cross-Reference Index + +Every documentation page that references specific registers or memory addresses: + +### BCM4500 Direct Registers + +| Register | Pages | +|----------|-------| +| 0xA2 (Status) | [Demodulator](/bcm4500/demodulator/), [Signal Monitoring](/bcm4500/signal-monitoring/), [Version Comparison](/firmware/version-comparison/) | +| 0xA4 (Lock) | [Demodulator](/bcm4500/demodulator/), [Signal Monitoring](/bcm4500/signal-monitoring/), [Tuning Protocol](/bcm4500/tuning-protocol/) | +| 0xA6/0xA7/0xA8 (Indirect) | [Demodulator](/bcm4500/demodulator/), [Tuning Protocol](/bcm4500/tuning-protocol/), [Signal Monitoring](/bcm4500/signal-monitoring/) | +| 0xF9 (Demod Status) | [Demodulator](/bcm4500/demodulator/), [Signal Monitoring](/bcm4500/signal-monitoring/), [Version Comparison](/firmware/version-comparison/) | + +### FX2 XRAM + +| Address Range | Pages | +|--------------|-------| +| 0xE0B1--0xE0F9 (FEC tables) | [Tuning Protocol](/bcm4500/tuning-protocol/) | +| 0xE0CB--0xE0DE (Tune params) | [Tuning Protocol](/bcm4500/tuning-protocol/) | +| 0xE0EB--0xE0F6 (Mod config) | [Tuning Protocol](/bcm4500/tuning-protocol/) | +| 0xE080--0xE08E (FW2/FW3 cal) | [FW2.13 Variants](/firmware/fw213-variants/) | + +### FX2 IRAM + +| Address | Pages | +|---------|-------| +| Config status (version-dependent) | [Config Status](/usb/config-status/), [Version Comparison](/firmware/version-comparison/), [FW2.13 Variants](/firmware/fw213-variants/) | +| SP, I2C buffers | [FW2.13 Variants](/firmware/fw213-variants/), [Version Comparison](/firmware/version-comparison/) | + +### I2C Controller + +| Register | Pages | +|----------|-------| +| I2CS/I2DAT/I2CTL (0xE678--0xE67A) | [I2C Bus Architecture](/i2c/bus-architecture/), [STOP Corruption Bug](/i2c/stop-corruption-bug/), [Demodulator](/bcm4500/demodulator/) | diff --git a/site/src/content/docs/bcm4500/signal-monitoring.mdx b/site/src/content/docs/bcm4500/signal-monitoring.mdx index d079254..d904eeb 100644 --- a/site/src/content/docs/bcm4500/signal-monitoring.mdx +++ b/site/src/content/docs/bcm4500/signal-monitoring.mdx @@ -1,119 +1,119 @@ ---- -title: Signal Monitoring -description: SNR, signal strength, and lock status readback from the BCM4500 demodulator. ---- - -import { Badge, Aside } from '@astrojs/starlight/components'; - -Signal monitoring uses two vendor commands: GET_SIGNAL_LOCK (0x90) for lock status and GET_SIGNAL_STRENGTH (0x87) for SNR and diagnostic data. Both read BCM4500 direct registers via I2C. - -See the [Register Map](/bcm4500/register-map/) for a consolidated lookup of all register addresses referenced on this page. - -## Signal Lock (GET_SIGNAL_LOCK, 0x90) - -Returns 1 byte from BCM4500 direct register 0xA4. - -| Bit | Mask | Meaning | -|-----|------|---------| -| 5 | 0x20 | Signal locked | -| Other bits | -- | Additional status (undocumented) | - -The kernel driver interprets **any non-zero value** as locked and reports the full lock status: - -```c title="Kernel Lock Status Flags" -FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_SIGNAL | FE_HAS_CARRIER -``` - -### Lock States - -| Value | State | Description | -|-------|-------|-------------| -| 0x00 | | No signal or signal not acquired | -| 0x20 | | Signal locked, FEC decoding active | -| Other non-zero | | Signal acquired, partial status bits | - - - -## Signal Strength (GET_SIGNAL_STRENGTH, 0x87) - -Returns 6 bytes. The first two bytes contain a 16-bit SNR value; the remaining bytes are diagnostic register data from the BCM4500. - -### Response Format - -| Byte | Content | Notes | -|------|---------|-------| -| 0 | SNR low byte (LSB) | 16-bit little-endian | -| 1 | SNR high byte (MSB) | dBu x 256 units | -| 2 | Reserved | BCM4500 diagnostic register | -| 3 | Reserved | BCM4500 diagnostic register | -| 4 | Reserved | BCM4500 diagnostic register | -| 5 | Reserved | BCM4500 diagnostic register | - -### SNR Scaling - -The Windows BDA driver provides the scaling formula: - -```c title="SNR to Signal Strength Conversion" -uint16_t snr_raw = (buf[1] << 8) | buf[0]; - -if (snr_raw <= 0x0F00) { - signal_strength = snr_raw * 17; // Maps 0--0x0F00 to 0--65535 -} else { - signal_strength = 0xFFFF; // 100% at SNR >= 0x0F00 -} -``` - -| SNR Raw Value | Signal Strength | Quality | -|--------------|----------------|---------| -| 0x0000 | 0 (0%) | No signal | -| 0x0400 | ~27% | Poor | -| 0x0800 | ~53% | Fair | -| 0x0C00 | ~80% | Good | -| 0x0F00 | 100% | Maximum | -| > 0x0F00 | 100% (clamped) | Maximum | - -### Firmware Implementation Differences - -The signal strength readback involves I2C transactions to the BCM4500's [indirect register protocol](/bcm4500/demodulator/). The implementation varies between firmware versions: - -| Aspect | v2.06 | Rev.2 v2.10 | v2.13 | -|--------|-------|-------------|-------| -| Registers polled | 0xA2, 0xA8, 0xA4 | 0xA2, 0xA8, 0xA4 | Consolidated (1 register) | -| Max poll iterations | 6 | 6 | Simplified | -| Read-back verification | No | Yes (explicit) | No | -| Call chain | 3-register loop | 3-register + verify | Single register path | - -All versions ultimately return the same 6-byte response format to the host. - -## BCM4500 Status Registers - -These direct registers are used for signal monitoring: - -| Register | Address | Function | Access | -|----------|---------|----------|--------| -| Status | 0xA2 | BCM4500 readiness status | Polled during boot and signal checks | -| Lock | 0xA4 | Lock/ready; bit 5 = locked | Read by GET_SIGNAL_LOCK (0x90) | -| Command | 0xA8 | Indirect command status; bit 0 = busy | Polled during register operations | -| Demod Status | 0xF9 | Extended demod status | Read by GET_DEMOD_STATUS (0x99, v2.13 only) | - -### Register 0xA2 -- Status - -Read during boot probing and signal strength readback. Returns 0x02 when the BCM4500 is powered on but no signal is locked. Exact bit field definitions are not documented in the public BCM4500 datasheet. - -### Register 0xA4 -- Lock - -The primary lock indicator. The kernel driver reads this as a single byte via GET_SIGNAL_LOCK (0x90). Bit 5 (0x20) is the definitive lock status flag. - -### Register 0xA8 -- Command - -Used by the [indirect register protocol](/bcm4500/demodulator/) to track command completion. Bit 0 clear indicates the previous indirect read/write command has completed. This register is polled after every indirect register operation. - -### Register 0xF9 -- Demod Status (v2.13 only) - -Read by the GET_DEMOD_STATUS vendor command (0x99). Also polled by the v2.13 INT0 handler for demodulator availability detection. This register is not accessed by v2.06 or Rev.2 firmware. - - +--- +title: Signal Monitoring +description: SNR, signal strength, and lock status readback from the BCM4500 demodulator. +--- + +import { Badge, Aside } from '@astrojs/starlight/components'; + +Signal monitoring uses two vendor commands: GET_SIGNAL_LOCK (0x90) for lock status and GET_SIGNAL_STRENGTH (0x87) for SNR and diagnostic data. Both read BCM4500 direct registers via I2C. + +See the [Register Map](/bcm4500/register-map/) for a consolidated lookup of all register addresses referenced on this page. + +## Signal Lock (GET_SIGNAL_LOCK, 0x90) + +Returns 1 byte from BCM4500 direct register 0xA4. + +| Bit | Mask | Meaning | +|-----|------|---------| +| 5 | 0x20 | Signal locked | +| Other bits | -- | Additional status (undocumented) | + +The kernel driver interprets **any non-zero value** as locked and reports the full lock status: + +```c title="Kernel Lock Status Flags" +FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_SIGNAL | FE_HAS_CARRIER +``` + +### Lock States + +| Value | State | Description | +|-------|-------|-------------| +| 0x00 | | No signal or signal not acquired | +| 0x20 | | Signal locked, FEC decoding active | +| Other non-zero | | Signal acquired, partial status bits | + + + +## Signal Strength (GET_SIGNAL_STRENGTH, 0x87) + +Returns 6 bytes. The first two bytes contain a 16-bit SNR value; the remaining bytes are diagnostic register data from the BCM4500. + +### Response Format + +| Byte | Content | Notes | +|------|---------|-------| +| 0 | SNR low byte (LSB) | 16-bit little-endian | +| 1 | SNR high byte (MSB) | dBu x 256 units | +| 2 | Reserved | BCM4500 diagnostic register | +| 3 | Reserved | BCM4500 diagnostic register | +| 4 | Reserved | BCM4500 diagnostic register | +| 5 | Reserved | BCM4500 diagnostic register | + +### SNR Scaling + +The Windows BDA driver provides the scaling formula: + +```c title="SNR to Signal Strength Conversion" +uint16_t snr_raw = (buf[1] << 8) | buf[0]; + +if (snr_raw <= 0x0F00) { + signal_strength = snr_raw * 17; // Maps 0--0x0F00 to 0--65535 +} else { + signal_strength = 0xFFFF; // 100% at SNR >= 0x0F00 +} +``` + +| SNR Raw Value | Signal Strength | Quality | +|--------------|----------------|---------| +| 0x0000 | 0 (0%) | No signal | +| 0x0400 | ~27% | Poor | +| 0x0800 | ~53% | Fair | +| 0x0C00 | ~80% | Good | +| 0x0F00 | 100% | Maximum | +| > 0x0F00 | 100% (clamped) | Maximum | + +### Firmware Implementation Differences + +The signal strength readback involves I2C transactions to the BCM4500's [indirect register protocol](/bcm4500/demodulator/). The implementation varies between firmware versions: + +| Aspect | v2.06 | Rev.2 v2.10 | v2.13 | +|--------|-------|-------------|-------| +| Registers polled | 0xA2, 0xA8, 0xA4 | 0xA2, 0xA8, 0xA4 | Consolidated (1 register) | +| Max poll iterations | 6 | 6 | Simplified | +| Read-back verification | No | Yes (explicit) | No | +| Call chain | 3-register loop | 3-register + verify | Single register path | + +All versions ultimately return the same 6-byte response format to the host. + +## BCM4500 Status Registers + +These direct registers are used for signal monitoring: + +| Register | Address | Function | Access | +|----------|---------|----------|--------| +| Status | 0xA2 | BCM4500 readiness status | Polled during boot and signal checks | +| Lock | 0xA4 | Lock/ready; bit 5 = locked | Read by GET_SIGNAL_LOCK (0x90) | +| Command | 0xA8 | Indirect command status; bit 0 = busy | Polled during register operations | +| Demod Status | 0xF9 | Extended demod status | Read by GET_DEMOD_STATUS (0x99, v2.13 only) | + +### Register 0xA2 -- Status + +Read during boot probing and signal strength readback. Returns 0x02 when the BCM4500 is powered on but no signal is locked. Exact bit field definitions are not documented in the public BCM4500 datasheet. + +### Register 0xA4 -- Lock + +The primary lock indicator. The kernel driver reads this as a single byte via GET_SIGNAL_LOCK (0x90). Bit 5 (0x20) is the definitive lock status flag. + +### Register 0xA8 -- Command + +Used by the [indirect register protocol](/bcm4500/demodulator/) to track command completion. Bit 0 clear indicates the previous indirect read/write command has completed. This register is polled after every indirect register operation. + +### Register 0xF9 -- Demod Status (v2.13 only) + +Read by the GET_DEMOD_STATUS vendor command (0x99). Also polled by the v2.13 INT0 handler for demodulator availability detection. This register is not accessed by v2.06 or Rev.2 firmware. + + diff --git a/site/src/content/docs/bcm4500/tuning-protocol.mdx b/site/src/content/docs/bcm4500/tuning-protocol.mdx index d7a4288..47902c7 100644 --- a/site/src/content/docs/bcm4500/tuning-protocol.mdx +++ b/site/src/content/docs/bcm4500/tuning-protocol.mdx @@ -1,260 +1,260 @@ ---- -title: Tuning Protocol -description: Complete TUNE_8PSK command protocol including parameter encoding, modulation dispatch, and BCM4500 I2C programming. ---- - -import { Tabs, TabItem, Badge, Steps, Aside } from '@astrojs/starlight/components'; - -The TUNE_8PSK vendor command (0x86) is the primary mechanism for programming the BCM4500 demodulator to receive a satellite signal. The command carries a 10-byte payload encoding frequency, symbol rate, modulation type, and FEC rate. The firmware parses this payload, dispatches to a modulation-specific handler, programs the BCM4500 via I2C, and the host then polls for signal lock. - -See the [Register Map](/bcm4500/register-map/) for a consolidated lookup of all XRAM addresses and BCM4500 registers referenced on this page. - -## Command Format - -``` -USB SETUP: - bmRequestType = 0x40 (Vendor, Host-to-Device, OUT) - bRequest = 0x86 (TUNE_8PSK) - wValue = 0x0000 - wIndex = 0x0000 - wLength = 10 -``` - -### EP0 Payload Layout - -| Byte | Content | Encoding | -|------|---------|----------| -| [0] | Symbol Rate byte 0 | Little-endian LSB | -| [1] | Symbol Rate byte 1 | | -| [2] | Symbol Rate byte 2 | | -| [3] | Symbol Rate byte 3 | Little-endian MSB | -| [4] | Frequency byte 0 | Little-endian LSB | -| [5] | Frequency byte 1 | | -| [6] | Frequency byte 2 | | -| [7] | Frequency byte 3 | Little-endian MSB | -| [8] | Modulation Type | 0--9 (see table below) | -| [9] | Inner FEC Rate | Index into modulation-specific table | - -**Symbol Rate** is in samples per second (sps). The Windows driver converts from ksps: `ulTempSymbolRate = pDeviceParameter->ulSymbolRate * 1000`. Valid range: 256,000 -- 30,000,000 sps. - -**Frequency** is the IF frequency in kHz (950,000 -- 2,150,000), computed by the host as `(RF_freq - LO_freq) * multiplier`. - -## Firmware Parameter Parsing - -The firmware reads the 10-byte payload from EP0BUF (XRAM 0xE740--0xE749) and stores: - -| Source | Destination | Notes | -|--------|-------------|-------| -| EP0BUF[8] (modulation) | IRAM 0x4D | Direct copy | -| EP0BUF[9] (FEC rate) | IRAM 0x4F | Direct copy | -| EP0BUF[4--7] (frequency) | XRAM 0xE0DB--0xE0DE | Byte-reversed (LE to BE) | -| EP0BUF[0--3] (symbol rate) | XRAM 0xE0CB--0xE0CE | Byte-reversed (LE to BE) | - -The byte reversal converts host little-endian to BCM4500 big-endian, allowing values to be written directly to the demodulator via I2C without further conversion. - -```asm title="EP0BUF Read (Rev.2 at CODE:0802)" -; Read modulation type and FEC rate -MOV DPTR, #0xE748 ; EP0BUF[8] = modulation type -MOVX A, @DPTR -MOV 0x4D, A ; Store to IRAM 0x4D -INC DPTR ; DPTR = 0xE749 -MOVX A, @DPTR ; EP0BUF[9] = FEC rate -MOV 0x4F, A ; Store to IRAM 0x4F -``` - -## Modulation Dispatch - -After parsing, the firmware validates the modulation type (bounds check against 10) and dispatches via a 20-byte jump table (10 entries x 2 bytes) at CODE:0873. Values >= 10 are rejected silently. - -### Dispatch Table (Rev.2) - -| Entry | Target | Modulation | -|-------|--------|-----------| -| 0 | 0x08B7 | DVB-S QPSK | -| 1 | 0x08DF | Turbo QPSK | -| 2 | 0x08FA | Turbo 8PSK | -| 3 | 0x0915 | Turbo 16QAM | -| 4 | 0x0947 | DCII Combo | -| 5 | 0x094F | DCII I-stream | -| 6 | 0x0957 | DCII Q-stream | -| 7 | 0x095F | DCII Offset QPSK | -| 8 | 0x0887 | DSS QPSK | -| 9 | 0x0887 | DVB BPSK (shares DSS handler) | - -Each handler validates the FEC index, looks up a preconfigured byte from an XRAM table, and writes configuration to four XRAM registers. - -## Modulation Handler Details - - - - -### DVB-S QPSK (Index 0) - -| Parameter | Value | -|-----------|-------| -| FEC table base | XRAM 0xE0F9 | -| Max FEC index | 7 | -| Modulation type register | 0x09 | -| Turbo flag | 0x00 (off) | -| Demod mode | 0x10 (standard) | -| bmDCtuned | Cleared | - -**FEC rates available** (table at 0xE0F9): 1/2, 2/3, 3/4, 5/6, 7/8, auto, none. - -### DSS QPSK (Index 8) and DVB-S BPSK (Index 9) - -DSS and DVB BPSK share the same handler at 0x0887. They use the same FEC table as DVB-S QPSK (0xE0F9) but OR the lookup value with 0x80 to distinguish them: - -```c title="DSS/BPSK FEC Encoding" -XRAM[0xE0EB] = XRAM[0xE0F9 + FEC_index] | 0x80; -// Out-of-range default: 0x8C -``` - - - - -### Turbo QPSK (Index 1) - -| Parameter | Value | -|-----------|-------| -| FEC table base | XRAM 0xE0B7 | -| Max FEC index | 5 | -| Modulation type register | 0x09 | -| Turbo flag | 0x01 (on) | -| Demod mode | 0x10 | -| bmDCtuned | Cleared | - -### Turbo 8PSK (Index 2) - -| Parameter | Value | -|-----------|-------| -| FEC table base | XRAM 0xE0B1 | -| Max FEC index | 5 | -| Modulation type register | 0x09 | -| Turbo flag | 0x01 (on) | -| Demod mode | 0x10 | -| bmDCtuned | Cleared | - -### Turbo 16QAM (Index 3) - -| Parameter | Value | -|-----------|-------| -| FEC table base | XRAM 0xE0BC | -| Max FEC index | 1 | -| Modulation type register | 0x09 | -| Turbo flag | 0x01 (on) | -| Demod mode | 0x10 | -| bmDCtuned | Cleared | - - - - -All four DCII variants share a common post-processing path but set different demod mode values: - -| Modulation | Index | Demod Mode (0xE0F5) | -|-----------|-------|---------------------| -| DCII Combo | 4 | 0x10 | -| DCII Offset QPSK | 7 | 0x11 | -| DCII I-stream (split) | 5 | 0x12 | -| DCII Q-stream (split) | 6 | 0x16 | - -**Common DCII parameters:** - -| Parameter | Value | -|-----------|-------| -| FEC table base | XRAM 0xE0BD | -| Max FEC index | 9 | -| FEC code rate register | 0xFC (fixed for all DCII) | -| Turbo flag | 0x00 (off) | -| bmDCtuned | **Set** (0x40 OR'd into config status) | - -The DCII modulation type register (0xE0EC) is loaded from the lookup table at `0xE0BD + FEC_index`, unlike other modulations which use the fixed value 0x09. - - - - -## XRAM Configuration Summary - -After modulation dispatch, four XRAM registers hold the BCM4500 configuration: - -| XRAM Address | Register Name | DVB-S QPSK | Turbo (Q/8/16) | DCII | DSS/BPSK | -|-------------|---------------|-----------|---------------|------|----------| -| 0xE0EB | FEC Code Rate | Table lookup | Table lookup | 0xFC (fixed) | Lookup OR 0x80 | -| 0xE0EC | Modulation Type | 0x09 | 0x09 | From DCII table | 0x09 | -| 0xE0F5 | Demod Mode | 0x10 | 0x10 | 0x10/0x11/0x12/0x16 | 0x10 | -| 0xE0F6 | Turbo Flag | 0x00 | 0x01 | 0x00 | 0x00 | - -## FEC Rate Lookup Tables - -These tables are populated at boot from the CODE-space init table: - -| XRAM Base | Modulation | Max Index | Code Rates | -|-----------|-----------|-----------|------------| -| 0xE0F9 | DVB-S QPSK, DSS, BPSK | 7 | 1/2, 2/3, 3/4, 5/6, 7/8, auto, none | -| 0xE0B7 | Turbo QPSK | 5 | Turbo-specific rates | -| 0xE0B1 | Turbo 8PSK | 5 | Turbo-specific rates | -| 0xE0BC | Turbo 16QAM | 1 | Single code rate | -| 0xE0BD | DCII (all variants) | 9 | Combined code + modulation | - -## Complete Tuning Sequence - -The full sequence from host command to signal acquisition: - - -1. **LNB Configuration** (separate vendor commands, before TUNE_8PSK) - - SET_LNB_VOLTAGE (0x8B): GPIO P0.4, no I2C. wValue=1 for 18V (H/Circular-L), wValue=0 for 13V (V/Circular-R). - - SET_22KHZ_TONE (0x8C): GPIO P0.3, no I2C. wValue=1 for high band, wValue=0 for low band. - - SEND_DISEQC_COMMAND (0x8D): If multi-switch is needed. - -2. **TUNE_8PSK (0x86)** -- Host sends 10-byte payload via USB control transfer. - -3. **EP0BUF Parsing** -- Firmware reads modulation/FEC to IRAM, byte-reverses frequency/symbol rate to XRAM. - -4. **Modulation Dispatch** -- FEC lookup via jump table, XRAM configuration registers set. - -5. **GPIO P3.6** -- DVB mode select pin driven based on modulation type. - -6. **BCM4500 I2C Programming** (3 outer retries, each trying up to 3 I2C addresses): - - Poll BCM4500 ready: I2C READ registers 0xA2, 0xA8, 0xA4. - - Write page: I2C WRITE register 0xA6 with 0x00. - - Write config data: I2C WRITE register 0xA7 with frequency, symbol rate, FEC, modulation, and demod parameters. - - Execute: I2C WRITE register 0xA8 with 0x03 (indirect write command). - - Poll completion: I2C READ registers 0xA8, 0xA2. - - Verify: I2C READ register 0xA7 (read-back compare). - -7. **Signal Acquisition** (host polling): - - GET_SIGNAL_LOCK (0x90): Poll until non-zero. - - GET_SIGNAL_STRENGTH (0x87): Read [SNR value](/bcm4500/signal-monitoring/). - - -## I2C Programming Details - -The core I2C write function (`FUN_CODE_1670` on Rev.2) implements the [BCM4500 indirect register protocol](/bcm4500/demodulator/): - -```c title="BCM4500 Indirect Write (Decompiled)" -void bcm4500_indirect_write(byte *data, byte count) { - // Wait for BCM4500 ready (poll regs 0xA2, 0xA8, 0xA4) - bus_wait_ready(); - - // Write page address (0x00) to register 0xA6 - i2c_write(1, 0, 0xA6, 0x10); // [0x00] - - // Write data to register 0xA7 - i2c_write(count, 0, 0xA7, 0x10); // [data0..dataN] - - // Write command (0x03 = indirect write) to 0xA8 - i2c_write(1, 0, 0xA8, 0x10); // [0x03] - - // Poll completion (regs 0xA8, 0xA2) - poll_write_complete(); - - // Verify: read back from 0xA7 and compare - verify_readback(); -} -``` - -The demod scan function (`FUN_CODE_1dd0`) wraps this in a 3-address iteration loop, supporting hardware variants where the BCM4500 may respond at different I2C addresses. The outer tune function retries the entire scan up to 3 times. - - +--- +title: Tuning Protocol +description: Complete TUNE_8PSK command protocol including parameter encoding, modulation dispatch, and BCM4500 I2C programming. +--- + +import { Tabs, TabItem, Badge, Steps, Aside } from '@astrojs/starlight/components'; + +The TUNE_8PSK vendor command (0x86) is the primary mechanism for programming the BCM4500 demodulator to receive a satellite signal. The command carries a 10-byte payload encoding frequency, symbol rate, modulation type, and FEC rate. The firmware parses this payload, dispatches to a modulation-specific handler, programs the BCM4500 via I2C, and the host then polls for signal lock. + +See the [Register Map](/bcm4500/register-map/) for a consolidated lookup of all XRAM addresses and BCM4500 registers referenced on this page. + +## Command Format + +``` +USB SETUP: + bmRequestType = 0x40 (Vendor, Host-to-Device, OUT) + bRequest = 0x86 (TUNE_8PSK) + wValue = 0x0000 + wIndex = 0x0000 + wLength = 10 +``` + +### EP0 Payload Layout + +| Byte | Content | Encoding | +|------|---------|----------| +| [0] | Symbol Rate byte 0 | Little-endian LSB | +| [1] | Symbol Rate byte 1 | | +| [2] | Symbol Rate byte 2 | | +| [3] | Symbol Rate byte 3 | Little-endian MSB | +| [4] | Frequency byte 0 | Little-endian LSB | +| [5] | Frequency byte 1 | | +| [6] | Frequency byte 2 | | +| [7] | Frequency byte 3 | Little-endian MSB | +| [8] | Modulation Type | 0--9 (see table below) | +| [9] | Inner FEC Rate | Index into modulation-specific table | + +**Symbol Rate** is in samples per second (sps). The Windows driver converts from ksps: `ulTempSymbolRate = pDeviceParameter->ulSymbolRate * 1000`. Valid range: 256,000 -- 30,000,000 sps. + +**Frequency** is the IF frequency in kHz (950,000 -- 2,150,000), computed by the host as `(RF_freq - LO_freq) * multiplier`. + +## Firmware Parameter Parsing + +The firmware reads the 10-byte payload from EP0BUF (XRAM 0xE740--0xE749) and stores: + +| Source | Destination | Notes | +|--------|-------------|-------| +| EP0BUF[8] (modulation) | IRAM 0x4D | Direct copy | +| EP0BUF[9] (FEC rate) | IRAM 0x4F | Direct copy | +| EP0BUF[4--7] (frequency) | XRAM 0xE0DB--0xE0DE | Byte-reversed (LE to BE) | +| EP0BUF[0--3] (symbol rate) | XRAM 0xE0CB--0xE0CE | Byte-reversed (LE to BE) | + +The byte reversal converts host little-endian to BCM4500 big-endian, allowing values to be written directly to the demodulator via I2C without further conversion. + +```asm title="EP0BUF Read (Rev.2 at CODE:0802)" +; Read modulation type and FEC rate +MOV DPTR, #0xE748 ; EP0BUF[8] = modulation type +MOVX A, @DPTR +MOV 0x4D, A ; Store to IRAM 0x4D +INC DPTR ; DPTR = 0xE749 +MOVX A, @DPTR ; EP0BUF[9] = FEC rate +MOV 0x4F, A ; Store to IRAM 0x4F +``` + +## Modulation Dispatch + +After parsing, the firmware validates the modulation type (bounds check against 10) and dispatches via a 20-byte jump table (10 entries x 2 bytes) at CODE:0873. Values >= 10 are rejected silently. + +### Dispatch Table (Rev.2) + +| Entry | Target | Modulation | +|-------|--------|-----------| +| 0 | 0x08B7 | DVB-S QPSK | +| 1 | 0x08DF | Turbo QPSK | +| 2 | 0x08FA | Turbo 8PSK | +| 3 | 0x0915 | Turbo 16QAM | +| 4 | 0x0947 | DCII Combo | +| 5 | 0x094F | DCII I-stream | +| 6 | 0x0957 | DCII Q-stream | +| 7 | 0x095F | DCII Offset QPSK | +| 8 | 0x0887 | DSS QPSK | +| 9 | 0x0887 | DVB BPSK (shares DSS handler) | + +Each handler validates the FEC index, looks up a preconfigured byte from an XRAM table, and writes configuration to four XRAM registers. + +## Modulation Handler Details + + + + +### DVB-S QPSK (Index 0) + +| Parameter | Value | +|-----------|-------| +| FEC table base | XRAM 0xE0F9 | +| Max FEC index | 7 | +| Modulation type register | 0x09 | +| Turbo flag | 0x00 (off) | +| Demod mode | 0x10 (standard) | +| bmDCtuned | Cleared | + +**FEC rates available** (table at 0xE0F9): 1/2, 2/3, 3/4, 5/6, 7/8, auto, none. + +### DSS QPSK (Index 8) and DVB-S BPSK (Index 9) + +DSS and DVB BPSK share the same handler at 0x0887. They use the same FEC table as DVB-S QPSK (0xE0F9) but OR the lookup value with 0x80 to distinguish them: + +```c title="DSS/BPSK FEC Encoding" +XRAM[0xE0EB] = XRAM[0xE0F9 + FEC_index] | 0x80; +// Out-of-range default: 0x8C +``` + + + + +### Turbo QPSK (Index 1) + +| Parameter | Value | +|-----------|-------| +| FEC table base | XRAM 0xE0B7 | +| Max FEC index | 5 | +| Modulation type register | 0x09 | +| Turbo flag | 0x01 (on) | +| Demod mode | 0x10 | +| bmDCtuned | Cleared | + +### Turbo 8PSK (Index 2) + +| Parameter | Value | +|-----------|-------| +| FEC table base | XRAM 0xE0B1 | +| Max FEC index | 5 | +| Modulation type register | 0x09 | +| Turbo flag | 0x01 (on) | +| Demod mode | 0x10 | +| bmDCtuned | Cleared | + +### Turbo 16QAM (Index 3) + +| Parameter | Value | +|-----------|-------| +| FEC table base | XRAM 0xE0BC | +| Max FEC index | 1 | +| Modulation type register | 0x09 | +| Turbo flag | 0x01 (on) | +| Demod mode | 0x10 | +| bmDCtuned | Cleared | + + + + +All four DCII variants share a common post-processing path but set different demod mode values: + +| Modulation | Index | Demod Mode (0xE0F5) | +|-----------|-------|---------------------| +| DCII Combo | 4 | 0x10 | +| DCII Offset QPSK | 7 | 0x11 | +| DCII I-stream (split) | 5 | 0x12 | +| DCII Q-stream (split) | 6 | 0x16 | + +**Common DCII parameters:** + +| Parameter | Value | +|-----------|-------| +| FEC table base | XRAM 0xE0BD | +| Max FEC index | 9 | +| FEC code rate register | 0xFC (fixed for all DCII) | +| Turbo flag | 0x00 (off) | +| bmDCtuned | **Set** (0x40 OR'd into config status) | + +The DCII modulation type register (0xE0EC) is loaded from the lookup table at `0xE0BD + FEC_index`, unlike other modulations which use the fixed value 0x09. + + + + +## XRAM Configuration Summary + +After modulation dispatch, four XRAM registers hold the BCM4500 configuration: + +| XRAM Address | Register Name | DVB-S QPSK | Turbo (Q/8/16) | DCII | DSS/BPSK | +|-------------|---------------|-----------|---------------|------|----------| +| 0xE0EB | FEC Code Rate | Table lookup | Table lookup | 0xFC (fixed) | Lookup OR 0x80 | +| 0xE0EC | Modulation Type | 0x09 | 0x09 | From DCII table | 0x09 | +| 0xE0F5 | Demod Mode | 0x10 | 0x10 | 0x10/0x11/0x12/0x16 | 0x10 | +| 0xE0F6 | Turbo Flag | 0x00 | 0x01 | 0x00 | 0x00 | + +## FEC Rate Lookup Tables + +These tables are populated at boot from the CODE-space init table: + +| XRAM Base | Modulation | Max Index | Code Rates | +|-----------|-----------|-----------|------------| +| 0xE0F9 | DVB-S QPSK, DSS, BPSK | 7 | 1/2, 2/3, 3/4, 5/6, 7/8, auto, none | +| 0xE0B7 | Turbo QPSK | 5 | Turbo-specific rates | +| 0xE0B1 | Turbo 8PSK | 5 | Turbo-specific rates | +| 0xE0BC | Turbo 16QAM | 1 | Single code rate | +| 0xE0BD | DCII (all variants) | 9 | Combined code + modulation | + +## Complete Tuning Sequence + +The full sequence from host command to signal acquisition: + + +1. **LNB Configuration** (separate vendor commands, before TUNE_8PSK) + - SET_LNB_VOLTAGE (0x8B): GPIO P0.4, no I2C. wValue=1 for 18V (H/Circular-L), wValue=0 for 13V (V/Circular-R). + - SET_22KHZ_TONE (0x8C): GPIO P0.3, no I2C. wValue=1 for high band, wValue=0 for low band. + - SEND_DISEQC_COMMAND (0x8D): If multi-switch is needed. + +2. **TUNE_8PSK (0x86)** -- Host sends 10-byte payload via USB control transfer. + +3. **EP0BUF Parsing** -- Firmware reads modulation/FEC to IRAM, byte-reverses frequency/symbol rate to XRAM. + +4. **Modulation Dispatch** -- FEC lookup via jump table, XRAM configuration registers set. + +5. **GPIO P3.6** -- DVB mode select pin driven based on modulation type. + +6. **BCM4500 I2C Programming** (3 outer retries, each trying up to 3 I2C addresses): + - Poll BCM4500 ready: I2C READ registers 0xA2, 0xA8, 0xA4. + - Write page: I2C WRITE register 0xA6 with 0x00. + - Write config data: I2C WRITE register 0xA7 with frequency, symbol rate, FEC, modulation, and demod parameters. + - Execute: I2C WRITE register 0xA8 with 0x03 (indirect write command). + - Poll completion: I2C READ registers 0xA8, 0xA2. + - Verify: I2C READ register 0xA7 (read-back compare). + +7. **Signal Acquisition** (host polling): + - GET_SIGNAL_LOCK (0x90): Poll until non-zero. + - GET_SIGNAL_STRENGTH (0x87): Read [SNR value](/bcm4500/signal-monitoring/). + + +## I2C Programming Details + +The core I2C write function (`FUN_CODE_1670` on Rev.2) implements the [BCM4500 indirect register protocol](/bcm4500/demodulator/): + +```c title="BCM4500 Indirect Write (Decompiled)" +void bcm4500_indirect_write(byte *data, byte count) { + // Wait for BCM4500 ready (poll regs 0xA2, 0xA8, 0xA4) + bus_wait_ready(); + + // Write page address (0x00) to register 0xA6 + i2c_write(1, 0, 0xA6, 0x10); // [0x00] + + // Write data to register 0xA7 + i2c_write(count, 0, 0xA7, 0x10); // [data0..dataN] + + // Write command (0x03 = indirect write) to 0xA8 + i2c_write(1, 0, 0xA8, 0x10); // [0x03] + + // Poll completion (regs 0xA8, 0xA2) + poll_write_complete(); + + // Verify: read back from 0xA7 and compare + verify_readback(); +} +``` + +The demod scan function (`FUN_CODE_1dd0`) wraps this in a 3-address iteration loop, supporting hardware variants where the BCM4500 may respond at different I2C addresses. The outer tune function retries the entire scan up to 3 times. + + diff --git a/site/src/content/docs/driver/dvb-s2.mdx b/site/src/content/docs/driver/dvb-s2.mdx index 0e976e7..7ec64de 100644 --- a/site/src/content/docs/driver/dvb-s2.mdx +++ b/site/src/content/docs/driver/dvb-s2.mdx @@ -1,169 +1,169 @@ ---- -title: DVB-S2 Incompatibility -description: Why the SkyWalker-1 cannot support DVB-S2 and what the BCM4500 demodulator actually provides. ---- - -import { Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; - - - -## The Core Problem - -DVB-S2 (ETSI EN 302 307) requires two forward error correction technologies that do not exist in the BCM4500: - -| FEC Component | BCM4500 Has | DVB-S2 Requires | -|---------------|-------------|-----------------| -| Inner code | Viterbi (convolutional) + Turbo | **LDPC** (Low-Density Parity-Check) | -| Outer code | Reed-Solomon (t=10) | **BCH** (Bose-Chaudhuri-Hocquenghem) | -| Block size | Streaming (Viterbi) or short turbo blocks | **64,800 or 16,200 bits** | -| Decoder type | Trellis-based / iterative turbo | **Iterative belief propagation** | - -LDPC decoding requires dedicated silicon: large block RAM for message passing (the LDPC block is 64,800 bits), iterative belief propagation logic, and a fundamentally different decoder architecture. This cannot be emulated in firmware on the BCM4500's simple 8-bit on-chip microcontroller, which handles only configuration, acquisition, and monitoring -- not data-path processing. - -## BCM4500 FEC Architecture - -The BCM4500 contains exactly two FEC decoder paths (from the [BCM4500 datasheet](https://elcodis.com/parts/5786421/BCM4500.html)): - - - - -### Viterbi + Reed-Solomon (Legacy) - -Used for DVB-S QPSK, DSS QPSK, DVB-S BPSK, and Digicipher II modes. - -| Parameter | Value | -|-----------|-------| -| Inner decoder | Viterbi (convolutional) | -| Code rates | 1/2, 2/3, 3/4, 5/6, 7/8 | -| Outer decoder | Reed-Solomon | -| Signal path | Soft decisions fed to Viterbi, then RS outer code | -| Modulations | BPSK, QPSK | - -**Firmware evidence** (XRAM 0xE0F9): FEC lookup table with max index 7. Modulation dispatch sets `XRAM 0xE0F6 = 0x00` (turbo flag OFF), `XRAM 0xE0F5 = 0x10` (standard demod mode). - -**Windows driver**: `m_CurResource.ulInnerFecType = BDA_FEC_VITERBI` -- explicitly rejects any FEC type other than Viterbi. - - - - -### Turbo Code Decoder (Proprietary) - -Used for Turbo QPSK, Turbo 8PSK, and Turbo 16QAM -- proprietary "advanced modulation" modes developed by Broadcom for EchoStar/Dish Network. - -| Parameter | Value | -|-----------|-------| -| Inner decoder | Iterative turbo code | -| QPSK rates | 1/4, 1/2, 3/4 | -| 8PSK rates | 2/3, 3/4, 5/6, 8/9 | -| 16QAM rates | 3/4 | -| Outer decoder | Reed-Solomon (t=10) | -| Modulations | QPSK, 8PSK, 16QAM | - -**Firmware evidence** (XRAM 0xE0B7, 0xE0B1, 0xE0BC): Turbo FEC lookup tables. All turbo modes set `XRAM 0xE0F6 = 0x01` (turbo flag ON). - -These turbo codes are NOT the same as DVB-S2's LDPC codes. The turbo decoder uses parallel concatenated convolutional codes, while LDPC uses sparse parity-check matrix belief propagation. Different algorithms, different silicon. - - - - -### DCII Decoder - -Used for DCII combo, split I/Q, and offset QPSK modes. - -| Parameter | Value | -|-----------|-------| -| FEC table | XRAM 0xE0BD, max index 9 | -| Fixed FEC code | `0xFC` written to XRAM 0xE0EB | -| Modulations | QPSK variants (combo, split, offset) | - - - -The BCM4500 datasheet states explicitly: "Optimized soft decisions are then fed into either a DVB/DIRECTV/DCII-compliant FEC decoder, or an advanced modulation turbo decoder." These are the only two FEC paths. There is no third path for LDPC/BCH. - -## Zero DVB-S2 Evidence in Firmware or Driver - -Exhaustive search across all firmware versions and Windows driver source: - -| What Was Searched | Result | -|-------------------|--------| -| All firmware binaries (v2.06, Rev.2, v2.13) via Ghidra | No LDPC/BCH/DVB-S2 references | -| Windows driver `SkyWalker1Control.h` | Modulation constants 0--9 only, none for DVB-S2 | -| Windows driver `SkyWalker1TunerFilter.cpp` | Rejects non-Viterbi FEC types | -| Windows driver `SkyWalker1Control.cpp` | Hardcodes `ADV_MOD_DVB_QPSK` (value 0) | -| Firmware dispatch table (CODE:0873) | 10 entries max, values >= 10 rejected | -| All FEC lookup tables in XRAM | Only Viterbi rates and turbo rates, no LDPC rates | -| I2C register addresses | BCM4500-specific protocol only (page 0x00, regs 0xA6/A7/A8) | - -**Specific proof points:** -- `SkyWalker1TunerFilter.cpp`, line 1070: `if(ulNewInnerFecType == BDA_FEC_VITERBI)` -- only Viterbi accepted; any other FEC type returns `STATUS_INVALID_PARAMETER` -- `SkyWalker1Control.cpp`, line 292: `ucCommand[8] = ADV_MOD_DVB_QPSK;` -- always sends modulation type 0 -- Firmware jump table at CODE:0866: bounds check rejects modulation values >= 10 - -## Is the USB Data Path a Bottleneck? - -**No.** The GPIF/USB 2.0 streaming architecture has roughly 5x headroom for DVB-S2 data rates. The bottleneck is the demodulator silicon, not the transport path. - -| Metric | Value | -|--------|-------| -| DVB-S2 max net rate (8PSK 9/10, 30 Msps) | ~58 Mbps (~7.25 MB/s) | -| Typical HD transponder (8PSK 3/4, 27.5 Msps) | ~44 Mbps | -| USB 2.0 practical bulk throughput | ~280 Mbps (~35 MB/s) | -| GPIF engine theoretical throughput (48 MHz, 8-bit) | 384 Mbps (48 MB/s) | -| Current DVB-S typical TS rate | 1--5 MB/s | - -DVB-S2 uses the same MPEG-TS output format (188-byte packets) as DVB-S, so the GPIF waveform and AUTOIN configuration would work unchanged. - -However, this is a moot point: even if the BCM4500 were physically replaced with a DVB-S2-capable chip, the entire FX2 firmware would need rewriting (I2C register protocol, tuning sequence, modulation dispatch, FEC configuration), since every DVB-S2 demodulator uses a completely different register interface. - -## Broadcom's DVB-S2 Silicon Timeline - -Broadcom addressed DVB-S2 by designing entirely new chips -- they did not add LDPC to the BCM4500: - -| Chip | Year | DVB-S2 | Key Addition | -|------|------|--------|-------------| -| **BCM4500** | ~2003 | No | Turbo FEC + Viterbi/RS | -| **BCM4501** | 2006 | **Yes** | First dual-tuner DVB-S2; LDPC/BCH decoder | -| **BCM4505** | 2007 | **Yes** | Single-channel, 65nm, LDPC/BCH + legacy | -| **BCM4506** | 2007 | **Yes** | Dual-channel, 65nm, LDPC/BCH + legacy | - -The BCM4501 datasheet explicitly states it includes "four 8-bit ADCs, all-digital variable rate QPSK/8PSK receivers, advanced modulation LDPC/BCH, and DVB-S-compliant forward error correction decoder." Adding LDPC/BCH required new silicon. - - - -## What Genpix Did: The SkyWalker-3 - -Genpix released the SkyWalker-3 as a DVB-S2-capable successor using a completely different demodulator (likely STMicroelectronics STV0903): - -| Feature | SkyWalker-1 (BCM4500) | SkyWalker-3 (likely STV0903) | -|---------|----------------------|---------------------------| -| DVB-S QPSK | Yes | Yes | -| DVB-S2 QPSK | **No** | Yes | -| DVB-S2 8PSK | **No** | Yes | -| Turbo QPSK | Yes | **No** | -| Turbo 8PSK | Yes | **No** | -| Turbo 16QAM | Yes | **No** | -| DCII | Yes | Yes | -| DSS | Yes | Yes | -| Symbol rate (DVB-S) | 256 Ksps -- 30 Msps | 1 -- 45 Msps | -| Symbol rate (DVB-S2) | N/A | 5 -- 33 Msps | -| FEC inner (DVB-S) | Viterbi | Viterbi | -| FEC inner (DVB-S2) | N/A | LDPC | -| FEC outer (DVB-S2) | N/A | BCH | - -The trade-off is clear: the SkyWalker-3 gained DVB-S2 but **lost turbo-FEC support entirely**. The turbo codes were proprietary to Broadcom/EchoStar, and the STV0903 does not implement them. This means the SkyWalker-3 cannot receive Dish Network's legacy turbo-coded 8PSK transmissions. - -## Summary - -| Question | Answer | -|----------|--------| -| Is DVB-S2 a hardware or firmware limitation? | **Hardware** -- BCM4500 has no LDPC/BCH decoder | -| Could a firmware update add DVB-S2? | **No** -- LDPC requires dedicated silicon | -| Which Broadcom chip first added LDPC? | **BCM4501** (2006) | -| Any DVB-S2 hints in firmware/driver? | **None** -- zero references anywhere | -| Is the USB data path a bottleneck? | **No** -- ~5x headroom for DVB-S2 rates | -| What did Genpix do for DVB-S2? | Released SkyWalker-3 with STV0903 demodulator | -| What was lost in the SkyWalker-3? | Turbo-FEC support (Broadcom/EchoStar proprietary) | +--- +title: DVB-S2 Incompatibility +description: Why the SkyWalker-1 cannot support DVB-S2 and what the BCM4500 demodulator actually provides. +--- + +import { Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + + + +## The Core Problem + +DVB-S2 (ETSI EN 302 307) requires two forward error correction technologies that do not exist in the BCM4500: + +| FEC Component | BCM4500 Has | DVB-S2 Requires | +|---------------|-------------|-----------------| +| Inner code | Viterbi (convolutional) + Turbo | **LDPC** (Low-Density Parity-Check) | +| Outer code | Reed-Solomon (t=10) | **BCH** (Bose-Chaudhuri-Hocquenghem) | +| Block size | Streaming (Viterbi) or short turbo blocks | **64,800 or 16,200 bits** | +| Decoder type | Trellis-based / iterative turbo | **Iterative belief propagation** | + +LDPC decoding requires dedicated silicon: large block RAM for message passing (the LDPC block is 64,800 bits), iterative belief propagation logic, and a fundamentally different decoder architecture. This cannot be emulated in firmware on the BCM4500's simple 8-bit on-chip microcontroller, which handles only configuration, acquisition, and monitoring -- not data-path processing. + +## BCM4500 FEC Architecture + +The BCM4500 contains exactly two FEC decoder paths (from the [BCM4500 datasheet](https://elcodis.com/parts/5786421/BCM4500.html)): + + + + +### Viterbi + Reed-Solomon (Legacy) + +Used for DVB-S QPSK, DSS QPSK, DVB-S BPSK, and Digicipher II modes. + +| Parameter | Value | +|-----------|-------| +| Inner decoder | Viterbi (convolutional) | +| Code rates | 1/2, 2/3, 3/4, 5/6, 7/8 | +| Outer decoder | Reed-Solomon | +| Signal path | Soft decisions fed to Viterbi, then RS outer code | +| Modulations | BPSK, QPSK | + +**Firmware evidence** (XRAM 0xE0F9): FEC lookup table with max index 7. Modulation dispatch sets `XRAM 0xE0F6 = 0x00` (turbo flag OFF), `XRAM 0xE0F5 = 0x10` (standard demod mode). + +**Windows driver**: `m_CurResource.ulInnerFecType = BDA_FEC_VITERBI` -- explicitly rejects any FEC type other than Viterbi. + + + + +### Turbo Code Decoder (Proprietary) + +Used for Turbo QPSK, Turbo 8PSK, and Turbo 16QAM -- proprietary "advanced modulation" modes developed by Broadcom for EchoStar/Dish Network. + +| Parameter | Value | +|-----------|-------| +| Inner decoder | Iterative turbo code | +| QPSK rates | 1/4, 1/2, 3/4 | +| 8PSK rates | 2/3, 3/4, 5/6, 8/9 | +| 16QAM rates | 3/4 | +| Outer decoder | Reed-Solomon (t=10) | +| Modulations | QPSK, 8PSK, 16QAM | + +**Firmware evidence** (XRAM 0xE0B7, 0xE0B1, 0xE0BC): Turbo FEC lookup tables. All turbo modes set `XRAM 0xE0F6 = 0x01` (turbo flag ON). + +These turbo codes are NOT the same as DVB-S2's LDPC codes. The turbo decoder uses parallel concatenated convolutional codes, while LDPC uses sparse parity-check matrix belief propagation. Different algorithms, different silicon. + + + + +### DCII Decoder + +Used for DCII combo, split I/Q, and offset QPSK modes. + +| Parameter | Value | +|-----------|-------| +| FEC table | XRAM 0xE0BD, max index 9 | +| Fixed FEC code | `0xFC` written to XRAM 0xE0EB | +| Modulations | QPSK variants (combo, split, offset) | + + + +The BCM4500 datasheet states explicitly: "Optimized soft decisions are then fed into either a DVB/DIRECTV/DCII-compliant FEC decoder, or an advanced modulation turbo decoder." These are the only two FEC paths. There is no third path for LDPC/BCH. + +## Zero DVB-S2 Evidence in Firmware or Driver + +Exhaustive search across all firmware versions and Windows driver source: + +| What Was Searched | Result | +|-------------------|--------| +| All firmware binaries (v2.06, Rev.2, v2.13) via Ghidra | No LDPC/BCH/DVB-S2 references | +| Windows driver `SkyWalker1Control.h` | Modulation constants 0--9 only, none for DVB-S2 | +| Windows driver `SkyWalker1TunerFilter.cpp` | Rejects non-Viterbi FEC types | +| Windows driver `SkyWalker1Control.cpp` | Hardcodes `ADV_MOD_DVB_QPSK` (value 0) | +| Firmware dispatch table (CODE:0873) | 10 entries max, values >= 10 rejected | +| All FEC lookup tables in XRAM | Only Viterbi rates and turbo rates, no LDPC rates | +| I2C register addresses | BCM4500-specific protocol only (page 0x00, regs 0xA6/A7/A8) | + +**Specific proof points:** +- `SkyWalker1TunerFilter.cpp`, line 1070: `if(ulNewInnerFecType == BDA_FEC_VITERBI)` -- only Viterbi accepted; any other FEC type returns `STATUS_INVALID_PARAMETER` +- `SkyWalker1Control.cpp`, line 292: `ucCommand[8] = ADV_MOD_DVB_QPSK;` -- always sends modulation type 0 +- Firmware jump table at CODE:0866: bounds check rejects modulation values >= 10 + +## Is the USB Data Path a Bottleneck? + +**No.** The GPIF/USB 2.0 streaming architecture has roughly 5x headroom for DVB-S2 data rates. The bottleneck is the demodulator silicon, not the transport path. + +| Metric | Value | +|--------|-------| +| DVB-S2 max net rate (8PSK 9/10, 30 Msps) | ~58 Mbps (~7.25 MB/s) | +| Typical HD transponder (8PSK 3/4, 27.5 Msps) | ~44 Mbps | +| USB 2.0 practical bulk throughput | ~280 Mbps (~35 MB/s) | +| GPIF engine theoretical throughput (48 MHz, 8-bit) | 384 Mbps (48 MB/s) | +| Current DVB-S typical TS rate | 1--5 MB/s | + +DVB-S2 uses the same MPEG-TS output format (188-byte packets) as DVB-S, so the GPIF waveform and AUTOIN configuration would work unchanged. + +However, this is a moot point: even if the BCM4500 were physically replaced with a DVB-S2-capable chip, the entire FX2 firmware would need rewriting (I2C register protocol, tuning sequence, modulation dispatch, FEC configuration), since every DVB-S2 demodulator uses a completely different register interface. + +## Broadcom's DVB-S2 Silicon Timeline + +Broadcom addressed DVB-S2 by designing entirely new chips -- they did not add LDPC to the BCM4500: + +| Chip | Year | DVB-S2 | Key Addition | +|------|------|--------|-------------| +| **BCM4500** | ~2003 | No | Turbo FEC + Viterbi/RS | +| **BCM4501** | 2006 | **Yes** | First dual-tuner DVB-S2; LDPC/BCH decoder | +| **BCM4505** | 2007 | **Yes** | Single-channel, 65nm, LDPC/BCH + legacy | +| **BCM4506** | 2007 | **Yes** | Dual-channel, 65nm, LDPC/BCH + legacy | + +The BCM4501 datasheet explicitly states it includes "four 8-bit ADCs, all-digital variable rate QPSK/8PSK receivers, advanced modulation LDPC/BCH, and DVB-S-compliant forward error correction decoder." Adding LDPC/BCH required new silicon. + + + +## What Genpix Did: The SkyWalker-3 + +Genpix released the SkyWalker-3 as a DVB-S2-capable successor using a completely different demodulator (likely STMicroelectronics STV0903): + +| Feature | SkyWalker-1 (BCM4500) | SkyWalker-3 (likely STV0903) | +|---------|----------------------|---------------------------| +| DVB-S QPSK | Yes | Yes | +| DVB-S2 QPSK | **No** | Yes | +| DVB-S2 8PSK | **No** | Yes | +| Turbo QPSK | Yes | **No** | +| Turbo 8PSK | Yes | **No** | +| Turbo 16QAM | Yes | **No** | +| DCII | Yes | Yes | +| DSS | Yes | Yes | +| Symbol rate (DVB-S) | 256 Ksps -- 30 Msps | 1 -- 45 Msps | +| Symbol rate (DVB-S2) | N/A | 5 -- 33 Msps | +| FEC inner (DVB-S) | Viterbi | Viterbi | +| FEC inner (DVB-S2) | N/A | LDPC | +| FEC outer (DVB-S2) | N/A | BCH | + +The trade-off is clear: the SkyWalker-3 gained DVB-S2 but **lost turbo-FEC support entirely**. The turbo codes were proprietary to Broadcom/EchoStar, and the STV0903 does not implement them. This means the SkyWalker-3 cannot receive Dish Network's legacy turbo-coded 8PSK transmissions. + +## Summary + +| Question | Answer | +|----------|--------| +| Is DVB-S2 a hardware or firmware limitation? | **Hardware** -- BCM4500 has no LDPC/BCH decoder | +| Could a firmware update add DVB-S2? | **No** -- LDPC requires dedicated silicon | +| Which Broadcom chip first added LDPC? | **BCM4501** (2006) | +| Any DVB-S2 hints in firmware/driver? | **None** -- zero references anywhere | +| Is the USB data path a bottleneck? | **No** -- ~5x headroom for DVB-S2 rates | +| What did Genpix do for DVB-S2? | Released SkyWalker-3 with STV0903 demodulator | +| What was lost in the SkyWalker-3? | Turbo-FEC support (Broadcom/EchoStar proprietary) | diff --git a/site/src/content/docs/driver/linux-kernel.mdx b/site/src/content/docs/driver/linux-kernel.mdx index d44753a..5f2ad5d 100644 --- a/site/src/content/docs/driver/linux-kernel.mdx +++ b/site/src/content/docs/driver/linux-kernel.mdx @@ -1,272 +1,272 @@ ---- -title: Linux Kernel Driver -description: Architecture and command analysis of the dvb_usb_gp8psk kernel module for Genpix satellite receivers. ---- - -import { Steps, Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; - -The `dvb_usb_gp8psk` module is a Linux kernel DVB-USB driver supporting multiple Genpix satellite receiver models. It communicates with the FX2 microcontroller via USB vendor control requests to manage tuning, demodulation, LNB control, DiSEqC switching, and transport stream capture. - -**Source files**: `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h`, `gp8psk-fe.c` - -## USB Device Table - -| USB ID | Device Name | cold_ids | warm_ids | FW01 Needed? | -|--------|-------------|----------|----------|-------------| -| `v09C0p0200` | Rev.1 Cold | Yes | -- | **Yes** | -| `v09C0p0201` | Rev.1 Warm | -- | Yes | No (FW02 needed) | -| `v09C0p0202` | Rev.2 | -- | Yes | No | -| `v09C0p0203` | SkyWalker-1 | -- | Yes | No | -| `v09C0p0204` | SkyWalker-1 (alt) | -- | Yes | No | -| `v09C0p0206` | SkyWalker CW3K | -- | Yes | No | - - - -## Driver Architecture - -The driver consists of three source files: - -| File | Purpose | -|------|---------| -| `gp8psk.c` | USB device management, firmware loading, power control, vendor command wrappers | -| `gp8psk.h` | Vendor command constants, firmware version thresholds, USB PID definitions | -| `gp8psk-fe.c` | DVB frontend implementation: tuning, signal monitoring, LNB/DiSEqC callbacks | - -The driver registers with the DVB-USB framework (`dvb_usb_device_properties`) which handles: -- USB device enumeration and firmware download (for cold devices) -- DVB adapter and frontend creation -- URB management for bulk transport stream capture -- Power management callbacks - -## USB Transfer Parameters - -```c title="Driver USB configuration" -gp8psk_properties { - .usb_ctrl = CYPRESS_FX2; - .firmware = "dvb-usb-gp8psk-01.fw"; - .num_adapters = 1; - .generic_bulk_ctrl_endpoint = 0x01; - // Streaming: - .endpoint = 0x82; // IN bulk - .stream = USB_BULK; - .count = 7; // URBs - .buffersize = 8192; // bytes per URB -} -``` - -The driver allocates 7 URBs of 8192 bytes each (56 KB total) for transport stream reception on endpoint `0x82` (IN bulk). Vendor commands use endpoint `0x01` with a 2000 ms timeout. - -### Retry Logic - -IN operations retry up to 3 times if partial data is received. The command buffer is 80 bytes maximum: - -```c title="Vendor command wrapper" -static int gp8psk_usb_in_op(struct dvb_usb_device *d, - u8 req, u16 value, u16 index, u8 *b, int blen) -{ - int ret; - int try; - for (try = 0; try < 3; try++) { - ret = usb_control_msg(d->udev, - usb_rcvctrlpipe(d->udev, 0), - req, USB_TYPE_VENDOR | USB_DIR_IN, - value, index, b, blen, 2000); - if (ret == blen) break; - } - return ret; -} -``` - -## Complete Vendor Command Map - -| Cmd | Name | Dir | wValue | wLength | Purpose | -|-----|------|-----|--------|---------|---------| -| `0x80` | GET_8PSK_CONFIG | IN | `0x0000` | 1 | Read config status byte | -| `0x81` | SET_8PSK_CONFIG | OUT | varies | 0 | STALL (not implemented) | -| `0x83` | I2C_WRITE | OUT | dev_addr | N | Write to BCM4500 via I2C | -| `0x84` | I2C_READ | IN | dev_addr | N | Read from BCM4500 via I2C | -| `0x85` | ARM_TRANSFER | OUT | 0/1 | 0 | Start/stop TS streaming | -| `0x86` | TUNE_8PSK | OUT | `0x0000` | 10 | Send tuning parameters | -| `0x87` | GET_SIGNAL_STRENGTH | IN | `0x0000` | 6 | Read SNR values | -| `0x88` | LOAD_BCM4500 | OUT | 1 | 0 | Initiate demod FW download | -| `0x89` | BOOT_8PSK | IN | 0/1 | 1 | Power on/off demodulator | -| `0x8A` | START_INTERSIL | IN | 0/1 | 1 | Enable/disable LNB supply | -| `0x8B` | SET_LNB_VOLTAGE | OUT | 0/1 | 0 | Set 13V (0) or 18V (1) | -| `0x8C` | SET_22KHZ_TONE | OUT | 0/1 | 0 | Enable/disable 22 kHz tone | -| `0x8D` | SEND_DISEQC | OUT | msg[0] | 3-6 | Send DiSEqC message | -| `0x8E` | SET_DVB_MODE | OUT | 1 | 0 | Enable DVB-S mode | -| `0x8F` | SET_DN_SWITCH | OUT | cmd | 0 | Legacy Dish switch command | -| `0x90` | GET_SIGNAL_LOCK | IN | `0x0000` | 1 | Read lock status | -| `0x92` | GET_FW_VERS | IN | `0x0000` | 6 | Read firmware version | -| `0x94` | USE_EXTRA_VOLT | OUT | 0/1 | 0 | Enable +1V LNB boost | -| `0x95` | GET_FPGA_VERS | IN | `0x0000` | 1 | Read hardware platform ID | -| `0x99` | GET_DEMOD_STATUS | IN | `0x0000` | 1 | Read BCM4500 reg 0xF9 (v2.13+) | -| `0x9A` | INIT_DEMOD | OUT | `0x0000` | 0 | Re-init demodulator (v2.13+) | -| `0x9C` | DELAY_COMMAND | OUT | param | 0 | Tuning delay with polling (v2.13+) | -| `0x9D` | CW3K_INIT | OUT | 0/1 | 0 | CW3K model initialization | - -## Configuration Status Byte - -`GET_8PSK_CONFIG` (`0x80`) returns a bit-mapped status register: - -``` -Bit 0 (0x01): bm8pskStarted - Device booted and running -Bit 1 (0x02): bm8pskFW_Loaded - BCM4500 firmware loaded -Bit 2 (0x04): bmIntersilOn - LNB power supply enabled -Bit 3 (0x08): bmDVBmode - DVB mode enabled -Bit 4 (0x10): bm22kHz - 22 kHz tone active -Bit 5 (0x20): bmSEL18V - 18V LNB voltage selected -Bit 6 (0x40): bmDCtuned - DC offset tuning complete -Bit 7 (0x80): bmArmed - MPEG-2 stream transfer armed -``` - -## Boot Sequence - - - -1. **Read config** (`GET_8PSK_CONFIG 0x80`): Check bit 0 (`bm8pskStarted`) - -2. **Boot device** if not started: Send `BOOT_8PSK 0x89` with `wValue=1`, then read firmware version via `GET_FW_VERS 0x92` - -3. **Load BCM4500 firmware** if bit 1 not set: Only for Rev.1 Warm (PID `0x0201`). Send `LOAD_BCM4500 0x88` followed by firmware chunks. Skipped for SkyWalker-1 (bit already set from EEPROM boot). - -4. **Enable LNB** if bit 2 not set: Send `START_INTERSIL 0x8A` with `wValue=1` - -5. **Set DVB mode**: Send `SET_DVB_MODE 0x8E` with `wValue=1` (STALL on some revisions) - -6. **Cancel pending stream**: Send `ARM_TRANSFER 0x85` with `wValue=0` - -7. **Ready for tuning** - - - -## Tuning Flow - -```c title="10-byte tuning payload" -Bytes 0-3: Symbol Rate (u32 LE, in sps) -Bytes 4-7: Frequency (u32 LE, in kHz) -Byte 8: Modulation (0-9, see modulation types) -Byte 9: FEC Rate (index into firmware FEC table) -``` - -### Modulation Types - -| Value | Constant | Mode | -|-------|----------|------| -| 0 | `ADV_MOD_DVB_QPSK` | DVB-S QPSK | -| 1 | `ADV_MOD_TURBO_QPSK` | Turbo QPSK | -| 2 | `ADV_MOD_TURBO_8PSK` | Turbo 8PSK | -| 3 | `ADV_MOD_TURBO_16QAM` | Turbo 16QAM | -| 4 | `ADV_MOD_DCII_C_QPSK` | Digicipher II Combo | -| 5 | `ADV_MOD_DCII_I_QPSK` | Digicipher II I-stream | -| 6 | `ADV_MOD_DCII_Q_QPSK` | Digicipher II Q-stream | -| 7 | `ADV_MOD_DCII_C_OQPSK` | Digicipher II Offset QPSK | -| 8 | `ADV_MOD_DSS_QPSK` | DSS/DIRECTV QPSK | -| 9 | `ADV_MOD_DVB_BPSK` | DVB-S BPSK | - -### Signal Quality - -`GET_SIGNAL_STRENGTH` (`0x87`) returns 6 bytes: - -``` -Bytes 0-1: SNR value (u16 LE, in dBu*256 units) -Bytes 2-5: Reserved / diagnostics -``` - -SNR scaling in the kernel: `snr_value * 17` maps to the 0--65535 range. 100% signal quality corresponds to SNR >= `0x0F00`. - -## Firmware Version Handling - -The driver defines two version constants in `gp8psk-fe.h`: - -```c title="Firmware version thresholds" -#define GP8PSK_FW_REV1 0x020604 // v2.06.4 -#define GP8PSK_FW_REV2 0x020704 // v2.07.4 -``` - - - -Oldest firmware. No extended commands available. - - -v2.06.4 baseline. All standard commands operational. Uses `GET_SIGNAL_STRENGTH` for BER monitoring. - - -v2.07.4+. Enables additional code paths: -- `GET_DEMOD_STATUS` (`0x99`) for demod health check -- `INIT_DEMOD` (`0x9A`) for demod re-initialization -- `DELAY_COMMAND` (`0x9C`) for tuning acquisition delays -- Different signal quality calculation - - - -## Command Correlation with Firmware - -Not all vendor commands work on all firmware versions. The STALL behavior varies: - -| Command | v2.06 FW | v2.13 FW | Rev.2 FW | Custom v3.01 | -|---------|----------|----------|----------|-------------| -| `0x80` GET_8PSK_CONFIG | | | | | -| `0x86` TUNE_8PSK | | | | | -| `0x87` GET_SIGNAL_STRENGTH | | | | | -| `0x88` LOAD_BCM4500 | | | | | -| `0x99` GET_DEMOD_STATUS | | | | N/A | -| `0x9A` INIT_DEMOD | | | | N/A | -| `0x9C` DELAY_COMMAND | | | N/A | N/A | - - - -## Kernel Module Parameters - -The gp8psk module inherits standard DVB-USB parameters: - -| Parameter | Default | Description | -|-----------|---------|-------------| -| `debug` | 0 | Enable debug logging (bitmask) | -| `force_pid_filter` | 0 | Force PID filtering on/off | -| `generic_bulk_ctrl_endpoint` | `0x01` | Control endpoint | - -Enable verbose logging with: - -```bash -modprobe dvb_usb_gp8psk debug=0xff -``` - -Or at runtime: - -```bash -echo 0xff > /sys/module/dvb_usb_gp8psk/parameters/debug -``` - -## DVB Frontend Properties - -The gp8psk frontend (`gp8psk-fe.c`) registers with the following capabilities: - -```c title="Frontend info structure" -.name = "Genpix 8psk-to-USB2 DVB-S" -.frequency_min_hz = 800 * MHz -.frequency_max_hz = 2250 * MHz -.frequency_stepsize_hz = 100 * kHz -.symbol_rate_min = 256000 // 256 Ksps -.symbol_rate_max = 30000000 // 30 Msps -.caps = FE_CAN_INVERSION_AUTO | - FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | - FE_CAN_FEC_3_4 | FE_CAN_FEC_5_6 | - FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | - FE_CAN_QPSK -``` - - - -## Related Pages - -- [Vendor Commands](/usb/vendor-commands/) -- Complete command reference -- [Kernel FW01](/firmware/kernel-fw01/) -- Firmware format and loading analysis -- [DVB-S2 Investigation](/driver/dvb-s2/) -- Why DVB-S2 cannot be added -- [Custom Firmware v3.01](/firmware/custom-v301/) -- Open-source replacement with extended commands +--- +title: Linux Kernel Driver +description: Architecture and command analysis of the dvb_usb_gp8psk kernel module for Genpix satellite receivers. +--- + +import { Steps, Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +The `dvb_usb_gp8psk` module is a Linux kernel DVB-USB driver supporting multiple Genpix satellite receiver models. It communicates with the FX2 microcontroller via USB vendor control requests to manage tuning, demodulation, LNB control, DiSEqC switching, and transport stream capture. + +**Source files**: `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h`, `gp8psk-fe.c` + +## USB Device Table + +| USB ID | Device Name | cold_ids | warm_ids | FW01 Needed? | +|--------|-------------|----------|----------|-------------| +| `v09C0p0200` | Rev.1 Cold | Yes | -- | **Yes** | +| `v09C0p0201` | Rev.1 Warm | -- | Yes | No (FW02 needed) | +| `v09C0p0202` | Rev.2 | -- | Yes | No | +| `v09C0p0203` | SkyWalker-1 | -- | Yes | No | +| `v09C0p0204` | SkyWalker-1 (alt) | -- | Yes | No | +| `v09C0p0206` | SkyWalker CW3K | -- | Yes | No | + + + +## Driver Architecture + +The driver consists of three source files: + +| File | Purpose | +|------|---------| +| `gp8psk.c` | USB device management, firmware loading, power control, vendor command wrappers | +| `gp8psk.h` | Vendor command constants, firmware version thresholds, USB PID definitions | +| `gp8psk-fe.c` | DVB frontend implementation: tuning, signal monitoring, LNB/DiSEqC callbacks | + +The driver registers with the DVB-USB framework (`dvb_usb_device_properties`) which handles: +- USB device enumeration and firmware download (for cold devices) +- DVB adapter and frontend creation +- URB management for bulk transport stream capture +- Power management callbacks + +## USB Transfer Parameters + +```c title="Driver USB configuration" +gp8psk_properties { + .usb_ctrl = CYPRESS_FX2; + .firmware = "dvb-usb-gp8psk-01.fw"; + .num_adapters = 1; + .generic_bulk_ctrl_endpoint = 0x01; + // Streaming: + .endpoint = 0x82; // IN bulk + .stream = USB_BULK; + .count = 7; // URBs + .buffersize = 8192; // bytes per URB +} +``` + +The driver allocates 7 URBs of 8192 bytes each (56 KB total) for transport stream reception on endpoint `0x82` (IN bulk). Vendor commands use endpoint `0x01` with a 2000 ms timeout. + +### Retry Logic + +IN operations retry up to 3 times if partial data is received. The command buffer is 80 bytes maximum: + +```c title="Vendor command wrapper" +static int gp8psk_usb_in_op(struct dvb_usb_device *d, + u8 req, u16 value, u16 index, u8 *b, int blen) +{ + int ret; + int try; + for (try = 0; try < 3; try++) { + ret = usb_control_msg(d->udev, + usb_rcvctrlpipe(d->udev, 0), + req, USB_TYPE_VENDOR | USB_DIR_IN, + value, index, b, blen, 2000); + if (ret == blen) break; + } + return ret; +} +``` + +## Complete Vendor Command Map + +| Cmd | Name | Dir | wValue | wLength | Purpose | +|-----|------|-----|--------|---------|---------| +| `0x80` | GET_8PSK_CONFIG | IN | `0x0000` | 1 | Read config status byte | +| `0x81` | SET_8PSK_CONFIG | OUT | varies | 0 | STALL (not implemented) | +| `0x83` | I2C_WRITE | OUT | dev_addr | N | Write to BCM4500 via I2C | +| `0x84` | I2C_READ | IN | dev_addr | N | Read from BCM4500 via I2C | +| `0x85` | ARM_TRANSFER | OUT | 0/1 | 0 | Start/stop TS streaming | +| `0x86` | TUNE_8PSK | OUT | `0x0000` | 10 | Send tuning parameters | +| `0x87` | GET_SIGNAL_STRENGTH | IN | `0x0000` | 6 | Read SNR values | +| `0x88` | LOAD_BCM4500 | OUT | 1 | 0 | Initiate demod FW download | +| `0x89` | BOOT_8PSK | IN | 0/1 | 1 | Power on/off demodulator | +| `0x8A` | START_INTERSIL | IN | 0/1 | 1 | Enable/disable LNB supply | +| `0x8B` | SET_LNB_VOLTAGE | OUT | 0/1 | 0 | Set 13V (0) or 18V (1) | +| `0x8C` | SET_22KHZ_TONE | OUT | 0/1 | 0 | Enable/disable 22 kHz tone | +| `0x8D` | SEND_DISEQC | OUT | msg[0] | 3-6 | Send DiSEqC message | +| `0x8E` | SET_DVB_MODE | OUT | 1 | 0 | Enable DVB-S mode | +| `0x8F` | SET_DN_SWITCH | OUT | cmd | 0 | Legacy Dish switch command | +| `0x90` | GET_SIGNAL_LOCK | IN | `0x0000` | 1 | Read lock status | +| `0x92` | GET_FW_VERS | IN | `0x0000` | 6 | Read firmware version | +| `0x94` | USE_EXTRA_VOLT | OUT | 0/1 | 0 | Enable +1V LNB boost | +| `0x95` | GET_FPGA_VERS | IN | `0x0000` | 1 | Read hardware platform ID | +| `0x99` | GET_DEMOD_STATUS | IN | `0x0000` | 1 | Read BCM4500 reg 0xF9 (v2.13+) | +| `0x9A` | INIT_DEMOD | OUT | `0x0000` | 0 | Re-init demodulator (v2.13+) | +| `0x9C` | DELAY_COMMAND | OUT | param | 0 | Tuning delay with polling (v2.13+) | +| `0x9D` | CW3K_INIT | OUT | 0/1 | 0 | CW3K model initialization | + +## Configuration Status Byte + +`GET_8PSK_CONFIG` (`0x80`) returns a bit-mapped status register: + +``` +Bit 0 (0x01): bm8pskStarted - Device booted and running +Bit 1 (0x02): bm8pskFW_Loaded - BCM4500 firmware loaded +Bit 2 (0x04): bmIntersilOn - LNB power supply enabled +Bit 3 (0x08): bmDVBmode - DVB mode enabled +Bit 4 (0x10): bm22kHz - 22 kHz tone active +Bit 5 (0x20): bmSEL18V - 18V LNB voltage selected +Bit 6 (0x40): bmDCtuned - DC offset tuning complete +Bit 7 (0x80): bmArmed - MPEG-2 stream transfer armed +``` + +## Boot Sequence + + + +1. **Read config** (`GET_8PSK_CONFIG 0x80`): Check bit 0 (`bm8pskStarted`) + +2. **Boot device** if not started: Send `BOOT_8PSK 0x89` with `wValue=1`, then read firmware version via `GET_FW_VERS 0x92` + +3. **Load BCM4500 firmware** if bit 1 not set: Only for Rev.1 Warm (PID `0x0201`). Send `LOAD_BCM4500 0x88` followed by firmware chunks. Skipped for SkyWalker-1 (bit already set from EEPROM boot). + +4. **Enable LNB** if bit 2 not set: Send `START_INTERSIL 0x8A` with `wValue=1` + +5. **Set DVB mode**: Send `SET_DVB_MODE 0x8E` with `wValue=1` (STALL on some revisions) + +6. **Cancel pending stream**: Send `ARM_TRANSFER 0x85` with `wValue=0` + +7. **Ready for tuning** + + + +## Tuning Flow + +```c title="10-byte tuning payload" +Bytes 0-3: Symbol Rate (u32 LE, in sps) +Bytes 4-7: Frequency (u32 LE, in kHz) +Byte 8: Modulation (0-9, see modulation types) +Byte 9: FEC Rate (index into firmware FEC table) +``` + +### Modulation Types + +| Value | Constant | Mode | +|-------|----------|------| +| 0 | `ADV_MOD_DVB_QPSK` | DVB-S QPSK | +| 1 | `ADV_MOD_TURBO_QPSK` | Turbo QPSK | +| 2 | `ADV_MOD_TURBO_8PSK` | Turbo 8PSK | +| 3 | `ADV_MOD_TURBO_16QAM` | Turbo 16QAM | +| 4 | `ADV_MOD_DCII_C_QPSK` | Digicipher II Combo | +| 5 | `ADV_MOD_DCII_I_QPSK` | Digicipher II I-stream | +| 6 | `ADV_MOD_DCII_Q_QPSK` | Digicipher II Q-stream | +| 7 | `ADV_MOD_DCII_C_OQPSK` | Digicipher II Offset QPSK | +| 8 | `ADV_MOD_DSS_QPSK` | DSS/DIRECTV QPSK | +| 9 | `ADV_MOD_DVB_BPSK` | DVB-S BPSK | + +### Signal Quality + +`GET_SIGNAL_STRENGTH` (`0x87`) returns 6 bytes: + +``` +Bytes 0-1: SNR value (u16 LE, in dBu*256 units) +Bytes 2-5: Reserved / diagnostics +``` + +SNR scaling in the kernel: `snr_value * 17` maps to the 0--65535 range. 100% signal quality corresponds to SNR >= `0x0F00`. + +## Firmware Version Handling + +The driver defines two version constants in `gp8psk-fe.h`: + +```c title="Firmware version thresholds" +#define GP8PSK_FW_REV1 0x020604 // v2.06.4 +#define GP8PSK_FW_REV2 0x020704 // v2.07.4 +``` + + + +Oldest firmware. No extended commands available. + + +v2.06.4 baseline. All standard commands operational. Uses `GET_SIGNAL_STRENGTH` for BER monitoring. + + +v2.07.4+. Enables additional code paths: +- `GET_DEMOD_STATUS` (`0x99`) for demod health check +- `INIT_DEMOD` (`0x9A`) for demod re-initialization +- `DELAY_COMMAND` (`0x9C`) for tuning acquisition delays +- Different signal quality calculation + + + +## Command Correlation with Firmware + +Not all vendor commands work on all firmware versions. The STALL behavior varies: + +| Command | v2.06 FW | v2.13 FW | Rev.2 FW | Custom v3.01 | +|---------|----------|----------|----------|-------------| +| `0x80` GET_8PSK_CONFIG | | | | | +| `0x86` TUNE_8PSK | | | | | +| `0x87` GET_SIGNAL_STRENGTH | | | | | +| `0x88` LOAD_BCM4500 | | | | | +| `0x99` GET_DEMOD_STATUS | | | | N/A | +| `0x9A` INIT_DEMOD | | | | N/A | +| `0x9C` DELAY_COMMAND | | | N/A | N/A | + + + +## Kernel Module Parameters + +The gp8psk module inherits standard DVB-USB parameters: + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `debug` | 0 | Enable debug logging (bitmask) | +| `force_pid_filter` | 0 | Force PID filtering on/off | +| `generic_bulk_ctrl_endpoint` | `0x01` | Control endpoint | + +Enable verbose logging with: + +```bash +modprobe dvb_usb_gp8psk debug=0xff +``` + +Or at runtime: + +```bash +echo 0xff > /sys/module/dvb_usb_gp8psk/parameters/debug +``` + +## DVB Frontend Properties + +The gp8psk frontend (`gp8psk-fe.c`) registers with the following capabilities: + +```c title="Frontend info structure" +.name = "Genpix 8psk-to-USB2 DVB-S" +.frequency_min_hz = 800 * MHz +.frequency_max_hz = 2250 * MHz +.frequency_stepsize_hz = 100 * kHz +.symbol_rate_min = 256000 // 256 Ksps +.symbol_rate_max = 30000000 // 30 Msps +.caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | FE_CAN_FEC_5_6 | + FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK +``` + + + +## Related Pages + +- [Vendor Commands](/usb/vendor-commands/) -- Complete command reference +- [Kernel FW01](/firmware/kernel-fw01/) -- Firmware format and loading analysis +- [DVB-S2 Investigation](/driver/dvb-s2/) -- Why DVB-S2 cannot be added +- [Custom Firmware v3.01](/firmware/custom-v301/) -- Open-source replacement with extended commands diff --git a/site/src/content/docs/firmware/custom-v301.mdx b/site/src/content/docs/firmware/custom-v301.mdx index 548ac4a..612fd52 100644 --- a/site/src/content/docs/firmware/custom-v301.mdx +++ b/site/src/content/docs/firmware/custom-v301.mdx @@ -1,291 +1,291 @@ ---- -title: Custom Firmware v3.01 -description: Open-source SDCC + fx2lib replacement firmware with spectrum sweep, blind scan, raw demod access, and hardware diagnostics. ---- - -import { Steps, Badge, Aside, Tabs, TabItem, FileTree } from '@astrojs/starlight/components'; - -The custom firmware is an open-source replacement for the stock SkyWalker-1 FX2 firmware, built with the SDCC compiler and fx2lib library. It implements all stock vendor commands for kernel driver compatibility and adds diagnostic, spectrum sweep, blind scan, and raw register access capabilities. - -See also: [v3.02](/firmware/custom-v302/) adds signal monitoring, tune-and-measure, and batch register read commands on top of this base. - -## Project Structure - - -- firmware/ - - skywalker1.c Main firmware source (1351 lines) - - Makefile SDCC build rules - - skywalker1.ihx Compiled Intel HEX output -- tools/ - - fw_load.py FX2 RAM loader utility - - eeprom_flash.py EEPROM flash tool - - -## Architecture Overview - -| Property | Value | -|----------|-------| -| Toolchain | SDCC 4.x + fx2lib | -| Target | Cypress CY7C68013A (FX2LP) | -| Load method | RAM upload via `fw_load.py` (USB 0xA0 vendor request) | -| Binary size | ~3 KB | -| Source lines | 1351 | -| DiSEqC data pin | P0.7 (matches v2.06 hardware) | -| BCM4500 I2C address | `0x08` (7-bit); wire address `0x10`/`0x11` | - - - -## Stock-Compatible Commands - -All stock vendor commands (`0x80`--`0x94`) are implemented for full compatibility with the Linux `dvb_usb_gp8psk` kernel driver: - -| Command | Name | Implementation | -|---------|------|---------------| -| `0x80` | GET_8PSK_CONFIG | Returns `config_status` byte (1 byte) | -| `0x85` | ARM_TRANSFER | Calls `gpif_start()` / `gpif_stop()` | -| `0x86` | TUNE_8PSK | Parses 10-byte EP0 payload, programs BCM4500 | -| `0x87` | GET_SIGNAL_STRENGTH | Reads 6 BCM4500 indirect registers | -| `0x89` | BOOT_8PSK | Full BCM4500 boot sequence (see below) | -| `0x8A` | START_INTERSIL | Enables/disables LNB power supply | -| `0x8B` | SET_LNB_VOLTAGE | Sets P0.4 for 13V/18V selection | -| `0x8C` | SET_22KHZ_TONE | Sets P0.3 for 22 kHz oscillator gate | -| `0x8D` | SEND_DISEQC | DiSEqC tone burst via Timer2 bit-bang | -| `0x90` | GET_SIGNAL_LOCK | Reads BCM4500 lock register `0xA4` | -| `0x92` | GET_FW_VERS | Returns version `0x030100`, build date | -| `0x94` | USE_EXTRA_VOLT | Writes `0x62`/`0x6A` to XRAM `0xE0B6` | - -## Custom Diagnostic Commands - -Seven new vendor commands (`0xB0`--`0xB6`) extend the firmware with capabilities absent from all stock versions: - -| Command | Name | Direction | Payload | Purpose | -|---------|------|-----------|---------|---------| -| `0xB0` | SPECTRUM_SWEEP | OUT+Bulk | 10 bytes EP0 | Step through frequencies, return power readings via EP2 | -| `0xB1` | RAW_DEMOD_READ | IN | 2 bytes | Read arbitrary BCM4500 indirect register | -| `0xB2` | RAW_DEMOD_WRITE | OUT | 3 bytes | Write arbitrary BCM4500 indirect register | -| `0xB3` | BLIND_SCAN | OUT+EP0 | 16 bytes EP0 | Sweep symbol rates at a frequency, report lock | -| `0xB4` | I2C_SCAN | IN | N bytes | Scan I2C bus for responsive devices | -| `0xB5` | GET_BOOT_STAGE | IN | 2 bytes | Read `config_status` + `boot_stage` | -| `0xB6` | GET_GPIO_STATE | IN | 3 bytes | Read IOA, IOB, IOD port registers | - -### Spectrum Sweep (0xB0) - -Steps through frequencies from start to stop, reading BCM4500 signal energy at each step. Results are packed as u16 LE values into EP2 bulk endpoint. - -```c title="Spectrum sweep EP0 payload (10 bytes)" -EP0BUF[0..3] start_freq (u32 LE, kHz) -EP0BUF[4..7] stop_freq (u32 LE, kHz) -EP0BUF[8..9] step_khz (u16 LE, default 1000 if 0) -``` - -At each step, the firmware programs the BCM4500 frequency register via indirect write, waits 10 ms for settling, reads the SNR register pair, and packs the result into EP2 FIFO. When the buffer reaches 512 bytes, it is committed to the host. - -### Blind Scan (0xB3) - -Sweeps symbol rates from `sr_min` to `sr_max` at a given frequency, checking for signal lock at each step. - -```c title="Blind scan EP0 payload (16 bytes)" -EP0BUF[0..3] freq_khz (u32 LE) -EP0BUF[4..7] sr_min (u32 LE, sps) -EP0BUF[8..11] sr_max (u32 LE, sps) -EP0BUF[12..15] sr_step (u32 LE, sps, default 1000000 if 0) -``` - -Returns 8 bytes on lock (`freq_khz[4] + sr_locked[4]`), or 1 byte `0x00` if no lock found. - -### Raw Demod Access (0xB1 / 0xB2) - -Direct access to any BCM4500 indirect register, bypassing the stock firmware's limited register set: - -```c title="Raw demod read (0xB1)" -// wValue = register page, wIndex = register number -// Returns 1 byte in EP0 -bcm_indirect_read(page, &val); -EP0BUF[0] = val; -``` - -```c title="Raw demod write (0xB2)" -// wValue = register page, wIndex = register number -// EP0 data = 1 byte value -bcm_indirect_write(page, val); -``` - -## BCM4500 Boot Sequence - -The `bcm4500_boot()` function replicates the stock firmware's initialization with added diagnostic instrumentation. The `boot_stage` variable tracks progress for debugging failed boots. - - - -1. **GPIO setup** (`boot_stage = 1`): Set P3.7/P3.6/P3.5 HIGH (control lines idle), assert BCM4500 RESET (P0.5 LOW) - -2. **Power on** (`boot_stage = 2`): Enable power supply (P0.1 HIGH, P0.2 LOW), wait 30 ms for settling, release RESET (P0.5 HIGH), wait 50 ms for BCM4500 POR - -3. **I2C probe** (`boot_stage = 3`): Read BCM4500 status register `0xA2` to verify the chip is alive on the I2C bus - -4. **Init block 0** (`boot_stage = 4`): Write 7-byte configuration block to BCM4500 page 0 indirect registers - -5. **Init block 1** (`boot_stage = 5`): Write 8-byte configuration block - -6. **Init block 2** (`boot_stage = 6`): Write 3-byte configuration block - -7. **Success** (`boot_stage = 0xFF`): Set `BM_STARTED | BM_FW_LOADED` in config status - - - -### BCM4500 Init Data - -Three initialization blocks extracted from stock v2.06 firmware (`FUN_CODE_0ddd`): - -```c title="BCM4500 register initialization data" -static const __code BYTE bcm_init_block0[] = { - 0x06, 0x0b, 0x17, 0x38, 0x9f, 0xd9, 0x80 -}; -static const __code BYTE bcm_init_block1[] = { - 0x07, 0x09, 0x39, 0x4f, 0x00, 0x65, 0xb7, 0x10 -}; -static const __code BYTE bcm_init_block2[] = { - 0x0f, 0x0c, 0x09 -}; -``` - -Each block is written to BCM4500 page 0 via the indirect register protocol: page select to `0xA6`, data bytes to `0xA7`, trailing zero to `0xA7`, commit `0x03` to `0xA8`, then poll for completion. - -### Debug Boot Modes - -The BOOT_8PSK command (`0x89`) accepts debug wValue parameters that execute partial boot sequences for incremental hardware debugging: - -| wValue | Stage | What It Does | Success Marker | -|--------|-------|-------------|----------------| -| `0x80` | None | No-op, return current state | -- | -| `0x81` | GPIO only | GPIO setup + power + reset, no I2C | `0xA1` | -| `0x82` | GPIO + probe | GPIO + I2C read of status register | `0xA2` | -| `0x83` | GPIO + probe + block 0 | GPIO + I2C + first init block | `0xA3` | -| `0x84` | I2C only | Probe without GPIO (chip must be powered) | `0xA4` | -| `0x85` | GPIO + probe (no bus reset) | Same as `0x82` without I2CS bmSTOP | `0xA5` | -| `0x01` | Full boot | Complete `bcm4500_boot()` sequence | `0xFF` | -| `0x00` | Shutdown | Power off BCM4500 | -- | - - - -## I2C Implementation - -The custom firmware implements I2C from scratch rather than using fx2lib's I2C functions, providing full timeout protection: - -```c title="I2C timeout constant" -#define I2C_TIMEOUT 6000 // ~5ms at 48MHz (4 clocks/cycle, ~12 MIPS) -``` - -Key I2C functions: - -| Function | Purpose | -|----------|---------| -| `i2c_wait_done()` | Poll `I2CS.bmDONE` with 6000-count timeout | -| `i2c_wait_stop()` | Poll `I2CS.bmSTOP` clear with timeout | -| `i2c_combined_read()` | Write-then-read with repeated START (no intermediate STOP) | -| `i2c_write_timeout()` | Single-byte write with timeout on each phase | -| `i2c_write_multi_timeout()` | Multi-byte write with timeout | - - - -### BCM4500 Register Access - -The BCM4500 uses an indirect register protocol through three I2C registers: - -| Register | Address | Purpose | -|----------|---------|---------| -| BCM_REG_PAGE | `0xA6` | Page/register select | -| BCM_REG_DATA | `0xA7` | Data read/write | -| BCM_REG_CMD | `0xA8` | Command trigger (`0x01` = read, `0x03` = write) | - -```c title="Indirect register read sequence" -// 1. Write [page, 0x00, 0x01] to A6/A7/A8 in one I2C transaction -// 2. Wait for command completion (poll A8 bit 0 == 0) -// 3. Read result from A7 -``` - -## GPIF Streaming - -Transport stream data from the BCM4500 flows through the FX2's GPIF engine into USB endpoint EP2: - -```c title="GPIF configuration" -IFCONFIG = 0xEE; // Internal 48MHz, GPIF master, async, clock output -EP2FIFOCFG = 0x0C; // AUTOIN, ZEROLENIN, 8-bit -FLOWSTATE |= 0x09; // Enable flow state + FS[3] -GPIFTCB3 = 0x80; // Transaction count = 0x80000000 (effectively infinite) -``` - -The `gpif_start()` function arms the GPIF for continuous read into EP2, while `gpif_stop()` flushes the FIFO and de-asserts the BCM4500 control lines on P3. - -## GPIO Pin Map - -```c title="GPIO pin definitions (v2.06 hardware)" -#define PIN_PWR_EN 0x02 // P0.1 -- power supply enable -#define PIN_PWR_DIS 0x04 // P0.2 -- power supply disable -#define PIN_22KHZ 0x08 // P0.3 -- 22kHz oscillator gate -#define PIN_LNB_VOLT 0x10 // P0.4 -- LNB voltage select -#define PIN_BCM_RESET 0x20 // P0.5 -- BCM4500 hardware reset -#define PIN_DISEQC 0x80 // P0.7 -- DiSEqC data -``` - -Initial state after `TD_Init()`: -- `IOA = 0x84` (P0.7 HIGH, P0.2 HIGH -- power disabled, streaming off) -- `OEA = 0xBE` (P0.1 through P0.5 and P0.7 as outputs) - -## Differences from Stock Firmware - -| Feature | Stock v2.06 | Custom v3.01 | -|---------|-------------|--------------| -| Toolchain | Unknown (proprietary) | SDCC + fx2lib (open source) | -| I2C timeout | None (infinite spin) | 6000-count (~5 ms) | -| Boot diagnostics | None | Incremental debug modes | -| Custom commands | None | 7 (`0xB0`--`0xB6`) | -| Spectrum sweep | Not possible | `0xB0` via EP2 bulk | -| Blind scan | Not possible | `0xB3` with SR sweep | -| Raw register access | Not possible | `0xB1`/`0xB2` | -| I2C bus scan | Not possible | `0xB4` | -| GPIO read | Not possible | `0xB6` | -| Anti-tampering | Present (v2.13) | Removed | -| Source available | No | Yes (`firmware/skywalker1.c`) | - -See the [v3.02 comparison table](/firmware/custom-v302/#what-changed-from-v301) for signal monitoring and batch register improvements added on top of v3.01. - -## Tuning Implementation - -The `do_tune()` function parses the same 10-byte EP0 payload as the stock firmware: - -```c title="Tune command payload parsing" -// Byte-reverse symbol rate and frequency from LE to BE -for (i = 0; i < 4; i++) { - tune_data[i] = EP0BUF[3 - i]; // Symbol rate (BE) - tune_data[4 + i] = EP0BUF[7 - i]; // Frequency (BE) -} -tune_data[8] = EP0BUF[8]; // Modulation type (0-9) -tune_data[9] = EP0BUF[9]; // FEC index -tune_data[10] = 0x10; // Demod mode (standard) -tune_data[11] = 0x00; // Turbo flag -``` - -Modulation-specific handling: -- Modulation types 1--3 (turbo modes): Set turbo flag `tune_data[11] = 0x01` -- Modulation type 5: DCII I-stream, demod mode `0x12` -- Modulation type 6: DCII Q-stream, demod mode `0x16` -- Modulation type 7: DCII offset QPSK, demod mode `0x11` +--- +title: Custom Firmware v3.01 +description: Open-source SDCC + fx2lib replacement firmware with spectrum sweep, blind scan, raw demod access, and hardware diagnostics. +--- + +import { Steps, Badge, Aside, Tabs, TabItem, FileTree } from '@astrojs/starlight/components'; + +The custom firmware is an open-source replacement for the stock SkyWalker-1 FX2 firmware, built with the SDCC compiler and fx2lib library. It implements all stock vendor commands for kernel driver compatibility and adds diagnostic, spectrum sweep, blind scan, and raw register access capabilities. + +See also: [v3.02](/firmware/custom-v302/) adds signal monitoring, tune-and-measure, and batch register read commands on top of this base. + +## Project Structure + + +- firmware/ + - skywalker1.c Main firmware source (1351 lines) + - Makefile SDCC build rules + - skywalker1.ihx Compiled Intel HEX output +- tools/ + - fw_load.py FX2 RAM loader utility + - eeprom_flash.py EEPROM flash tool + + +## Architecture Overview + +| Property | Value | +|----------|-------| +| Toolchain | SDCC 4.x + fx2lib | +| Target | Cypress CY7C68013A (FX2LP) | +| Load method | RAM upload via `fw_load.py` (USB 0xA0 vendor request) | +| Binary size | ~3 KB | +| Source lines | 1351 | +| DiSEqC data pin | P0.7 (matches v2.06 hardware) | +| BCM4500 I2C address | `0x08` (7-bit); wire address `0x10`/`0x11` | + + + +## Stock-Compatible Commands + +All stock vendor commands (`0x80`--`0x94`) are implemented for full compatibility with the Linux `dvb_usb_gp8psk` kernel driver: + +| Command | Name | Implementation | +|---------|------|---------------| +| `0x80` | GET_8PSK_CONFIG | Returns `config_status` byte (1 byte) | +| `0x85` | ARM_TRANSFER | Calls `gpif_start()` / `gpif_stop()` | +| `0x86` | TUNE_8PSK | Parses 10-byte EP0 payload, programs BCM4500 | +| `0x87` | GET_SIGNAL_STRENGTH | Reads 6 BCM4500 indirect registers | +| `0x89` | BOOT_8PSK | Full BCM4500 boot sequence (see below) | +| `0x8A` | START_INTERSIL | Enables/disables LNB power supply | +| `0x8B` | SET_LNB_VOLTAGE | Sets P0.4 for 13V/18V selection | +| `0x8C` | SET_22KHZ_TONE | Sets P0.3 for 22 kHz oscillator gate | +| `0x8D` | SEND_DISEQC | DiSEqC tone burst via Timer2 bit-bang | +| `0x90` | GET_SIGNAL_LOCK | Reads BCM4500 lock register `0xA4` | +| `0x92` | GET_FW_VERS | Returns version `0x030100`, build date | +| `0x94` | USE_EXTRA_VOLT | Writes `0x62`/`0x6A` to XRAM `0xE0B6` | + +## Custom Diagnostic Commands + +Seven new vendor commands (`0xB0`--`0xB6`) extend the firmware with capabilities absent from all stock versions: + +| Command | Name | Direction | Payload | Purpose | +|---------|------|-----------|---------|---------| +| `0xB0` | SPECTRUM_SWEEP | OUT+Bulk | 10 bytes EP0 | Step through frequencies, return power readings via EP2 | +| `0xB1` | RAW_DEMOD_READ | IN | 2 bytes | Read arbitrary BCM4500 indirect register | +| `0xB2` | RAW_DEMOD_WRITE | OUT | 3 bytes | Write arbitrary BCM4500 indirect register | +| `0xB3` | BLIND_SCAN | OUT+EP0 | 16 bytes EP0 | Sweep symbol rates at a frequency, report lock | +| `0xB4` | I2C_SCAN | IN | N bytes | Scan I2C bus for responsive devices | +| `0xB5` | GET_BOOT_STAGE | IN | 2 bytes | Read `config_status` + `boot_stage` | +| `0xB6` | GET_GPIO_STATE | IN | 3 bytes | Read IOA, IOB, IOD port registers | + +### Spectrum Sweep (0xB0) + +Steps through frequencies from start to stop, reading BCM4500 signal energy at each step. Results are packed as u16 LE values into EP2 bulk endpoint. + +```c title="Spectrum sweep EP0 payload (10 bytes)" +EP0BUF[0..3] start_freq (u32 LE, kHz) +EP0BUF[4..7] stop_freq (u32 LE, kHz) +EP0BUF[8..9] step_khz (u16 LE, default 1000 if 0) +``` + +At each step, the firmware programs the BCM4500 frequency register via indirect write, waits 10 ms for settling, reads the SNR register pair, and packs the result into EP2 FIFO. When the buffer reaches 512 bytes, it is committed to the host. + +### Blind Scan (0xB3) + +Sweeps symbol rates from `sr_min` to `sr_max` at a given frequency, checking for signal lock at each step. + +```c title="Blind scan EP0 payload (16 bytes)" +EP0BUF[0..3] freq_khz (u32 LE) +EP0BUF[4..7] sr_min (u32 LE, sps) +EP0BUF[8..11] sr_max (u32 LE, sps) +EP0BUF[12..15] sr_step (u32 LE, sps, default 1000000 if 0) +``` + +Returns 8 bytes on lock (`freq_khz[4] + sr_locked[4]`), or 1 byte `0x00` if no lock found. + +### Raw Demod Access (0xB1 / 0xB2) + +Direct access to any BCM4500 indirect register, bypassing the stock firmware's limited register set: + +```c title="Raw demod read (0xB1)" +// wValue = register page, wIndex = register number +// Returns 1 byte in EP0 +bcm_indirect_read(page, &val); +EP0BUF[0] = val; +``` + +```c title="Raw demod write (0xB2)" +// wValue = register page, wIndex = register number +// EP0 data = 1 byte value +bcm_indirect_write(page, val); +``` + +## BCM4500 Boot Sequence + +The `bcm4500_boot()` function replicates the stock firmware's initialization with added diagnostic instrumentation. The `boot_stage` variable tracks progress for debugging failed boots. + + + +1. **GPIO setup** (`boot_stage = 1`): Set P3.7/P3.6/P3.5 HIGH (control lines idle), assert BCM4500 RESET (P0.5 LOW) + +2. **Power on** (`boot_stage = 2`): Enable power supply (P0.1 HIGH, P0.2 LOW), wait 30 ms for settling, release RESET (P0.5 HIGH), wait 50 ms for BCM4500 POR + +3. **I2C probe** (`boot_stage = 3`): Read BCM4500 status register `0xA2` to verify the chip is alive on the I2C bus + +4. **Init block 0** (`boot_stage = 4`): Write 7-byte configuration block to BCM4500 page 0 indirect registers + +5. **Init block 1** (`boot_stage = 5`): Write 8-byte configuration block + +6. **Init block 2** (`boot_stage = 6`): Write 3-byte configuration block + +7. **Success** (`boot_stage = 0xFF`): Set `BM_STARTED | BM_FW_LOADED` in config status + + + +### BCM4500 Init Data + +Three initialization blocks extracted from stock v2.06 firmware (`FUN_CODE_0ddd`): + +```c title="BCM4500 register initialization data" +static const __code BYTE bcm_init_block0[] = { + 0x06, 0x0b, 0x17, 0x38, 0x9f, 0xd9, 0x80 +}; +static const __code BYTE bcm_init_block1[] = { + 0x07, 0x09, 0x39, 0x4f, 0x00, 0x65, 0xb7, 0x10 +}; +static const __code BYTE bcm_init_block2[] = { + 0x0f, 0x0c, 0x09 +}; +``` + +Each block is written to BCM4500 page 0 via the indirect register protocol: page select to `0xA6`, data bytes to `0xA7`, trailing zero to `0xA7`, commit `0x03` to `0xA8`, then poll for completion. + +### Debug Boot Modes + +The BOOT_8PSK command (`0x89`) accepts debug wValue parameters that execute partial boot sequences for incremental hardware debugging: + +| wValue | Stage | What It Does | Success Marker | +|--------|-------|-------------|----------------| +| `0x80` | None | No-op, return current state | -- | +| `0x81` | GPIO only | GPIO setup + power + reset, no I2C | `0xA1` | +| `0x82` | GPIO + probe | GPIO + I2C read of status register | `0xA2` | +| `0x83` | GPIO + probe + block 0 | GPIO + I2C + first init block | `0xA3` | +| `0x84` | I2C only | Probe without GPIO (chip must be powered) | `0xA4` | +| `0x85` | GPIO + probe (no bus reset) | Same as `0x82` without I2CS bmSTOP | `0xA5` | +| `0x01` | Full boot | Complete `bcm4500_boot()` sequence | `0xFF` | +| `0x00` | Shutdown | Power off BCM4500 | -- | + + + +## I2C Implementation + +The custom firmware implements I2C from scratch rather than using fx2lib's I2C functions, providing full timeout protection: + +```c title="I2C timeout constant" +#define I2C_TIMEOUT 6000 // ~5ms at 48MHz (4 clocks/cycle, ~12 MIPS) +``` + +Key I2C functions: + +| Function | Purpose | +|----------|---------| +| `i2c_wait_done()` | Poll `I2CS.bmDONE` with 6000-count timeout | +| `i2c_wait_stop()` | Poll `I2CS.bmSTOP` clear with timeout | +| `i2c_combined_read()` | Write-then-read with repeated START (no intermediate STOP) | +| `i2c_write_timeout()` | Single-byte write with timeout on each phase | +| `i2c_write_multi_timeout()` | Multi-byte write with timeout | + + + +### BCM4500 Register Access + +The BCM4500 uses an indirect register protocol through three I2C registers: + +| Register | Address | Purpose | +|----------|---------|---------| +| BCM_REG_PAGE | `0xA6` | Page/register select | +| BCM_REG_DATA | `0xA7` | Data read/write | +| BCM_REG_CMD | `0xA8` | Command trigger (`0x01` = read, `0x03` = write) | + +```c title="Indirect register read sequence" +// 1. Write [page, 0x00, 0x01] to A6/A7/A8 in one I2C transaction +// 2. Wait for command completion (poll A8 bit 0 == 0) +// 3. Read result from A7 +``` + +## GPIF Streaming + +Transport stream data from the BCM4500 flows through the FX2's GPIF engine into USB endpoint EP2: + +```c title="GPIF configuration" +IFCONFIG = 0xEE; // Internal 48MHz, GPIF master, async, clock output +EP2FIFOCFG = 0x0C; // AUTOIN, ZEROLENIN, 8-bit +FLOWSTATE |= 0x09; // Enable flow state + FS[3] +GPIFTCB3 = 0x80; // Transaction count = 0x80000000 (effectively infinite) +``` + +The `gpif_start()` function arms the GPIF for continuous read into EP2, while `gpif_stop()` flushes the FIFO and de-asserts the BCM4500 control lines on P3. + +## GPIO Pin Map + +```c title="GPIO pin definitions (v2.06 hardware)" +#define PIN_PWR_EN 0x02 // P0.1 -- power supply enable +#define PIN_PWR_DIS 0x04 // P0.2 -- power supply disable +#define PIN_22KHZ 0x08 // P0.3 -- 22kHz oscillator gate +#define PIN_LNB_VOLT 0x10 // P0.4 -- LNB voltage select +#define PIN_BCM_RESET 0x20 // P0.5 -- BCM4500 hardware reset +#define PIN_DISEQC 0x80 // P0.7 -- DiSEqC data +``` + +Initial state after `TD_Init()`: +- `IOA = 0x84` (P0.7 HIGH, P0.2 HIGH -- power disabled, streaming off) +- `OEA = 0xBE` (P0.1 through P0.5 and P0.7 as outputs) + +## Differences from Stock Firmware + +| Feature | Stock v2.06 | Custom v3.01 | +|---------|-------------|--------------| +| Toolchain | Unknown (proprietary) | SDCC + fx2lib (open source) | +| I2C timeout | None (infinite spin) | 6000-count (~5 ms) | +| Boot diagnostics | None | Incremental debug modes | +| Custom commands | None | 7 (`0xB0`--`0xB6`) | +| Spectrum sweep | Not possible | `0xB0` via EP2 bulk | +| Blind scan | Not possible | `0xB3` with SR sweep | +| Raw register access | Not possible | `0xB1`/`0xB2` | +| I2C bus scan | Not possible | `0xB4` | +| GPIO read | Not possible | `0xB6` | +| Anti-tampering | Present (v2.13) | Removed | +| Source available | No | Yes (`firmware/skywalker1.c`) | + +See the [v3.02 comparison table](/firmware/custom-v302/#what-changed-from-v301) for signal monitoring and batch register improvements added on top of v3.01. + +## Tuning Implementation + +The `do_tune()` function parses the same 10-byte EP0 payload as the stock firmware: + +```c title="Tune command payload parsing" +// Byte-reverse symbol rate and frequency from LE to BE +for (i = 0; i < 4; i++) { + tune_data[i] = EP0BUF[3 - i]; // Symbol rate (BE) + tune_data[4 + i] = EP0BUF[7 - i]; // Frequency (BE) +} +tune_data[8] = EP0BUF[8]; // Modulation type (0-9) +tune_data[9] = EP0BUF[9]; // FEC index +tune_data[10] = 0x10; // Demod mode (standard) +tune_data[11] = 0x00; // Turbo flag +``` + +Modulation-specific handling: +- Modulation types 1--3 (turbo modes): Set turbo flag `tune_data[11] = 0x01` +- Modulation type 5: DCII I-stream, demod mode `0x12` +- Modulation type 6: DCII Q-stream, demod mode `0x16` +- Modulation type 7: DCII offset QPSK, demod mode `0x11` diff --git a/site/src/content/docs/firmware/custom-v302.mdx b/site/src/content/docs/firmware/custom-v302.mdx index 3450c57..0dfbb79 100644 --- a/site/src/content/docs/firmware/custom-v302.mdx +++ b/site/src/content/docs/firmware/custom-v302.mdx @@ -1,80 +1,80 @@ ---- -title: Custom Firmware v3.02 -description: Signal monitoring, tune-and-measure, and batch register read commands for real-time RF analysis. ---- - -import { Badge, Aside } from '@astrojs/starlight/components'; - -Firmware v3.02 adds three new vendor commands (`0xB7`--`0xB9`) optimized for real-time signal monitoring and RF analysis workflows. These commands reduce USB round-trips by combining multiple register reads into single transfers. - -Built on the same [v3.01 codebase](/firmware/custom-v301/) — same SDCC + fx2lib toolchain, same stock-compatible command set, same I2C timeout protection. - -| Property | Value | -|----------|-------| -| Version ID | `0x030200` | -| Date | 2026-02-12 | -| New commands | `0xB7`--`0xB9` (3 added to v3.01's 7) | -| Total custom commands | 10 (`0xB0`--`0xB9`) | - -## New Commands - -| Command | Name | Direction | Payload | Purpose | -|---------|------|-----------|---------|---------| -| `0xB7` | SIGNAL_MONITOR | IN | 8 bytes | Fast combined read: SNR + AGC + lock + status | -| `0xB8` | TUNE_MONITOR | OUT+IN | 10 bytes each | Tune + dwell + read in one round-trip | -| `0xB9` | MULTI_REG_READ | IN | 1--64 bytes | Batch read contiguous indirect registers | - -## Signal Monitor (0xB7) - -Combines six indirect register reads and two direct register reads into a single 8-byte USB transfer. Replaces three separate transfers (`GET_SIGNAL_STRENGTH` + `GET_SIGNAL_LOCK` + individual register reads) with one. - -```c title="SIGNAL_MONITOR response format (8 bytes)" -Bytes 0-1: SNR (u16 LE, indirect regs 0x00-0x01, dBu × 256) -Bytes 2-3: AGC1 (u16 LE, indirect regs 0x02-0x03) -Bytes 4-5: AGC2 (u16 LE, indirect regs 0x04-0x05) -Byte 6: Lock (direct reg 0xA4, bit 5 = locked) -Byte 7: Status (direct reg 0xA2) -``` - -Enables ~50 Hz polling for real-time dish alignment feedback. - -## Tune Monitor (0xB8) - -Combines tune + configurable dwell + signal read into one command round-trip. This is the building block for host-driven spectrum sweeps. - -The command uses two USB control transfers sharing the same bRequest code, distinguished by direction: - -```c title="TUNE_MONITOR protocol" -// Phase 1: OUT (0x40) — host sends 10-byte tune payload -// wValue = dwell_ms (1-255), firmware tunes, waits, reads signal -// Phase 2: IN (0xC0) — host reads 10-byte result -// Bytes 0-5: SNR(2) + AGC1(2) + AGC2(2) -// Byte 6: lock, Byte 7: status -// Bytes 8-9: dwell_ms echo (u16 LE) -``` - - - -## Multi Register Read (0xB9) - -Batch-reads up to 64 contiguous BCM4500 indirect registers in a single USB transfer. Each register still requires an individual I2C read sequence internally, but eliminating 63 USB control transfer round-trips provides ~64x speedup for register exploration. - -```c title="MULTI_REG_READ parameters" -wValue = start register number -wIndex = count (1-64) -Returns: count bytes, one per register -``` - -## What Changed from v3.01 - -| Feature | v3.01 | v3.02 | -|---------|-------|-------| -| Custom commands | 7 (`0xB0`--`0xB6`) | 10 (`0xB0`--`0xB9`) | -| Spectrum sweep | `0xB0` via EP2 bulk (firmware-driven) | Also `0xB8` via control EP (host-driven) | -| Signal monitoring | 3 USB transfers (stock method) | 1 transfer (`0xB7`, 8 bytes) | -| Batch register read | 1 reg per transfer | 64 regs per transfer (`0xB9`) | -| All other features | -- | Unchanged | - -The v3.01 commands (`0xB0`--`0xB6`) remain fully functional. See the [v3.01 documentation](/firmware/custom-v301/) for spectrum sweep, blind scan, raw demod access, I2C scan, boot stage, and GPIO state commands. +--- +title: Custom Firmware v3.02 +description: Signal monitoring, tune-and-measure, and batch register read commands for real-time RF analysis. +--- + +import { Badge, Aside } from '@astrojs/starlight/components'; + +Firmware v3.02 adds three new vendor commands (`0xB7`--`0xB9`) optimized for real-time signal monitoring and RF analysis workflows. These commands reduce USB round-trips by combining multiple register reads into single transfers. + +Built on the same [v3.01 codebase](/firmware/custom-v301/) — same SDCC + fx2lib toolchain, same stock-compatible command set, same I2C timeout protection. + +| Property | Value | +|----------|-------| +| Version ID | `0x030200` | +| Date | 2026-02-12 | +| New commands | `0xB7`--`0xB9` (3 added to v3.01's 7) | +| Total custom commands | 10 (`0xB0`--`0xB9`) | + +## New Commands + +| Command | Name | Direction | Payload | Purpose | +|---------|------|-----------|---------|---------| +| `0xB7` | SIGNAL_MONITOR | IN | 8 bytes | Fast combined read: SNR + AGC + lock + status | +| `0xB8` | TUNE_MONITOR | OUT+IN | 10 bytes each | Tune + dwell + read in one round-trip | +| `0xB9` | MULTI_REG_READ | IN | 1--64 bytes | Batch read contiguous indirect registers | + +## Signal Monitor (0xB7) + +Combines six indirect register reads and two direct register reads into a single 8-byte USB transfer. Replaces three separate transfers (`GET_SIGNAL_STRENGTH` + `GET_SIGNAL_LOCK` + individual register reads) with one. + +```c title="SIGNAL_MONITOR response format (8 bytes)" +Bytes 0-1: SNR (u16 LE, indirect regs 0x00-0x01, dBu × 256) +Bytes 2-3: AGC1 (u16 LE, indirect regs 0x02-0x03) +Bytes 4-5: AGC2 (u16 LE, indirect regs 0x04-0x05) +Byte 6: Lock (direct reg 0xA4, bit 5 = locked) +Byte 7: Status (direct reg 0xA2) +``` + +Enables ~50 Hz polling for real-time dish alignment feedback. + +## Tune Monitor (0xB8) + +Combines tune + configurable dwell + signal read into one command round-trip. This is the building block for host-driven spectrum sweeps. + +The command uses two USB control transfers sharing the same bRequest code, distinguished by direction: + +```c title="TUNE_MONITOR protocol" +// Phase 1: OUT (0x40) — host sends 10-byte tune payload +// wValue = dwell_ms (1-255), firmware tunes, waits, reads signal +// Phase 2: IN (0xC0) — host reads 10-byte result +// Bytes 0-5: SNR(2) + AGC1(2) + AGC2(2) +// Byte 6: lock, Byte 7: status +// Bytes 8-9: dwell_ms echo (u16 LE) +``` + + + +## Multi Register Read (0xB9) + +Batch-reads up to 64 contiguous BCM4500 indirect registers in a single USB transfer. Each register still requires an individual I2C read sequence internally, but eliminating 63 USB control transfer round-trips provides ~64x speedup for register exploration. + +```c title="MULTI_REG_READ parameters" +wValue = start register number +wIndex = count (1-64) +Returns: count bytes, one per register +``` + +## What Changed from v3.01 + +| Feature | v3.01 | v3.02 | +|---------|-------|-------| +| Custom commands | 7 (`0xB0`--`0xB6`) | 10 (`0xB0`--`0xB9`) | +| Spectrum sweep | `0xB0` via EP2 bulk (firmware-driven) | Also `0xB8` via control EP (host-driven) | +| Signal monitoring | 3 USB transfers (stock method) | 1 transfer (`0xB7`, 8 bytes) | +| Batch register read | 1 reg per transfer | 64 regs per transfer (`0xB9`) | +| All other features | -- | Unchanged | + +The v3.01 commands (`0xB0`--`0xB6`) remain fully functional. See the [v3.01 documentation](/firmware/custom-v301/) for spectrum sweep, blind scan, raw demod access, I2C scan, boot stage, and GPIO state commands. diff --git a/site/src/content/docs/firmware/custom-v305.mdx b/site/src/content/docs/firmware/custom-v305.mdx index f29091d..8a5c644 100644 --- a/site/src/content/docs/firmware/custom-v305.mdx +++ b/site/src/content/docs/firmware/custom-v305.mdx @@ -1,181 +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. - -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:** - - -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) - - - - -## 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 | +--- +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. + +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:** + + +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) + + + + +## 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 | diff --git a/site/src/content/docs/firmware/fw213-variants.mdx b/site/src/content/docs/firmware/fw213-variants.mdx index 2e9f89b..d5d8033 100644 --- a/site/src/content/docs/firmware/fw213-variants.mdx +++ b/site/src/content/docs/firmware/fw213-variants.mdx @@ -1,243 +1,243 @@ ---- -title: FW2.13 Sub-Variant Comparison -description: Binary and functional comparison of v2.13 firmware sub-variants FW1, FW2, and FW3. ---- - -import { Tabs, TabItem, Badge, Aside } from '@astrojs/starlight/components'; - -The v2.13 firmware was distributed as three sub-variants via the `SW1_update_2_13_x.exe` Windows updater tool. Ghidra analysis reveals that these target fundamentally different hardware interfaces, not just minor revisions. - -## Overview - -| Aspect | FW1 (v2.13.1) | FW2 (v2.13.2) | FW3 (v2.13.3) | -|--------|---------------|---------------|---------------| -| Version ID | `0x020D01` | `0x020D01` | `0x020D01` | -| Build date | 2010-03-12 | 2010-03-12 | 2010-03-12 | -| Functions | 82 | 83 | 83 | -| Binary size | 9,322 bytes | 9,377 bytes | 9,369 bytes | -| Stack pointer | `0x50` | `0x50` | **`0x52`** | -| P0 init | `0xA4` | `0xA4` | **`0xA0`** | -| Status register | INTMEM `0x4F` | INTMEM `0x4F` | **INTMEM `0x51`** | -| Demod interface | **I2C bus** | **Parallel bus (P0/P1)** | **Parallel bus (enhanced)** | -| Config source | Hardcoded | External (`0xE080`--`0xE08E`) | External (`0xE080`--`0xE08E`) | - -## Hardware Detection - -All three sub-variants report the same version ID (`0x020D01`) to the host. The Windows updater `SW1_update_2_13_x.exe` selects which sub-variant to flash, but the detection mechanism is not yet fully understood. - - - -### Distinguishing Characteristics - -Although the updater's detection method is opaque, the firmware binaries themselves contain clear markers that distinguish each target hardware: - -| Characteristic | FW1 | FW2 | FW3 | Docs | -|---------------|-----|-----|-----|------| -| P0 init value | `0xA4` | `0xA4` | `0xA0` (bit 2 cleared) | [Register Map — IRAM](/bcm4500/register-map/#fx2-iram-by-firmware-version) | -| Stack pointer | `0x50` | `0x50` | `0x52` (+2 for status reg) | [Register Map — IRAM](/bcm4500/register-map/#fx2-iram-by-firmware-version) | -| Config status IRAM | `0x4F` | `0x4F` | `0x51` | [Config Status](/usb/config-status/) | -| I2C buffer IRAM | `0x48`/`0x49` | `0x48`/`0x49` | `0x4A`/`0x4B` | [Register Map — IRAM](/bcm4500/register-map/#fx2-iram-by-firmware-version) | -| Demod interface | I2C bus | Parallel (P0/P1) | Parallel (enhanced, dual-phase) | See tabs below | -| Config source | Hardcoded | External `0xE080`--`0xE08E` | External `0xE080`--`0xE08E` | [Register Map — XRAM](/bcm4500/register-map/#fw2fw3-external-configuration) | - -### FW1 Demod Type Detection - -FW1 is the only sub-variant that performs demodulator type identification at runtime. Function `FUN_CODE_1405` reads the P1 port after an I2C transaction and matches the result against known silicon signatures: - -| Type Code | P1 Signature | Likely Silicon | -|-----------|--------------|----------------| -| Type 3 | `0xA5` or `0xB5` | Original BCM4500 | -| Type 4 | `0x5A` | BCM4500 revision A | -| Type 5 | `0x5B` | BCM4500 revision B | -| Type 6 | `0x5C` | BCM4500 revision C | - -FW2 and FW3 use a different signature check (`P1 ^ 0x1D`) through the parallel bus, suggesting the demod variant is already known at flash time on those PCBs. - -## Hardware Interface Evolution - - - - -### FW1 (v2.13.1) - -FW1 targets the original SkyWalker-1 PCB with an **I2C-connected demodulator**. The FX2 communicates with the demod entirely through standard I2C master-mode transactions. - -**Evidence from `FUN_CODE_0eea`:** -- Uses `FUN_CODE_23ae` (I2C START), `FUN_CODE_23ee` (I2C byte write), `FUN_CODE_23d0` (I2C address) -- Standard I2C retry with NACK detection -- Timer2-based I2C timeout (TR2 check in vendor handler) -- Reads back via `FUN_CODE_2164` - -**Unique functions:** -- `FUN_CODE_0fc7` -- I2C write-with-retry (20 attempts via I2C bus) -- `FUN_CODE_1405` -- Tuner/demodulator identification via I2C + P1 port reads with signature matching -- `FUN_CODE_14b9` -- Calibrated delay function with CPUCS clock divider awareness - -**Demodulator type detection** (from `FUN_CODE_1405`): -| Type Code | P1 Signature | -|-----------|--------------| -| Type 3 | `0xA5` or `0xB5` | -| Type 4 | `0x5A` | -| Type 5 | `0x5B` | -| Type 6 | `0x5C` | - - - - -### FW2 (v2.13.2) - -FW2 targets a revised PCB with a **parallel-bus connected demodulator**. The demod data port is connected directly to the FX2's P1, with P0 bits controlling bus signals. - -**Evidence from `FUN_CODE_0eea`:** -- Reads demod type from address table (BANK1 pointer + offset) -- Uses `FUN_CODE_11b6` for demod selection -- Toggles P0 bits 6/7 for bus control (P0.6 = chip select, P0.7 = read strobe) -- Reads data from **P1 port** (8-bit parallel data bus) -- Single-phase read: one P1 read per bus cycle - -**Bus protocol (decompiled):** -```c title="FW2 parallel bus read" -uVar1 = P1; // Read data with one bus state -P0 |= 0x40; // Change control line -uVar2 = P1; // Read again with new state -FUN_CODE_1b2a(uVar2, uVar1); // Process both samples -``` - -**Configuration loading:** -- Loads 15 configuration bytes from external memory (`0xE080`--`0xE08E`) into demod registers (`0xE6C0`--`0xE6CD`) -- Same device signature matching as FW1 but via parallel bus (`P1 ^ 0x1D` check) - - - - -### FW3 (v2.13.3) - -FW3 targets a further revised PCB with the same parallel-bus architecture as FW2 but with a **different bus timing protocol**. Uses dual-phase reads with OR-accumulation. - -**Evidence from `FUN_CODE_0eea`:** -- Initializes OR-accumulators: `DAT_INTMEM_3f = 0; DAT_INTMEM_40 = 0` -- Sets P0 | 0x80 once at start (not per-iteration like FW2) -- Two separate P1 reads per cycle with different P0.6 states -- OR-accumulates results before processing - -**Bus protocol (decompiled):** -```c title="FW3 dual-phase parallel bus read" -DAT_INTMEM_3f = 0; -DAT_INTMEM_40 = 0; // Clear accumulators - -// Phase 1: P0.6 high -P0 |= 0x44; -bVar2 = P1; -DAT_INTMEM_3f |= bVar2; // OR-accumulate - -// Phase 2: P0.6 low -P0 &= ~0x40; -bVar2 = P1; -DAT_INTMEM_40 |= bVar2; // OR-accumulate - -FUN_CODE_1b2a(0, DAT_INTMEM_3f, DAT_INTMEM_40); -``` - -**Why OR-accumulation?** This pattern suggests the demod chip variant has either: -- Open-drain outputs requiring multiple read cycles -- Bus settling time issues on the newer PCB layout -- A chip revision that serializes data across multiple bus phases - - - -## Binary Distance Matrix - -Byte-level differences between sub-variants: - -| Pair | Different Bytes | Percentage Different | -|------|----------------:|---------------------:| -| FW1 vs FW2 | 3,993 | 42.8% | -| FW1 vs FW3 | 3,789 | 40.6% | -| FW2 vs FW3 | **1,525** | **16.5%** | - -FW2 and FW3 are 83.5% identical at the byte level, confirming they share the same parallel-bus architecture. FW1 diverges significantly because it uses a completely different bus interface (I2C vs. parallel). - -## Memory Comparison at Key Offsets - -### Identical Regions - -These regions are byte-identical across all three sub-variants: - -| Address Range | Content | -|---------------|---------| -| `0x0000`--`0x000F` | RESET vector (`LJMP 0x170D`), INT0 handler | -| `0x0B88`--`0x0B9F` | Init table (same XRAM register initialization) | -| `0x06D9`--`0x06F0` | Generic memory access utilities | -| `0x1740`--`0x174F` | Bit manipulation lookup table | - -### Critical Divergence: `CODE:0EEA` - -This is where the three sub-variants diverge most dramatically: - -``` -FW1: 8f44 8c45 8d46 8b47 754a14 e544 b451... - (I2C transfer parameters in registers) - -FW2: 753e14 e50d 240a f582 e435 0cf5 83e0... - (reads from DPTR+offset table) - -FW3: 753e14 e4f5 3ff5 40 e50d 240a f582... - (similar to FW2 + accumulator initialization) -``` - -FW1's `FUN_CODE_0eea` is a standard I2C master transfer function. FW2/FW3's version is a parallel bus demodulator interface. - -### Thunk Target Divergence (`CODE:1500`) - -``` -FW1: 02 2252 00 02 22dd 00 02 22c7 00 02 226a 00 -FW2: 02 228d 00 02 2318 00 02 2302 00 02 22a5 00 -FW3: 02 228d 00 02 2318 00 02 2302 00 02 22a5 00 -``` - -FW2 and FW3 share identical interrupt handler targets. FW1 jumps to different addresses, reflecting its different internal function layout. - -## Detailed Differences - -### Stack Pointer and Status Register - -| Property | FW1 | FW2 | FW3 | -|----------|-----|-----|-----| -| SP value | `0x50` | `0x50` | `0x52` | -| Status IRAM | `0x4F` | `0x4F` | `0x51` | -| I2C buffer IRAM | `0x48`/`0x49` | `0x48`/`0x49` | `0x4A`/`0x4B` | - -FW3 pushes the stack pointer up by 2 bytes to make room for the additional status register at IRAM `0x51`. The 2-byte SP difference exactly accounts for moving the status register from `0x4F` to `0x51`. - -### P0 Init Value - -| Variant | P0 Init | Binary | Difference | -|---------|---------|--------|------------| -| FW1/FW2 | `0xA4` | `1010 0100` | Bit 2 = 1 | -| FW3 | `0xA0` | `1010 0000` | Bit 2 = 0 | - -P0 bit 2 controls a GPIO signal likely related to demodulator interface mode or reset polarity on the FW3 target PCB. - -### Vendor Handler Differences - -| Feature | FW1 | FW2/FW3 | -|---------|-----|---------| -| Case `0x3D3` | TR2 timer check (I2C timeout) | OR operation (parallel bus) | -| Case `0x421`-`0x423` | Simple check | P2.1 write + rotate-left (bus direction) | -| Error path | `func_0x06e4` | `DAT=0x0` | - -FW1's timer-based case is used for I2C bus timeout recovery. FW2/FW3's rotate-left and P2.1 write is a parallel bus data direction control. - -## Hardware Progression Theory - -The three sub-variants represent an evolutionary progression: - -1. **FW1 (v2.13.1)**: Original design with I2C-connected demodulator. Simple interface but limited in bandwidth. The FX2 acts purely as an I2C master bridge. - -2. **FW2 (v2.13.2)**: Redesigned with parallel-bus demodulator for higher throughput. P1 carries 8-bit data, P0 provides control signals. External calibration data at `0xE080`--`0xE08E`. - -3. **FW3 (v2.13.3)**: Refined parallel interface for a newer demod silicon revision. Dual-phase reads with OR-accumulation handle bus timing differences. Additional IRAM state tracking (SP bumped to `0x52`). - -All three support the same modulation types (DVB-S QPSK, Turbo QPSK/8PSK/16QAM, DCII, DSS) and the same demod type codes (3--6). The differences are purely hardware interface, not feature set. +--- +title: FW2.13 Sub-Variant Comparison +description: Binary and functional comparison of v2.13 firmware sub-variants FW1, FW2, and FW3. +--- + +import { Tabs, TabItem, Badge, Aside } from '@astrojs/starlight/components'; + +The v2.13 firmware was distributed as three sub-variants via the `SW1_update_2_13_x.exe` Windows updater tool. Ghidra analysis reveals that these target fundamentally different hardware interfaces, not just minor revisions. + +## Overview + +| Aspect | FW1 (v2.13.1) | FW2 (v2.13.2) | FW3 (v2.13.3) | +|--------|---------------|---------------|---------------| +| Version ID | `0x020D01` | `0x020D01` | `0x020D01` | +| Build date | 2010-03-12 | 2010-03-12 | 2010-03-12 | +| Functions | 82 | 83 | 83 | +| Binary size | 9,322 bytes | 9,377 bytes | 9,369 bytes | +| Stack pointer | `0x50` | `0x50` | **`0x52`** | +| P0 init | `0xA4` | `0xA4` | **`0xA0`** | +| Status register | INTMEM `0x4F` | INTMEM `0x4F` | **INTMEM `0x51`** | +| Demod interface | **I2C bus** | **Parallel bus (P0/P1)** | **Parallel bus (enhanced)** | +| Config source | Hardcoded | External (`0xE080`--`0xE08E`) | External (`0xE080`--`0xE08E`) | + +## Hardware Detection + +All three sub-variants report the same version ID (`0x020D01`) to the host. The Windows updater `SW1_update_2_13_x.exe` selects which sub-variant to flash, but the detection mechanism is not yet fully understood. + + + +### Distinguishing Characteristics + +Although the updater's detection method is opaque, the firmware binaries themselves contain clear markers that distinguish each target hardware: + +| Characteristic | FW1 | FW2 | FW3 | Docs | +|---------------|-----|-----|-----|------| +| P0 init value | `0xA4` | `0xA4` | `0xA0` (bit 2 cleared) | [Register Map — IRAM](/bcm4500/register-map/#fx2-iram-by-firmware-version) | +| Stack pointer | `0x50` | `0x50` | `0x52` (+2 for status reg) | [Register Map — IRAM](/bcm4500/register-map/#fx2-iram-by-firmware-version) | +| Config status IRAM | `0x4F` | `0x4F` | `0x51` | [Config Status](/usb/config-status/) | +| I2C buffer IRAM | `0x48`/`0x49` | `0x48`/`0x49` | `0x4A`/`0x4B` | [Register Map — IRAM](/bcm4500/register-map/#fx2-iram-by-firmware-version) | +| Demod interface | I2C bus | Parallel (P0/P1) | Parallel (enhanced, dual-phase) | See tabs below | +| Config source | Hardcoded | External `0xE080`--`0xE08E` | External `0xE080`--`0xE08E` | [Register Map — XRAM](/bcm4500/register-map/#fw2fw3-external-configuration) | + +### FW1 Demod Type Detection + +FW1 is the only sub-variant that performs demodulator type identification at runtime. Function `FUN_CODE_1405` reads the P1 port after an I2C transaction and matches the result against known silicon signatures: + +| Type Code | P1 Signature | Likely Silicon | +|-----------|--------------|----------------| +| Type 3 | `0xA5` or `0xB5` | Original BCM4500 | +| Type 4 | `0x5A` | BCM4500 revision A | +| Type 5 | `0x5B` | BCM4500 revision B | +| Type 6 | `0x5C` | BCM4500 revision C | + +FW2 and FW3 use a different signature check (`P1 ^ 0x1D`) through the parallel bus, suggesting the demod variant is already known at flash time on those PCBs. + +## Hardware Interface Evolution + + + + +### FW1 (v2.13.1) + +FW1 targets the original SkyWalker-1 PCB with an **I2C-connected demodulator**. The FX2 communicates with the demod entirely through standard I2C master-mode transactions. + +**Evidence from `FUN_CODE_0eea`:** +- Uses `FUN_CODE_23ae` (I2C START), `FUN_CODE_23ee` (I2C byte write), `FUN_CODE_23d0` (I2C address) +- Standard I2C retry with NACK detection +- Timer2-based I2C timeout (TR2 check in vendor handler) +- Reads back via `FUN_CODE_2164` + +**Unique functions:** +- `FUN_CODE_0fc7` -- I2C write-with-retry (20 attempts via I2C bus) +- `FUN_CODE_1405` -- Tuner/demodulator identification via I2C + P1 port reads with signature matching +- `FUN_CODE_14b9` -- Calibrated delay function with CPUCS clock divider awareness + +**Demodulator type detection** (from `FUN_CODE_1405`): +| Type Code | P1 Signature | +|-----------|--------------| +| Type 3 | `0xA5` or `0xB5` | +| Type 4 | `0x5A` | +| Type 5 | `0x5B` | +| Type 6 | `0x5C` | + + + + +### FW2 (v2.13.2) + +FW2 targets a revised PCB with a **parallel-bus connected demodulator**. The demod data port is connected directly to the FX2's P1, with P0 bits controlling bus signals. + +**Evidence from `FUN_CODE_0eea`:** +- Reads demod type from address table (BANK1 pointer + offset) +- Uses `FUN_CODE_11b6` for demod selection +- Toggles P0 bits 6/7 for bus control (P0.6 = chip select, P0.7 = read strobe) +- Reads data from **P1 port** (8-bit parallel data bus) +- Single-phase read: one P1 read per bus cycle + +**Bus protocol (decompiled):** +```c title="FW2 parallel bus read" +uVar1 = P1; // Read data with one bus state +P0 |= 0x40; // Change control line +uVar2 = P1; // Read again with new state +FUN_CODE_1b2a(uVar2, uVar1); // Process both samples +``` + +**Configuration loading:** +- Loads 15 configuration bytes from external memory (`0xE080`--`0xE08E`) into demod registers (`0xE6C0`--`0xE6CD`) +- Same device signature matching as FW1 but via parallel bus (`P1 ^ 0x1D` check) + + + + +### FW3 (v2.13.3) + +FW3 targets a further revised PCB with the same parallel-bus architecture as FW2 but with a **different bus timing protocol**. Uses dual-phase reads with OR-accumulation. + +**Evidence from `FUN_CODE_0eea`:** +- Initializes OR-accumulators: `DAT_INTMEM_3f = 0; DAT_INTMEM_40 = 0` +- Sets P0 | 0x80 once at start (not per-iteration like FW2) +- Two separate P1 reads per cycle with different P0.6 states +- OR-accumulates results before processing + +**Bus protocol (decompiled):** +```c title="FW3 dual-phase parallel bus read" +DAT_INTMEM_3f = 0; +DAT_INTMEM_40 = 0; // Clear accumulators + +// Phase 1: P0.6 high +P0 |= 0x44; +bVar2 = P1; +DAT_INTMEM_3f |= bVar2; // OR-accumulate + +// Phase 2: P0.6 low +P0 &= ~0x40; +bVar2 = P1; +DAT_INTMEM_40 |= bVar2; // OR-accumulate + +FUN_CODE_1b2a(0, DAT_INTMEM_3f, DAT_INTMEM_40); +``` + +**Why OR-accumulation?** This pattern suggests the demod chip variant has either: +- Open-drain outputs requiring multiple read cycles +- Bus settling time issues on the newer PCB layout +- A chip revision that serializes data across multiple bus phases + + + +## Binary Distance Matrix + +Byte-level differences between sub-variants: + +| Pair | Different Bytes | Percentage Different | +|------|----------------:|---------------------:| +| FW1 vs FW2 | 3,993 | 42.8% | +| FW1 vs FW3 | 3,789 | 40.6% | +| FW2 vs FW3 | **1,525** | **16.5%** | + +FW2 and FW3 are 83.5% identical at the byte level, confirming they share the same parallel-bus architecture. FW1 diverges significantly because it uses a completely different bus interface (I2C vs. parallel). + +## Memory Comparison at Key Offsets + +### Identical Regions + +These regions are byte-identical across all three sub-variants: + +| Address Range | Content | +|---------------|---------| +| `0x0000`--`0x000F` | RESET vector (`LJMP 0x170D`), INT0 handler | +| `0x0B88`--`0x0B9F` | Init table (same XRAM register initialization) | +| `0x06D9`--`0x06F0` | Generic memory access utilities | +| `0x1740`--`0x174F` | Bit manipulation lookup table | + +### Critical Divergence: `CODE:0EEA` + +This is where the three sub-variants diverge most dramatically: + +``` +FW1: 8f44 8c45 8d46 8b47 754a14 e544 b451... + (I2C transfer parameters in registers) + +FW2: 753e14 e50d 240a f582 e435 0cf5 83e0... + (reads from DPTR+offset table) + +FW3: 753e14 e4f5 3ff5 40 e50d 240a f582... + (similar to FW2 + accumulator initialization) +``` + +FW1's `FUN_CODE_0eea` is a standard I2C master transfer function. FW2/FW3's version is a parallel bus demodulator interface. + +### Thunk Target Divergence (`CODE:1500`) + +``` +FW1: 02 2252 00 02 22dd 00 02 22c7 00 02 226a 00 +FW2: 02 228d 00 02 2318 00 02 2302 00 02 22a5 00 +FW3: 02 228d 00 02 2318 00 02 2302 00 02 22a5 00 +``` + +FW2 and FW3 share identical interrupt handler targets. FW1 jumps to different addresses, reflecting its different internal function layout. + +## Detailed Differences + +### Stack Pointer and Status Register + +| Property | FW1 | FW2 | FW3 | +|----------|-----|-----|-----| +| SP value | `0x50` | `0x50` | `0x52` | +| Status IRAM | `0x4F` | `0x4F` | `0x51` | +| I2C buffer IRAM | `0x48`/`0x49` | `0x48`/`0x49` | `0x4A`/`0x4B` | + +FW3 pushes the stack pointer up by 2 bytes to make room for the additional status register at IRAM `0x51`. The 2-byte SP difference exactly accounts for moving the status register from `0x4F` to `0x51`. + +### P0 Init Value + +| Variant | P0 Init | Binary | Difference | +|---------|---------|--------|------------| +| FW1/FW2 | `0xA4` | `1010 0100` | Bit 2 = 1 | +| FW3 | `0xA0` | `1010 0000` | Bit 2 = 0 | + +P0 bit 2 controls a GPIO signal likely related to demodulator interface mode or reset polarity on the FW3 target PCB. + +### Vendor Handler Differences + +| Feature | FW1 | FW2/FW3 | +|---------|-----|---------| +| Case `0x3D3` | TR2 timer check (I2C timeout) | OR operation (parallel bus) | +| Case `0x421`-`0x423` | Simple check | P2.1 write + rotate-left (bus direction) | +| Error path | `func_0x06e4` | `DAT=0x0` | + +FW1's timer-based case is used for I2C bus timeout recovery. FW2/FW3's rotate-left and P2.1 write is a parallel bus data direction control. + +## Hardware Progression Theory + +The three sub-variants represent an evolutionary progression: + +1. **FW1 (v2.13.1)**: Original design with I2C-connected demodulator. Simple interface but limited in bandwidth. The FX2 acts purely as an I2C master bridge. + +2. **FW2 (v2.13.2)**: Redesigned with parallel-bus demodulator for higher throughput. P1 carries 8-bit data, P0 provides control signals. External calibration data at `0xE080`--`0xE08E`. + +3. **FW3 (v2.13.3)**: Refined parallel interface for a newer demod silicon revision. Dual-phase reads with OR-accumulation handle bus timing differences. Additional IRAM state tracking (SP bumped to `0x52`). + +All three support the same modulation types (DVB-S QPSK, Turbo QPSK/8PSK/16QAM, DCII, DSS) and the same demod type codes (3--6). The differences are purely hardware interface, not feature set. diff --git a/site/src/content/docs/firmware/kernel-fw01.mdx b/site/src/content/docs/firmware/kernel-fw01.mdx index d48f675..3e630b8 100644 --- a/site/src/content/docs/firmware/kernel-fw01.mdx +++ b/site/src/content/docs/firmware/kernel-fw01.mdx @@ -1,184 +1,184 @@ ---- -title: Kernel FW01 Analysis -description: Analysis of the dvb-usb-gp8psk-01.fw firmware format, loading mechanism, and why SkyWalker-1 does not need it. ---- - -import { Steps, Badge, Aside, Tabs, TabItem, FileTree } from '@astrojs/starlight/components'; - -The Linux kernel `dvb_usb_gp8psk` driver references two firmware files: `dvb-usb-gp8psk-01.fw` (FX2 microcontroller code) and `dvb-usb-gp8psk-02.fw` (BCM4500 demodulator code). Neither file was ever open-sourced or included in the `linux-firmware` repository. The SkyWalker-1 does not need them. - -## Firmware File Status - -| File | Purpose | Available? | Needed by SkyWalker-1? | -|------|---------|------------|----------------------| -| `dvb-usb-gp8psk-01.fw` | FX2 RAM code | **Not in linux-firmware** | No | -| `dvb-usb-gp8psk-02.fw` | BCM4500 demod code | **Not in linux-firmware** | No | - -Standard locations checked: - -| Path | Result | -|------|--------| -| `/lib/firmware/dvb-usb-gp8psk-01.fw` | Not found | -| `/lib/firmware/dvb-usb-gp8psk-02.fw` | Not found | -| `linux-firmware` WHENCE manifest | No gp8psk entry | -| Kernel `scripts/get_dvb_firmware` | No gp8psk handler | -| `pacman -F dvb-usb-gp8psk-01.fw` | No package provides it | - - - -## Why SkyWalker-1 Works Without Firmware Files - -The answer is in the kernel driver's device table. Only Rev.1 Cold devices (PID `0x0200`) have a `cold_ids` entry, which triggers firmware download: - -```c title="Kernel device properties (from gp8psk.c)" -.devices = { - { .name = "Genpix 8PSK-to-USB2 Rev.1 DVB-S receiver", - .cold_ids = { &gp8psk_usb_table[GENPIX_8PSK_REV_1_COLD], NULL }, - .warm_ids = { &gp8psk_usb_table[GENPIX_8PSK_REV_1_WARM], NULL }, - }, - { .name = "Genpix SkyWalker-1 DVB-S receiver", - .cold_ids = { NULL }, // <-- NO cold_ids: skip firmware - .warm_ids = { &gp8psk_usb_table[GENPIX_SKYWALKER_1], NULL }, - }, - // ... -} -``` - -When `cold_ids` is NULL, the DVB-USB framework skips firmware download entirely. The SkyWalker-1 boots from its onboard EEPROM and enumerates directly as a "warm" device. - -| Device | PID | Needs FW01? | Needs FW02? | Boot Source | -|--------|-----|-------------|-------------|-------------| -| Rev.1 Cold | `0x0200` | **Yes** | -- | RAM (empty) | -| Rev.1 Warm | `0x0201` | No | **Yes** | RAM (FW01 loaded) | -| Rev.2 | `0x0202` | No | No | EEPROM | -| SkyWalker-1 | `0x0203` | No | No | EEPROM | -| SkyWalker CW3K | `0x0206` | No | No | EEPROM | - -## FW01 Loading Mechanism - -For Rev.1 Cold devices, the firmware loading follows this sequence: - - - -1. **DVB-USB framework** matches the USB VID:PID against `cold_ids` and calls `dvb_usb_download_firmware()` - -2. **Kernel requests firmware** via `request_firmware("dvb-usb-gp8psk-01.fw", ...)` from the userspace firmware loader - -3. **Cypress FX2 loader** (`usb_cypress_load_firmware()`) halts the FX2 CPU by writing `0x01` to CPUCS register (`0xE600`) via the 0xA0 vendor request - -4. **Hexline records** are parsed and written to FX2 RAM via USB control transfers (0xA0 vendor request) - -5. **FX2 CPU restarted** by writing `0x00` to CPUCS. The device re-enumerates with a new PID (0x0201, "warm") - -6. **DVB-USB framework** re-matches the new PID against `warm_ids` and proceeds to frontend attach - - - -The 0xA0 vendor request is handled by the FX2's built-in silicon boot ROM, which provides RAM read/write access regardless of whether user firmware is running. This is the same mechanism used by the custom firmware's `fw_load.py` tool. - -## FW01 Binary Hexline Format - -The kernel's `dvb_usb_get_hexline()` parser expects a compact binary representation of Intel HEX records. This is **not** standard Intel HEX text (`:10000000...`), nor the kernel's `ihex_binrec` format from ``. - -### Record Structure - -``` -Offset Size Field ------- ---- ----- -0 1 len - Number of data bytes -1 1 addr_lo - Target address low byte -2 1 addr_hi - Target address high byte -3 1 type - Record type -4 len data[] - Payload bytes -4+len 1 chk - Checksum byte - -Total per record: len + 5 bytes -``` - -### Record Types - -| Type | Name | Purpose | -|------|------|---------| -| `0x00` | Data | Code/data bytes for FX2 RAM | -| `0x01` | EOF | End of file | -| `0x04` | Extended Address | Sets upper 16 bits of target address | - -## FW02 Chunk Format (BCM4500 Firmware) - -FW02 is only relevant for Rev.1 Warm devices (PID `0x0201`). It uses a custom chunk protocol: - -``` -Chunk format: - Byte 0: payload_length (N) - Bytes 1-3: header/address bytes - Bytes 4..N+3: payload data - Terminator: single byte 0xFF - Maximum chunk size: 64 bytes (USB control transfer limit) -``` - -The loading sequence: - - - -1. **Initiate transfer**: Send `LOAD_BCM4500` command (`0x88`, `wValue=1`) - -2. **Download chunks**: Iterate through firmware data, sending each chunk via `dvb_usb_generic_write()` on bulk endpoint `0x01` - -3. **Detect end**: Stop when a byte with value `0xFF` is encountered - - - -```c title="BCM4500 firmware loading (from gp8psk.c)" -ptr = fw_data; -while (ptr[0] != 0xFF) { - chunk_size = ptr[0] + 4; - if (chunk_size > 64) { - // Error: chunk too large - } - usb_control_msg(device, USB_SNDCTRLPIPE, 0, ..., - buf, chunk_size, 2000); - ptr += chunk_size; -} -``` - - - -## C2 EEPROM Format vs Kernel Hexline - -The firmware as stored in the SkyWalker-1's EEPROM uses Cypress C2 format, which is structurally different from the kernel's binary hexline format. They carry identical payload data but are different containers. - -| Property | C2 (EEPROM) | Hexline (Kernel FW01) | -|----------|-------------|-----------------------| -| Header | 8-byte C2 with VID/PID/DID | None | -| Address encoding | Big-endian 16-bit per segment | Little-endian split (lo, hi) per record | -| Data chunking | 1023-byte segments | Typically 16-byte records | -| Record overhead | 4 bytes per segment | 5 bytes per record | -| Terminator | `0x80xx` + entry point | Type `0x01` EOF record | -| Entry point | Explicit in terminator | Implicit (CPUCS at `0xE600`) | - -A C2 file can theoretically be converted to hexline format by: -1. Stripping the 8-byte C2 header -2. Splitting each segment into 16-byte records with type `0x00` -3. Appending an EOF record (len=0, type=`0x01`) - -For the v2.06 EEPROM (9,472 code bytes), this would produce approximately 12,442 bytes in hexline format. - -See the [Storage Formats](/firmware/storage-formats/) page for detailed C2 format documentation. - -## Kernel dmesg Output - -When the SkyWalker-1 is connected, the kernel logs: - -``` -gp8psk: FW Version = 2.06.4 (0x20604) Build 2007/07/13 -gp8psk: usb in 149 operation failed. -gp8psk: failed to get FPGA version -gp8psk_fe: Frontend attached -gp8psk: found Genpix USB device pID = 203 (hex) -``` - -The "failed to get FPGA version" error is command `0x95` (`GET_FPGA_VERS`, decimal 149) returning an error on some units. Despite the name, there is no FPGA on the SkyWalker-1 -- this command reads a hardware platform ID from the EEPROM. The driver logs the failure but continues normally. +--- +title: Kernel FW01 Analysis +description: Analysis of the dvb-usb-gp8psk-01.fw firmware format, loading mechanism, and why SkyWalker-1 does not need it. +--- + +import { Steps, Badge, Aside, Tabs, TabItem, FileTree } from '@astrojs/starlight/components'; + +The Linux kernel `dvb_usb_gp8psk` driver references two firmware files: `dvb-usb-gp8psk-01.fw` (FX2 microcontroller code) and `dvb-usb-gp8psk-02.fw` (BCM4500 demodulator code). Neither file was ever open-sourced or included in the `linux-firmware` repository. The SkyWalker-1 does not need them. + +## Firmware File Status + +| File | Purpose | Available? | Needed by SkyWalker-1? | +|------|---------|------------|----------------------| +| `dvb-usb-gp8psk-01.fw` | FX2 RAM code | **Not in linux-firmware** | No | +| `dvb-usb-gp8psk-02.fw` | BCM4500 demod code | **Not in linux-firmware** | No | + +Standard locations checked: + +| Path | Result | +|------|--------| +| `/lib/firmware/dvb-usb-gp8psk-01.fw` | Not found | +| `/lib/firmware/dvb-usb-gp8psk-02.fw` | Not found | +| `linux-firmware` WHENCE manifest | No gp8psk entry | +| Kernel `scripts/get_dvb_firmware` | No gp8psk handler | +| `pacman -F dvb-usb-gp8psk-01.fw` | No package provides it | + + + +## Why SkyWalker-1 Works Without Firmware Files + +The answer is in the kernel driver's device table. Only Rev.1 Cold devices (PID `0x0200`) have a `cold_ids` entry, which triggers firmware download: + +```c title="Kernel device properties (from gp8psk.c)" +.devices = { + { .name = "Genpix 8PSK-to-USB2 Rev.1 DVB-S receiver", + .cold_ids = { &gp8psk_usb_table[GENPIX_8PSK_REV_1_COLD], NULL }, + .warm_ids = { &gp8psk_usb_table[GENPIX_8PSK_REV_1_WARM], NULL }, + }, + { .name = "Genpix SkyWalker-1 DVB-S receiver", + .cold_ids = { NULL }, // <-- NO cold_ids: skip firmware + .warm_ids = { &gp8psk_usb_table[GENPIX_SKYWALKER_1], NULL }, + }, + // ... +} +``` + +When `cold_ids` is NULL, the DVB-USB framework skips firmware download entirely. The SkyWalker-1 boots from its onboard EEPROM and enumerates directly as a "warm" device. + +| Device | PID | Needs FW01? | Needs FW02? | Boot Source | +|--------|-----|-------------|-------------|-------------| +| Rev.1 Cold | `0x0200` | **Yes** | -- | RAM (empty) | +| Rev.1 Warm | `0x0201` | No | **Yes** | RAM (FW01 loaded) | +| Rev.2 | `0x0202` | No | No | EEPROM | +| SkyWalker-1 | `0x0203` | No | No | EEPROM | +| SkyWalker CW3K | `0x0206` | No | No | EEPROM | + +## FW01 Loading Mechanism + +For Rev.1 Cold devices, the firmware loading follows this sequence: + + + +1. **DVB-USB framework** matches the USB VID:PID against `cold_ids` and calls `dvb_usb_download_firmware()` + +2. **Kernel requests firmware** via `request_firmware("dvb-usb-gp8psk-01.fw", ...)` from the userspace firmware loader + +3. **Cypress FX2 loader** (`usb_cypress_load_firmware()`) halts the FX2 CPU by writing `0x01` to CPUCS register (`0xE600`) via the 0xA0 vendor request + +4. **Hexline records** are parsed and written to FX2 RAM via USB control transfers (0xA0 vendor request) + +5. **FX2 CPU restarted** by writing `0x00` to CPUCS. The device re-enumerates with a new PID (0x0201, "warm") + +6. **DVB-USB framework** re-matches the new PID against `warm_ids` and proceeds to frontend attach + + + +The 0xA0 vendor request is handled by the FX2's built-in silicon boot ROM, which provides RAM read/write access regardless of whether user firmware is running. This is the same mechanism used by the custom firmware's `fw_load.py` tool. + +## FW01 Binary Hexline Format + +The kernel's `dvb_usb_get_hexline()` parser expects a compact binary representation of Intel HEX records. This is **not** standard Intel HEX text (`:10000000...`), nor the kernel's `ihex_binrec` format from ``. + +### Record Structure + +``` +Offset Size Field +------ ---- ----- +0 1 len - Number of data bytes +1 1 addr_lo - Target address low byte +2 1 addr_hi - Target address high byte +3 1 type - Record type +4 len data[] - Payload bytes +4+len 1 chk - Checksum byte + +Total per record: len + 5 bytes +``` + +### Record Types + +| Type | Name | Purpose | +|------|------|---------| +| `0x00` | Data | Code/data bytes for FX2 RAM | +| `0x01` | EOF | End of file | +| `0x04` | Extended Address | Sets upper 16 bits of target address | + +## FW02 Chunk Format (BCM4500 Firmware) + +FW02 is only relevant for Rev.1 Warm devices (PID `0x0201`). It uses a custom chunk protocol: + +``` +Chunk format: + Byte 0: payload_length (N) + Bytes 1-3: header/address bytes + Bytes 4..N+3: payload data + Terminator: single byte 0xFF + Maximum chunk size: 64 bytes (USB control transfer limit) +``` + +The loading sequence: + + + +1. **Initiate transfer**: Send `LOAD_BCM4500` command (`0x88`, `wValue=1`) + +2. **Download chunks**: Iterate through firmware data, sending each chunk via `dvb_usb_generic_write()` on bulk endpoint `0x01` + +3. **Detect end**: Stop when a byte with value `0xFF` is encountered + + + +```c title="BCM4500 firmware loading (from gp8psk.c)" +ptr = fw_data; +while (ptr[0] != 0xFF) { + chunk_size = ptr[0] + 4; + if (chunk_size > 64) { + // Error: chunk too large + } + usb_control_msg(device, USB_SNDCTRLPIPE, 0, ..., + buf, chunk_size, 2000); + ptr += chunk_size; +} +``` + + + +## C2 EEPROM Format vs Kernel Hexline + +The firmware as stored in the SkyWalker-1's EEPROM uses Cypress C2 format, which is structurally different from the kernel's binary hexline format. They carry identical payload data but are different containers. + +| Property | C2 (EEPROM) | Hexline (Kernel FW01) | +|----------|-------------|-----------------------| +| Header | 8-byte C2 with VID/PID/DID | None | +| Address encoding | Big-endian 16-bit per segment | Little-endian split (lo, hi) per record | +| Data chunking | 1023-byte segments | Typically 16-byte records | +| Record overhead | 4 bytes per segment | 5 bytes per record | +| Terminator | `0x80xx` + entry point | Type `0x01` EOF record | +| Entry point | Explicit in terminator | Implicit (CPUCS at `0xE600`) | + +A C2 file can theoretically be converted to hexline format by: +1. Stripping the 8-byte C2 header +2. Splitting each segment into 16-byte records with type `0x00` +3. Appending an EOF record (len=0, type=`0x01`) + +For the v2.06 EEPROM (9,472 code bytes), this would produce approximately 12,442 bytes in hexline format. + +See the [Storage Formats](/firmware/storage-formats/) page for detailed C2 format documentation. + +## Kernel dmesg Output + +When the SkyWalker-1 is connected, the kernel logs: + +``` +gp8psk: FW Version = 2.06.4 (0x20604) Build 2007/07/13 +gp8psk: usb in 149 operation failed. +gp8psk: failed to get FPGA version +gp8psk_fe: Frontend attached +gp8psk: found Genpix USB device pID = 203 (hex) +``` + +The "failed to get FPGA version" error is command `0x95` (`GET_FPGA_VERS`, decimal 149) returning an error on some units. Despite the name, there is no FPGA on the SkyWalker-1 -- this command reads a hardware platform ID from the EEPROM. The driver logs the failure but continues normally. diff --git a/site/src/content/docs/firmware/rev2-analysis.mdx b/site/src/content/docs/firmware/rev2-analysis.mdx index 30e3b49..92f2871 100644 --- a/site/src/content/docs/firmware/rev2-analysis.mdx +++ b/site/src/content/docs/firmware/rev2-analysis.mdx @@ -1,210 +1,210 @@ ---- -title: Rev.2 Firmware Analysis -description: Deep analysis of the Rev.2 v2.10.4 firmware variant with 107 functions and transitional architecture. ---- - -import { Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; - -The Rev.2 v2.10.4 firmware targets the Rev.2 hardware variant (PID `0x0202`) and contains **107 functions** -- the most of any firmware version. Despite this, it produces the smallest binary (8,843 bytes) due to aggressive function decomposition into small helper routines. - -## Architectural Position - -Rev.2 sits architecturally between v2.06 and v2.13: - -| Aspect | v2.06 | Rev.2 v2.10 | v2.13 | -|--------|-------|-------------|-------| -| INT0 behavior | USB re-enumeration | USB re-enumeration | Demod polling | -| Descriptor base | `0x1200` | `0x0E00` | `0x0E00` | -| Stack pointer | `0x72` | `0x4F` | `0x50` | -| Vendor command range | `0x80`--`0x9D` (30) | `0x80`--`0x9A` (27) | `0x80`--`0x9D` (30) | -| Demod probe at boot | No | No | Yes | -| Retry loops | No | No | Yes | -| Function count | 61 | **107** | 82-88 | -| Binary size | 9,472 bytes | **8,843 bytes** | 9,322 bytes | - - - -## Why 107 Functions? - -The high function count is driven by three factors: - -1. **Granular decomposition**: Rev.2 breaks large operations into many small helper functions (10-30 bytes each), where v2.06 inlines the same logic and v2.13 recombines it differently. - -2. **Massive configuration dispatcher**: `FUN_CODE_0800` is 874 bytes and contains an embedded copy of the main loop, causing Ghidra to count additional entry points as separate functions. - -3. **Extra I2C/demodulator helper chains**: GPIO control primitives, hardware-polling wait loops, and I2C bus management exist as individual callable units rather than being inlined. - -## Function Inventory Overview - -The 107 functions are organized into logical groups: - -### Vector Table and ISR Region (0x0000--0x0055) - -| Address | Name | Size | Role | -|---------|------|-----:|------| -| `0x0000` | `RESET_vector` | 3 | Jump to `main` at `0x155F` | -| `0x0003` | `INT0_ISR` | 12 | INT0 handler -- USB re-enumeration | -| `0x000F` | `INT0_ISR_bit_clear` | 36 | CPUCS pulse, IRQ clear, delay | -| `0x0033` | `INT2_USB_GPIF_vector` | 3 | Clears CCON.4 (PCA timer) | -| `0x0036` | `i2c_exchange_byte` | 5 | I2C byte exchange primitive | -| `0x003B` | `I2C_ISR` | 8 | I2C interrupt handler | -| `0x0043` | `INT4_FX2_vector` | 8 | Sets `_0_1` flag, clears EXIF.4 | -| `0x004B` | `INT5_FX2_vector` | 3 | Empty (RETI) | -| `0x0053` | `INT6_FX2_vector` | 3 | Sets `_0_1` flag, clears EXIF.4 | - -### Vendor Command Dispatch (0x0056--0x0319) - -| Address | Name | Size | Role | -|---------|------|-----:|------| -| `0x0056` | `vendor_cmd_dispatch` | 342 | Range check `0x80`--`0x9A`, jump table at `0x0076` | -| `0x01AC` | GET_8PSK_CONFIG handler | 361 | Reads config byte, calls LNB probe | -| `0x0315` | `vendor_cmd_stall` | 2 | Stall handler (empty RET) | -| `0x0319` | Standard USB request handler | 869 | Switch for `bRequest` `0x00`--`0x0B` | - -### Configuration and Tuning (0x0800--0x09A8) - -| Address | Name | Size | Role | -|---------|------|-----:|------| -| `0x0800` | Config/tuning dispatcher | **874** | 128-entry switch on demod type, embedded main loop | -| `0x09A9` | Main init + main loop | 699 | Hardware init, infinite poll loop | - -### BCM4500 and GPIF (0x0C64--0x0F00) - -| Address | Name | Size | Role | -|---------|------|-----:|------| -| `0x0C64` | BCM4500 firmware loader | 280 | I2C block transfer with address tracking | -| `0x0D7C` | GPIF/slave FIFO config | 128 | Enable/disable streaming mode | -| `0x0F00` | I2C multi-byte read | 256 | Parameter setup for bus transfer | - -### DiSEqC Implementation (0x07D1, 0x1D5E--0x1E3D) - -| Address | Name | Size | Role | -|---------|------|-----:|------| -| `0x07D1` | `DiSEqC byte transmit` | 45 | 8 data bits + odd parity via **P0.4** | -| `0x1D5E` | DiSEqC message sender | 59 | Iterates bytes, calls bit-bang per byte | -| `0x1E3D` | DiSEqC byte wrapper | 54 | Sets P0.2, adds inter-byte delay | -| `0x213C` | DiSEqC bit symbol | 22 | Carrier on/off via P0.3, data via P0.4 | -| `0x20E2` | 22 kHz tone burst | 23 | P0.3 ON, 25 ticks, P0.3 OFF | -| `0x225F` | Timer2 tick wait | 6 | TF2 poll and clear (500 us tick) | - - - -### LNB and GPIO Control (0x1F5C--0x2038) - -| Address | Name | Size | Role | -|---------|------|-----:|------| -| `0x1F5C` | LNB voltage I2C select | 41 | Probes I2C device `0x60` or address from `0xE0B6` | -| `0x1FCF` | GPIO pin controller | 46 | Sets P0.6, P0.0, P3.4 based on parameter bits | -| `0x2038` | GPIO clock strobe | 51 | Calls pin controller 3 times (setup, clock, cleanup) | -| `0x21B1` | LNB voltage select | 17 | Sets/clears P0.4, updates config bit 5 | -| `0x21C2` | 22 kHz tone enable | 17 | Sets/clears P0.3, updates config bit 4 | -| `0x21D3` | DiSEqC port direction | 17 | Sets/clears P3.6, updates config bit 3 | - -### I2C Bus Management (0x19F4--0x1B90) - -Rev.2 decomposes I2C operations into particularly fine-grained functions: - -| Address | Name | Size | Role | -|---------|------|-----:|------| -| `0x19F4` | I2C bus controller | 92 | Manages SDA/SCL via XRAM `0xE678` | -| `0x1A50` | I2C address select + start | 83 | START condition generation | -| `0x1AA3` | I2C stop + cleanup | 82 | STOP condition and bus release | -| `0x1AF5` | I2C address write helper | 9 | Writes device address byte | -| `0x1B01` | I2C byte-level transfer | 67 | Single byte send/receive | -| `0x1B44` | I2C ACK/NAK handling | 76 | Acknowledge detection | -| `0x1B90` | I2C bus reset/recovery | 74 | Error recovery sequence | -| `0x1F06` | I2C completion wait | 43 | Polls XRAM `0xE678` bit 0 with 16-bit timeout | -| `0x1F85` | I2C completion wait (2-flag) | 37 | Polls bits 0 and 2 | -| `0x2000` | I2C busy wait | 30 | Polls XRAM `0xE678` bit 6 with timeout | - -## Rev.2-Specific Features - -### GPIO Pin Controller (`FUN_CODE_1fcf`) - -A unique function that provides parameterized GPIO control through a bit-field interface: - -```c title="GPIO pin controller (decompiled)" -void gpio_pin_controller(BYTE param) { - if (param & 0x02) P0 |= 0x01; // P0.0 - else P0 &= ~0x01; - - if (param & 0x04) P0 |= 0x40; // P0.6 - else P0 &= ~0x40; - - if (param & 0x08) P3 |= 0x10; // P3.4 - else P3 &= ~0x10; -} -``` - -This function is called via `FUN_CODE_2038` (GPIO clock strobe) which invokes it three times per cycle -- setup, clock edge, and cleanup -- suggesting it controls a clocked peripheral interface. - -### Descriptor Version Checker (`FUN_CODE_1f31`) - -Walks a USB descriptor chain checking whether `descriptor_byte + 1 == 0x03`, enabling hardware-revision-aware code paths. This mechanism appears in a simplified form in v2.13 as the `_1_3` flag check. - -### Prototype Commands 0x99/0x9A - -Commands 0x99 and 0x9A exist in Rev.2 as partial prototype implementations, before becoming fully functional in v2.13: - -| Command | Rev.2 Behavior | v2.13 Behavior | -|---------|---------------|---------------| -| `0x99` | Prototype -- limited status read | Full GET_DEMOD_STATUS (reads BCM4500 reg `0xF9`) | -| `0x9A` | Prototype -- basic init call | Full INIT_DEMOD (3-attempt re-init with flag check) | - -## Cross-Version Function Mapping - -Key Rev.2 functions and their counterparts in other versions: - -| Rev.2 Function | Role | v2.06 | v2.13 | -|----------------|------|-------|-------| -| `0x155F` main | RESET entry, IRAM clear | `0x188D` | `0x170D` | -| `0x09A9` main init | Init + main loop | `0x09A7` | `0x0800` | -| `0x10D9` USB setup | Descriptor/peripheral init | `0x13C3` | `0x11AB` | -| `0x0056` vendor dispatch | Vendor command dispatcher | `0x0056` | `0x0056` | -| `0x0C64` BCM4500 loader | Firmware block transfer | `0x0DDD` | `0x0CA4` | -| `0x0D7C` GPIF/FIFO | Streaming management | `0x1919` | `0x1800` | -| `0x1BDA` delay | Clock-compensated delay | `0x1DFB` | `0x14B9` | - -## GPIO Differences from SkyWalker-1 - -The Rev.2 board has a different GPIO assignment from the standard SkyWalker-1: - -| Pin | Rev.2 v2.10 | v2.06 / v2.13 | -|-----|-------------|---------------| -| P0.0 | LNB control (cmd `0x97`) | DiSEqC data (v2.13) / unused (v2.06) | -| P0.4 | LNB voltage **+ DiSEqC data** | LNB voltage only | -| P0.5 | GPIO status input (cmd `0x98`) | BCM4500 RESET | -| P0.6 | GPIO control (cmd `0x97`) | Unused | -| P0.7 | Streaming indicator | DiSEqC data (v2.06) / streaming (v2.13) | - -The most significant difference is that Rev.2 multiplexes DiSEqC data onto the LNB voltage pin (P0.4), and the BCM4500 RESET function on P0.5 is replaced by a GPIO status input. - -## Main Loop Structure - -The Rev.2 main loop follows the same pattern as other versions but with the event handling delegated to `FUN_CODE_201e`: - -```c title="Main loop poll (simplified)" -void main_loop(void) { - // Process init table from CODE:0B48 - // Call USB/peripheral setup - // Enable interrupts - - while (1) { - if (sudav_flag) { - handle_setupdata(); - sudav_flag = 0; - } - FUN_CODE_201e(); // Delegated I2C config read/write - if (gpif_flag) { - handle_gpif_event(); - gpif_flag = 0; - } else { - PCON |= 0x01; // CPU idle - } - } -} -``` +--- +title: Rev.2 Firmware Analysis +description: Deep analysis of the Rev.2 v2.10.4 firmware variant with 107 functions and transitional architecture. +--- + +import { Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +The Rev.2 v2.10.4 firmware targets the Rev.2 hardware variant (PID `0x0202`) and contains **107 functions** -- the most of any firmware version. Despite this, it produces the smallest binary (8,843 bytes) due to aggressive function decomposition into small helper routines. + +## Architectural Position + +Rev.2 sits architecturally between v2.06 and v2.13: + +| Aspect | v2.06 | Rev.2 v2.10 | v2.13 | +|--------|-------|-------------|-------| +| INT0 behavior | USB re-enumeration | USB re-enumeration | Demod polling | +| Descriptor base | `0x1200` | `0x0E00` | `0x0E00` | +| Stack pointer | `0x72` | `0x4F` | `0x50` | +| Vendor command range | `0x80`--`0x9D` (30) | `0x80`--`0x9A` (27) | `0x80`--`0x9D` (30) | +| Demod probe at boot | No | No | Yes | +| Retry loops | No | No | Yes | +| Function count | 61 | **107** | 82-88 | +| Binary size | 9,472 bytes | **8,843 bytes** | 9,322 bytes | + + + +## Why 107 Functions? + +The high function count is driven by three factors: + +1. **Granular decomposition**: Rev.2 breaks large operations into many small helper functions (10-30 bytes each), where v2.06 inlines the same logic and v2.13 recombines it differently. + +2. **Massive configuration dispatcher**: `FUN_CODE_0800` is 874 bytes and contains an embedded copy of the main loop, causing Ghidra to count additional entry points as separate functions. + +3. **Extra I2C/demodulator helper chains**: GPIO control primitives, hardware-polling wait loops, and I2C bus management exist as individual callable units rather than being inlined. + +## Function Inventory Overview + +The 107 functions are organized into logical groups: + +### Vector Table and ISR Region (0x0000--0x0055) + +| Address | Name | Size | Role | +|---------|------|-----:|------| +| `0x0000` | `RESET_vector` | 3 | Jump to `main` at `0x155F` | +| `0x0003` | `INT0_ISR` | 12 | INT0 handler -- USB re-enumeration | +| `0x000F` | `INT0_ISR_bit_clear` | 36 | CPUCS pulse, IRQ clear, delay | +| `0x0033` | `INT2_USB_GPIF_vector` | 3 | Clears CCON.4 (PCA timer) | +| `0x0036` | `i2c_exchange_byte` | 5 | I2C byte exchange primitive | +| `0x003B` | `I2C_ISR` | 8 | I2C interrupt handler | +| `0x0043` | `INT4_FX2_vector` | 8 | Sets `_0_1` flag, clears EXIF.4 | +| `0x004B` | `INT5_FX2_vector` | 3 | Empty (RETI) | +| `0x0053` | `INT6_FX2_vector` | 3 | Sets `_0_1` flag, clears EXIF.4 | + +### Vendor Command Dispatch (0x0056--0x0319) + +| Address | Name | Size | Role | +|---------|------|-----:|------| +| `0x0056` | `vendor_cmd_dispatch` | 342 | Range check `0x80`--`0x9A`, jump table at `0x0076` | +| `0x01AC` | GET_8PSK_CONFIG handler | 361 | Reads config byte, calls LNB probe | +| `0x0315` | `vendor_cmd_stall` | 2 | Stall handler (empty RET) | +| `0x0319` | Standard USB request handler | 869 | Switch for `bRequest` `0x00`--`0x0B` | + +### Configuration and Tuning (0x0800--0x09A8) + +| Address | Name | Size | Role | +|---------|------|-----:|------| +| `0x0800` | Config/tuning dispatcher | **874** | 128-entry switch on demod type, embedded main loop | +| `0x09A9` | Main init + main loop | 699 | Hardware init, infinite poll loop | + +### BCM4500 and GPIF (0x0C64--0x0F00) + +| Address | Name | Size | Role | +|---------|------|-----:|------| +| `0x0C64` | BCM4500 firmware loader | 280 | I2C block transfer with address tracking | +| `0x0D7C` | GPIF/slave FIFO config | 128 | Enable/disable streaming mode | +| `0x0F00` | I2C multi-byte read | 256 | Parameter setup for bus transfer | + +### DiSEqC Implementation (0x07D1, 0x1D5E--0x1E3D) + +| Address | Name | Size | Role | +|---------|------|-----:|------| +| `0x07D1` | `DiSEqC byte transmit` | 45 | 8 data bits + odd parity via **P0.4** | +| `0x1D5E` | DiSEqC message sender | 59 | Iterates bytes, calls bit-bang per byte | +| `0x1E3D` | DiSEqC byte wrapper | 54 | Sets P0.2, adds inter-byte delay | +| `0x213C` | DiSEqC bit symbol | 22 | Carrier on/off via P0.3, data via P0.4 | +| `0x20E2` | 22 kHz tone burst | 23 | P0.3 ON, 25 ticks, P0.3 OFF | +| `0x225F` | Timer2 tick wait | 6 | TF2 poll and clear (500 us tick) | + + + +### LNB and GPIO Control (0x1F5C--0x2038) + +| Address | Name | Size | Role | +|---------|------|-----:|------| +| `0x1F5C` | LNB voltage I2C select | 41 | Probes I2C device `0x60` or address from `0xE0B6` | +| `0x1FCF` | GPIO pin controller | 46 | Sets P0.6, P0.0, P3.4 based on parameter bits | +| `0x2038` | GPIO clock strobe | 51 | Calls pin controller 3 times (setup, clock, cleanup) | +| `0x21B1` | LNB voltage select | 17 | Sets/clears P0.4, updates config bit 5 | +| `0x21C2` | 22 kHz tone enable | 17 | Sets/clears P0.3, updates config bit 4 | +| `0x21D3` | DiSEqC port direction | 17 | Sets/clears P3.6, updates config bit 3 | + +### I2C Bus Management (0x19F4--0x1B90) + +Rev.2 decomposes I2C operations into particularly fine-grained functions: + +| Address | Name | Size | Role | +|---------|------|-----:|------| +| `0x19F4` | I2C bus controller | 92 | Manages SDA/SCL via XRAM `0xE678` | +| `0x1A50` | I2C address select + start | 83 | START condition generation | +| `0x1AA3` | I2C stop + cleanup | 82 | STOP condition and bus release | +| `0x1AF5` | I2C address write helper | 9 | Writes device address byte | +| `0x1B01` | I2C byte-level transfer | 67 | Single byte send/receive | +| `0x1B44` | I2C ACK/NAK handling | 76 | Acknowledge detection | +| `0x1B90` | I2C bus reset/recovery | 74 | Error recovery sequence | +| `0x1F06` | I2C completion wait | 43 | Polls XRAM `0xE678` bit 0 with 16-bit timeout | +| `0x1F85` | I2C completion wait (2-flag) | 37 | Polls bits 0 and 2 | +| `0x2000` | I2C busy wait | 30 | Polls XRAM `0xE678` bit 6 with timeout | + +## Rev.2-Specific Features + +### GPIO Pin Controller (`FUN_CODE_1fcf`) + +A unique function that provides parameterized GPIO control through a bit-field interface: + +```c title="GPIO pin controller (decompiled)" +void gpio_pin_controller(BYTE param) { + if (param & 0x02) P0 |= 0x01; // P0.0 + else P0 &= ~0x01; + + if (param & 0x04) P0 |= 0x40; // P0.6 + else P0 &= ~0x40; + + if (param & 0x08) P3 |= 0x10; // P3.4 + else P3 &= ~0x10; +} +``` + +This function is called via `FUN_CODE_2038` (GPIO clock strobe) which invokes it three times per cycle -- setup, clock edge, and cleanup -- suggesting it controls a clocked peripheral interface. + +### Descriptor Version Checker (`FUN_CODE_1f31`) + +Walks a USB descriptor chain checking whether `descriptor_byte + 1 == 0x03`, enabling hardware-revision-aware code paths. This mechanism appears in a simplified form in v2.13 as the `_1_3` flag check. + +### Prototype Commands 0x99/0x9A + +Commands 0x99 and 0x9A exist in Rev.2 as partial prototype implementations, before becoming fully functional in v2.13: + +| Command | Rev.2 Behavior | v2.13 Behavior | +|---------|---------------|---------------| +| `0x99` | Prototype -- limited status read | Full GET_DEMOD_STATUS (reads BCM4500 reg `0xF9`) | +| `0x9A` | Prototype -- basic init call | Full INIT_DEMOD (3-attempt re-init with flag check) | + +## Cross-Version Function Mapping + +Key Rev.2 functions and their counterparts in other versions: + +| Rev.2 Function | Role | v2.06 | v2.13 | +|----------------|------|-------|-------| +| `0x155F` main | RESET entry, IRAM clear | `0x188D` | `0x170D` | +| `0x09A9` main init | Init + main loop | `0x09A7` | `0x0800` | +| `0x10D9` USB setup | Descriptor/peripheral init | `0x13C3` | `0x11AB` | +| `0x0056` vendor dispatch | Vendor command dispatcher | `0x0056` | `0x0056` | +| `0x0C64` BCM4500 loader | Firmware block transfer | `0x0DDD` | `0x0CA4` | +| `0x0D7C` GPIF/FIFO | Streaming management | `0x1919` | `0x1800` | +| `0x1BDA` delay | Clock-compensated delay | `0x1DFB` | `0x14B9` | + +## GPIO Differences from SkyWalker-1 + +The Rev.2 board has a different GPIO assignment from the standard SkyWalker-1: + +| Pin | Rev.2 v2.10 | v2.06 / v2.13 | +|-----|-------------|---------------| +| P0.0 | LNB control (cmd `0x97`) | DiSEqC data (v2.13) / unused (v2.06) | +| P0.4 | LNB voltage **+ DiSEqC data** | LNB voltage only | +| P0.5 | GPIO status input (cmd `0x98`) | BCM4500 RESET | +| P0.6 | GPIO control (cmd `0x97`) | Unused | +| P0.7 | Streaming indicator | DiSEqC data (v2.06) / streaming (v2.13) | + +The most significant difference is that Rev.2 multiplexes DiSEqC data onto the LNB voltage pin (P0.4), and the BCM4500 RESET function on P0.5 is replaced by a GPIO status input. + +## Main Loop Structure + +The Rev.2 main loop follows the same pattern as other versions but with the event handling delegated to `FUN_CODE_201e`: + +```c title="Main loop poll (simplified)" +void main_loop(void) { + // Process init table from CODE:0B48 + // Call USB/peripheral setup + // Enable interrupts + + while (1) { + if (sudav_flag) { + handle_setupdata(); + sudav_flag = 0; + } + FUN_CODE_201e(); // Delegated I2C config read/write + if (gpif_flag) { + handle_gpif_event(); + gpif_flag = 0; + } else { + PCON |= 0x01; // CPU idle + } + } +} +``` diff --git a/site/src/content/docs/firmware/storage-formats.mdx b/site/src/content/docs/firmware/storage-formats.mdx index 30b1710..dd52262 100644 --- a/site/src/content/docs/firmware/storage-formats.mdx +++ b/site/src/content/docs/firmware/storage-formats.mdx @@ -1,256 +1,256 @@ ---- -title: Firmware Storage Formats -description: Cypress C2 EEPROM boot format, kernel hexline format, and flat binary extraction. ---- - -import { Tabs, TabItem, Aside, FileTree, Badge } from '@astrojs/starlight/components'; - -The SkyWalker-1 firmware exists in multiple container formats depending on context: the onboard EEPROM uses the Cypress C2 IIC boot format, the Linux kernel expects a custom binary hexline format, and analysis tools work with flat extracted binaries. - -## Format Overview - - - - -### Cypress C2 IIC Second-Stage Boot Format - -This is the native format stored in the SkyWalker-1's onboard I2C EEPROM. The FX2's internal boot ROM reads this format on power-up. - -The `0xC2` marker byte in the header identifies this as "external memory, large code model" -- it tells the boot ROM to load code from the EEPROM into internal RAM using the segment map that follows. - - - - -### Kernel Binary Hexline Format - -Used only by `dvb-usb-gp8psk-01.fw` for Rev.1 Cold devices. This is a compact binary representation of Intel HEX records parsed by `dvb_usb_get_hexline()` in the kernel. It is NOT standard Intel HEX text. - - - - -### Flat Extracted Binary - -Raw 8051 machine code extracted from C2 segments. No headers or framing -- just code bytes at their target RAM addresses. Used for Ghidra analysis and binary diffing. - - - - -## C2 EEPROM Format - -### Header Structure (8 bytes) - -``` -Offset Size Field Value (SkyWalker-1) ------- ---- ---------- ------------------- -0x00 1 marker 0xC2 (external memory, large code model) -0x01 2 VID 0xC009 -> 0x09C0 (little-endian, Genpix) -0x03 2 PID 0x0302 -> 0x0203 (little-endian, SkyWalker-1) -0x05 2 DID 0x0000 (device ID, unused) -0x07 1 config 0x40 (400 kHz I2C bus speed) -``` - -The VID and PID in the C2 header determine the USB identifiers that the FX2 enumerates with after boot. This is how the kernel driver identifies the device model. - -### Config Byte (offset 0x07) - -| Value | I2C Speed | Notes | -|-------|-----------|-------| -| `0x00` | 100 kHz | Default if EEPROM missing | -| `0x20` | 200 kHz | | -| `0x40` | **400 kHz** | Used by SkyWalker-1 | - -### Code Segment Structure - -Following the 8-byte header, one or more code segments: - -``` -Offset Size Field ------- ---------- ----- -0 2 seg_len (big-endian) -- number of data bytes -2 2 seg_addr (big-endian) -- target RAM address -4 seg_len data[] -- code/data bytes -``` - -Segments are packed contiguously. The 1023-byte maximum segment size is the limit of the FX2 boot ROM's internal I2C read buffer. - -### Terminator - -``` -Offset Size Field ------- ---- ----- -0 2 0x8001 -- high bit set signals terminator (length with MSB set) -2 2 entry -- entry point address (big-endian) = 0xE600 (CPUCS) -``` - -Writing to CPUCS (`0xE600`) with value `0x00` releases the CPU from reset and begins execution at the reset vector (`0x0000`). - -## Decoded C2 Files - -### Header Comparison - -| File | VID | PID | DID | I2C Speed | Code Size | -|------|-----|-----|-----|-----------|-----------| -| `skywalker1_eeprom.bin` (v2.06) | `0x09C0` | **`0x0203`** | `0x0000` | 400 kHz | 9,472 bytes | -| `sw1_v213_fw_1_c2.bin` (v2.13.1) | `0x09C0` | **`0x0203`** | `0x0000` | 400 kHz | 9,322 bytes | -| `sw1_v213_fw_2_c2.bin` (v2.13.2) | `0x09C0` | **`0x0203`** | `0x0000` | 400 kHz | 9,377 bytes | -| `sw1_v213_fw_3_c2.bin` (v2.13.3) | `0x09C0` | **`0x0203`** | `0x0000` | 400 kHz | 9,369 bytes | -| `rev2_v210_fw_1_c2.bin` (Rev.2) | `0x09C0` | **`0x0202`** | `0x0000` | 400 kHz | 8,843 bytes | - - - -### Segment Layout (SkyWalker-1 Variants) - -All SkyWalker-1 C2 files use uniform 1023-byte segments (except the last): - -| Segment | Address | Length | Notes | -|---------|---------|-------:|-------| -| 1 | `0x0000` | 1023 | Reset vector, interrupt handlers | -| 2 | `0x03FF` | 1023 | | -| 3 | `0x07FE` | 1023 | | -| 4 | `0x0BFD` | 1023 | | -| 5 | `0x0FFC` | 1023 | | -| 6 | `0x13FB` | 1023 | | -| 7 | `0x17FA` | 1023 | | -| 8 | `0x1BF9` | 1023 | | -| 9 | `0x1FF8` | 1023 | | -| 10 | `0x23F7` | varies | 115--265 bytes depending on version | - -The address of each segment is exactly `previous_addr + 1023`, creating a contiguous mapping from `0x0000` to the end of the firmware image. - -### Rev.2 Segment Layout - -Rev.2 has only 9 segments (one fewer than SkyWalker-1 variants) because its firmware is smaller: - -| Segment | Address | Length | -|---------|---------|-------:| -| 1 | `0x0000` | 1023 | -| 2 | `0x03FF` | 1023 | -| 3 | `0x07FE` | 1023 | -| 4 | `0x0BFD` | 1023 | -| 5 | `0x0FFC` | 1023 | -| 6 | `0x13FB` | 1023 | -| 7 | `0x17FA` | 1023 | -| 8 | `0x1BF9` | 1023 | -| 9 | `0x1FF8` | 659 | - -## Kernel Binary Hexline Format - -This format is used exclusively by `dvb-usb-gp8psk-01.fw` for loading firmware into Rev.1 Cold devices (PID `0x0200`). The SkyWalker-1 never uses this format at runtime. - -### Record Structure - -``` -Offset Size Field ------- ---- ----- -0 1 len Number of data bytes in this record -1 1 addr_lo Target address, low byte -2 1 addr_hi Target address, high byte -3 1 type Record type -4 len data[] Payload bytes -4+len 1 chk Checksum byte - -Total per record: len + 5 bytes -``` - -### Record Types - -| Type | Name | Purpose | -|------|------|---------| -| `0x00` | Data | Write data bytes to FX2 RAM at address `(addr_hi * 256 + addr_lo)` | -| `0x01` | EOF | End of file, no more records | -| `0x04` | Extended Linear Address | `data[0]:data[1]` = upper 16 bits of target address | - -### Comparison With Other DVB-USB Firmware - -Files from other DVB-USB devices confirm the format: - -| File | Size | First Record | -|------|-----:|-------------| -| `dvb-usb-dib0700-1.20.fw` | 33,768 | len=2, addr=0x0000, type=0x04 | -| `dvb-usb-it9135-01.fw` | 8,128 | len=3, addr=0x0000, type=0x03 | -| `dvb-usb-it9135-02.fw` | 5,834 | len=3, addr=0x0000, type=0x03 | - -## 64K Full EEPROM Dump - -The complete EEPROM can be dumped as a 65,536-byte raw image. The firmware occupies the first ~10 KB; the remainder contains: - -| Offset Range | Content | -|-------------|---------| -| `0x0000`--`0x0007` | C2 header (8 bytes) | -| `0x0008`--`0x25xx` | Code segments (varies by version) | -| `0x25xx`--`0x26xx` | C2 terminator | -| `0x2700`--`0x3FFF` | Padding / unused (typically `0xFF`) | -| `0x4000`--`0x7FFF` | Mirror/unused (some EEPROMs wrap at 32K) | - - - -## Format Conversion - -### C2 to Flat Binary - -Extract raw code from a C2 file by stripping headers: - -```python title="C2 to flat binary extraction" -def c2_to_flat(c2_data): - """Extract flat binary from C2 EEPROM format.""" - pos = 8 # Skip 8-byte C2 header - flat = bytearray(0x10000) # 64K address space - - while pos < len(c2_data): - seg_len = (c2_data[pos] << 8) | c2_data[pos + 1] - seg_addr = (c2_data[pos + 2] << 8) | c2_data[pos + 3] - pos += 4 - - if seg_len & 0x8000: # Terminator (high bit set) - break - - flat[seg_addr:seg_addr + seg_len] = c2_data[pos:pos + seg_len] - pos += seg_len - - return flat -``` - -### C2 to Kernel Hexline - -Theoretical conversion for producing a `dvb-usb-gp8psk-01.fw` equivalent: - -```python title="C2 to kernel hexline conversion" -def c2_to_hexline(c2_data): - """Convert C2 EEPROM format to kernel binary hexline.""" - records = bytearray() - flat = c2_to_flat(c2_data) - code_end = find_code_end(flat) - - for addr in range(0, code_end, 16): - chunk = flat[addr:addr + 16] - rec_len = len(chunk) - addr_lo = addr & 0xFF - addr_hi = (addr >> 8) & 0xFF - rec_type = 0x00 # Data record - chk = (rec_len + addr_lo + addr_hi + rec_type - + sum(chunk)) & 0xFF - records.extend([rec_len, addr_lo, addr_hi, rec_type]) - records.extend(chunk) - records.append((~chk + 1) & 0xFF) - - # EOF record - records.extend([0x00, 0x00, 0x00, 0x01, 0xFF]) - return records -``` - -For the v2.06 firmware (9,472 code bytes), this produces approximately 12,442 bytes in hexline format. - -## File Identification - -Quick format identification by examining the first byte: - -| First Byte | Format | Notes | -|------------|--------|-------| -| `0xC2` | C2 EEPROM | Standard SkyWalker-1 EEPROM dump | -| `0xC0` | C0 EEPROM | VID/PID-only header (no code segments) | -| `0x02` | Flat binary | Likely starts with `LJMP` instruction (`0x02 xx xx`) | -| Other | Hexline or unknown | Check record structure | +--- +title: Firmware Storage Formats +description: Cypress C2 EEPROM boot format, kernel hexline format, and flat binary extraction. +--- + +import { Tabs, TabItem, Aside, FileTree, Badge } from '@astrojs/starlight/components'; + +The SkyWalker-1 firmware exists in multiple container formats depending on context: the onboard EEPROM uses the Cypress C2 IIC boot format, the Linux kernel expects a custom binary hexline format, and analysis tools work with flat extracted binaries. + +## Format Overview + + + + +### Cypress C2 IIC Second-Stage Boot Format + +This is the native format stored in the SkyWalker-1's onboard I2C EEPROM. The FX2's internal boot ROM reads this format on power-up. + +The `0xC2` marker byte in the header identifies this as "external memory, large code model" -- it tells the boot ROM to load code from the EEPROM into internal RAM using the segment map that follows. + + + + +### Kernel Binary Hexline Format + +Used only by `dvb-usb-gp8psk-01.fw` for Rev.1 Cold devices. This is a compact binary representation of Intel HEX records parsed by `dvb_usb_get_hexline()` in the kernel. It is NOT standard Intel HEX text. + + + + +### Flat Extracted Binary + +Raw 8051 machine code extracted from C2 segments. No headers or framing -- just code bytes at their target RAM addresses. Used for Ghidra analysis and binary diffing. + + + + +## C2 EEPROM Format + +### Header Structure (8 bytes) + +``` +Offset Size Field Value (SkyWalker-1) +------ ---- ---------- ------------------- +0x00 1 marker 0xC2 (external memory, large code model) +0x01 2 VID 0xC009 -> 0x09C0 (little-endian, Genpix) +0x03 2 PID 0x0302 -> 0x0203 (little-endian, SkyWalker-1) +0x05 2 DID 0x0000 (device ID, unused) +0x07 1 config 0x40 (400 kHz I2C bus speed) +``` + +The VID and PID in the C2 header determine the USB identifiers that the FX2 enumerates with after boot. This is how the kernel driver identifies the device model. + +### Config Byte (offset 0x07) + +| Value | I2C Speed | Notes | +|-------|-----------|-------| +| `0x00` | 100 kHz | Default if EEPROM missing | +| `0x20` | 200 kHz | | +| `0x40` | **400 kHz** | Used by SkyWalker-1 | + +### Code Segment Structure + +Following the 8-byte header, one or more code segments: + +``` +Offset Size Field +------ ---------- ----- +0 2 seg_len (big-endian) -- number of data bytes +2 2 seg_addr (big-endian) -- target RAM address +4 seg_len data[] -- code/data bytes +``` + +Segments are packed contiguously. The 1023-byte maximum segment size is the limit of the FX2 boot ROM's internal I2C read buffer. + +### Terminator + +``` +Offset Size Field +------ ---- ----- +0 2 0x8001 -- high bit set signals terminator (length with MSB set) +2 2 entry -- entry point address (big-endian) = 0xE600 (CPUCS) +``` + +Writing to CPUCS (`0xE600`) with value `0x00` releases the CPU from reset and begins execution at the reset vector (`0x0000`). + +## Decoded C2 Files + +### Header Comparison + +| File | VID | PID | DID | I2C Speed | Code Size | +|------|-----|-----|-----|-----------|-----------| +| `skywalker1_eeprom.bin` (v2.06) | `0x09C0` | **`0x0203`** | `0x0000` | 400 kHz | 9,472 bytes | +| `sw1_v213_fw_1_c2.bin` (v2.13.1) | `0x09C0` | **`0x0203`** | `0x0000` | 400 kHz | 9,322 bytes | +| `sw1_v213_fw_2_c2.bin` (v2.13.2) | `0x09C0` | **`0x0203`** | `0x0000` | 400 kHz | 9,377 bytes | +| `sw1_v213_fw_3_c2.bin` (v2.13.3) | `0x09C0` | **`0x0203`** | `0x0000` | 400 kHz | 9,369 bytes | +| `rev2_v210_fw_1_c2.bin` (Rev.2) | `0x09C0` | **`0x0202`** | `0x0000` | 400 kHz | 8,843 bytes | + + + +### Segment Layout (SkyWalker-1 Variants) + +All SkyWalker-1 C2 files use uniform 1023-byte segments (except the last): + +| Segment | Address | Length | Notes | +|---------|---------|-------:|-------| +| 1 | `0x0000` | 1023 | Reset vector, interrupt handlers | +| 2 | `0x03FF` | 1023 | | +| 3 | `0x07FE` | 1023 | | +| 4 | `0x0BFD` | 1023 | | +| 5 | `0x0FFC` | 1023 | | +| 6 | `0x13FB` | 1023 | | +| 7 | `0x17FA` | 1023 | | +| 8 | `0x1BF9` | 1023 | | +| 9 | `0x1FF8` | 1023 | | +| 10 | `0x23F7` | varies | 115--265 bytes depending on version | + +The address of each segment is exactly `previous_addr + 1023`, creating a contiguous mapping from `0x0000` to the end of the firmware image. + +### Rev.2 Segment Layout + +Rev.2 has only 9 segments (one fewer than SkyWalker-1 variants) because its firmware is smaller: + +| Segment | Address | Length | +|---------|---------|-------:| +| 1 | `0x0000` | 1023 | +| 2 | `0x03FF` | 1023 | +| 3 | `0x07FE` | 1023 | +| 4 | `0x0BFD` | 1023 | +| 5 | `0x0FFC` | 1023 | +| 6 | `0x13FB` | 1023 | +| 7 | `0x17FA` | 1023 | +| 8 | `0x1BF9` | 1023 | +| 9 | `0x1FF8` | 659 | + +## Kernel Binary Hexline Format + +This format is used exclusively by `dvb-usb-gp8psk-01.fw` for loading firmware into Rev.1 Cold devices (PID `0x0200`). The SkyWalker-1 never uses this format at runtime. + +### Record Structure + +``` +Offset Size Field +------ ---- ----- +0 1 len Number of data bytes in this record +1 1 addr_lo Target address, low byte +2 1 addr_hi Target address, high byte +3 1 type Record type +4 len data[] Payload bytes +4+len 1 chk Checksum byte + +Total per record: len + 5 bytes +``` + +### Record Types + +| Type | Name | Purpose | +|------|------|---------| +| `0x00` | Data | Write data bytes to FX2 RAM at address `(addr_hi * 256 + addr_lo)` | +| `0x01` | EOF | End of file, no more records | +| `0x04` | Extended Linear Address | `data[0]:data[1]` = upper 16 bits of target address | + +### Comparison With Other DVB-USB Firmware + +Files from other DVB-USB devices confirm the format: + +| File | Size | First Record | +|------|-----:|-------------| +| `dvb-usb-dib0700-1.20.fw` | 33,768 | len=2, addr=0x0000, type=0x04 | +| `dvb-usb-it9135-01.fw` | 8,128 | len=3, addr=0x0000, type=0x03 | +| `dvb-usb-it9135-02.fw` | 5,834 | len=3, addr=0x0000, type=0x03 | + +## 64K Full EEPROM Dump + +The complete EEPROM can be dumped as a 65,536-byte raw image. The firmware occupies the first ~10 KB; the remainder contains: + +| Offset Range | Content | +|-------------|---------| +| `0x0000`--`0x0007` | C2 header (8 bytes) | +| `0x0008`--`0x25xx` | Code segments (varies by version) | +| `0x25xx`--`0x26xx` | C2 terminator | +| `0x2700`--`0x3FFF` | Padding / unused (typically `0xFF`) | +| `0x4000`--`0x7FFF` | Mirror/unused (some EEPROMs wrap at 32K) | + + + +## Format Conversion + +### C2 to Flat Binary + +Extract raw code from a C2 file by stripping headers: + +```python title="C2 to flat binary extraction" +def c2_to_flat(c2_data): + """Extract flat binary from C2 EEPROM format.""" + pos = 8 # Skip 8-byte C2 header + flat = bytearray(0x10000) # 64K address space + + while pos < len(c2_data): + seg_len = (c2_data[pos] << 8) | c2_data[pos + 1] + seg_addr = (c2_data[pos + 2] << 8) | c2_data[pos + 3] + pos += 4 + + if seg_len & 0x8000: # Terminator (high bit set) + break + + flat[seg_addr:seg_addr + seg_len] = c2_data[pos:pos + seg_len] + pos += seg_len + + return flat +``` + +### C2 to Kernel Hexline + +Theoretical conversion for producing a `dvb-usb-gp8psk-01.fw` equivalent: + +```python title="C2 to kernel hexline conversion" +def c2_to_hexline(c2_data): + """Convert C2 EEPROM format to kernel binary hexline.""" + records = bytearray() + flat = c2_to_flat(c2_data) + code_end = find_code_end(flat) + + for addr in range(0, code_end, 16): + chunk = flat[addr:addr + 16] + rec_len = len(chunk) + addr_lo = addr & 0xFF + addr_hi = (addr >> 8) & 0xFF + rec_type = 0x00 # Data record + chk = (rec_len + addr_lo + addr_hi + rec_type + + sum(chunk)) & 0xFF + records.extend([rec_len, addr_lo, addr_hi, rec_type]) + records.extend(chunk) + records.append((~chk + 1) & 0xFF) + + # EOF record + records.extend([0x00, 0x00, 0x00, 0x01, 0xFF]) + return records +``` + +For the v2.06 firmware (9,472 code bytes), this produces approximately 12,442 bytes in hexline format. + +## File Identification + +Quick format identification by examining the first byte: + +| First Byte | Format | Notes | +|------------|--------|-------| +| `0xC2` | C2 EEPROM | Standard SkyWalker-1 EEPROM dump | +| `0xC0` | C0 EEPROM | VID/PID-only header (no code segments) | +| `0x02` | Flat binary | Likely starts with `LJMP` instruction (`0x02 xx xx`) | +| Other | Hexline or unknown | Check record structure | diff --git a/site/src/content/docs/firmware/version-comparison.mdx b/site/src/content/docs/firmware/version-comparison.mdx index d8a7e91..ac1f4b0 100644 --- a/site/src/content/docs/firmware/version-comparison.mdx +++ b/site/src/content/docs/firmware/version-comparison.mdx @@ -1,330 +1,330 @@ ---- -title: Firmware Version Comparison -description: Side-by-side comparison of all known SkyWalker-1 firmware revisions including stock and custom builds. ---- - -import { Tabs, TabItem, Badge, Aside } from '@astrojs/starlight/components'; - -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` | -| v2.13.03 (FW3) | `0x020D01` | 2010-03-12 | `0x0203` | 83 | 9,369 bytes | `0x52` | -| Rev.2 v2.10.04 | `0x020A04` | 2010-03-12 | `0x0202` | 107 | 8,843 bytes | `0x4F` | -| v2.06.04 | `0x020604` | 2007-07-13 | `0x0203` | 61 | 9,472 bytes | `0x72` | - - - -## Version-by-Version Details - - - - -### Custom v3.05.0 - -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. - - - - -### Custom v3.01.0 - -Open-source replacement firmware built with SDCC + fx2lib. RAM-loaded for testing. - -| Property | Value | -|----------|-------| -| Version ID | `0x030100` | -| Build date | 2026-02-12 | -| Toolchain | SDCC + fx2lib | -| Source | `firmware/skywalker1.c` (1351 lines) | -| Binary size | ~3 KB | -| Load method | RAM upload via `tools/fw_load.py` | -| DiSEqC data pin | P0.7 (v2.06 assignment) | - -**Additions over stock:** -- Seven new diagnostic commands (`0xB0`--`0xB6`) -- Incremental debug boot modes (wValue `0x80`--`0x85` for BOOT_8PSK) -- I2C timeout protection (6000-iteration countdown vs. infinite spin) -- I2C bus scan for device discovery -- Spectrum sweep and blind scan capabilities -- Raw BCM4500 register access - -See the [Custom v3.01](/firmware/custom-v301/) page for full details. - - - - -### v2.13.01 (FW1) - -The most feature-complete stock firmware, targeting the original I2C-connected SkyWalker-1 hardware. - -| Property | Value | -|----------|-------| -| Version ID | `0x020D01` | -| Build date | 2010-03-12 | -| Functions | 82-88 | -| Binary size | 9,322 bytes | -| Stack pointer | `0x50` | -| Config byte IRAM | `0x4F` | -| Descriptor base | `0x0E00` | -| Init table address | `CODE:0B88` | -| Vendor commands | 30 (`0x80`--`0x9D`) | -| DiSEqC data pin | P0.0 | - -**New features over v2.06:** -- Three new vendor commands: `0x99` (GET_DEMOD_STATUS), `0x9A` (INIT_DEMOD), `0x9C` (DELAY_COMMAND) -- INT0 repurposed for demodulator availability polling (40 attempts at addresses 0x7F and 0x3F) -- USB re-enumeration moved to `FUN_CODE_2031` (called as normal function before main loop) -- Demodulator signature verification (`FUN_CODE_1799`) with 20 retry attempts -- Descriptor checksum verification (`FUN_CODE_1ca0`) with 20 retry attempts -- Hardware revision detection via descriptor byte (flag `_1_3`) -- Consolidated BCM4500 status polling (1 register instead of 3) -- Anti-tampering string at firmware offset 0x1880 - -See the [FW2.13 Variants](/firmware/fw213-variants/) page for sub-variant comparison. - - - - -### Rev.2 v2.10.04 - -Firmware for the Rev.2 hardware variant (PID `0x0202`). Architecturally sits between v2.06 and v2.13. - -| Property | Value | -|----------|-------| -| Version ID | `0x020A04` | -| Build date | 2010-03-12 | -| Functions | 107 (most of any version) | -| Binary size | 8,843 bytes (smallest) | -| Stack pointer | `0x4F` | -| Config byte IRAM | `0x4E` | -| Descriptor base | `0x0E00` | -| Init table address | `CODE:0B48` | -| Vendor commands | 27 (`0x80`--`0x9A`) | -| DiSEqC data pin | P0.4 | - -**Characteristics:** -- Highest function count due to granular decomposition (10-30 byte helper functions) -- Smallest binary despite having the most functions -- Retained v2.06's INT0 USB re-enumeration behavior -- Already adopted v2.13's descriptor base (`0x0E00`) and similar stack pointer -- Contains `FUN_CODE_0800`: a massive 874-byte configuration dispatcher with embedded main loop -- Lacks v2.13's demodulator polling, retry loops, and additional vendor commands (0x9B--0x9D out of range) -- 0x99/0x9A present as prototype implementations - -See the [Rev.2 Analysis](/firmware/rev2-analysis/) page for the complete 107-function inventory. - - - - -### v2.06.04 - -The original SkyWalker-1 firmware extracted from the device's onboard EEPROM. - -| Property | Value | -|----------|-------| -| Version ID | `0x020604` | -| Build date | 2007-07-13 | -| Functions | 61 | -| Binary size | 9,472 bytes | -| Stack pointer | `0x72` | -| Config byte IRAM | `0x6D` | -| Descriptor base | `0x1200` | -| Init table address | `CODE:0B46` | -| Vendor commands | 30 (`0x80`--`0x9D`) | -| DiSEqC data pin | P0.7 | - -**Characteristics:** -- Simplest firmware with the fewest functions -- INT0 handler performs USB re-enumeration (CPUCS pulse) -- No demodulator probe at boot -- No retry loops or integrity verification -- BCM4500 status polling reads 3 registers (0xA2, 0xA8, 0xA4) up to 6 times -- Commands 0x99, 0x9A, 0x9C route to STALL -- Command 0x9D reads descriptor byte and sets mode flag based on hardware revision (4, 5, or 6) - - - -## Architectural Differences - -| 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 - -The Linux kernel driver defines two firmware version thresholds in `gp8psk-fe.h`: - -```c title="Kernel firmware version constants" -GP8PSK_FW_REV1 = 0x020604 // v2.06.4 -GP8PSK_FW_REV2 = 0x020704 // v2.07.4 -``` - -If the firmware version reported by `GET_FW_VERS` (command `0x92`) is >= `GP8PSK_FW_REV2`, the kernel enables Rev.2-specific code paths. All v2.10 and v2.13 firmwares are newer than either constant. - - - -## Binary Similarity Matrix - -Byte-level comparison across the shared code length (percentage of identical bytes): - -| | v2.06 | v2.13.1 | v2.13.2 | v2.13.3 | Rev.2 | -|---|---|---|---|---|---| -| **v2.06** | -- | 4.8% | 4.3% | 4.3% | 6.0% | -| **v2.13.1** | | -- | 57.2% | 59.4% | 8.0% | -| **v2.13.2** | | | -- | 83.5% | 5.8% | -| **v2.13.3** | | | | -- | 5.8% | -| **Rev.2** | | | | | -- | - - - -## Key Function Correspondence - -Functions that serve the same role but reside at different addresses: - -| Role | v2.06 | Rev.2 | v2.13 | -|------|-------|-------|-------| -| RESET vector / main | `0x188D` | `0x155F` | `0x170D` | -| Main init + loop | `0x09A7` | `0x09A9` | `0x0800` | -| USB descriptor setup | `0x13C3` | `0x10D9` | `0x11AB` | -| Standard USB handler | `0x032A` | `0x0319` | `0x034E` | -| Vendor cmd dispatch | `0x0056` | `0x0056` | `0x0056` | -| Main loop poll | `0x2297` | -- | `0x21EC` | -| GPIF/FIFO management | `0x1919` | `0x0D7C` | `0x1800` | -| BCM4500 firmware loader | `0x0DDD` | `0x0C64` | `0x0CA4` | -| BCM4500 status polling | `0x2000` | -- | `0x208D` | -| Delay loop | `0x1DFB` | `0x1BDA` | `0x14B9` | - -## INT0 Handler Evolution - -The INT0 interrupt vector (`CODE:0003`) was repurposed between firmware generations: - - - - -**USB Re-enumeration** -- pulses CPUCS bit 3 to trigger controlled USB disconnect/reconnect: - -```c title="INT0 handler (v2.06 and Rev.2)" -void INT0_vec(void) { - if (flag == 0) CPUCS |= 0x08; // CPUCS bit 3 - else CPUCS |= 0x0A; // CPUCS bits 3+1 - delay(5, 0xDC); // ~1500 cycles - EPIRQ = 0xFF; // Clear endpoint IRQs - USBIRQ = 0xFF; // Clear USB IRQs - EXIF &= 0xEF; // Clear external interrupt flag - CPUCS &= 0xF7; // Clear CPUCS bit 3 -} -``` - - - - -**Demodulator Availability Polling** -- probes two I2C addresses up to 40 times: - -```c title="INT0 handler (v2.13)" -void INT0_vector(void) { - for (counter = 0x28; counter != 0; counter--) { - byte result = I2C_read(0x7F); - if (result != 0x01) { - result = I2C_read(0x3F); - if (result != 0x01) break; - } - } - no_demod_flag = (counter == 0); -} -``` - -The USB re-enumeration logic was moved to `FUN_CODE_2031` and called as a normal function before the main loop. - - - -## XRAM Initialization Table - -All versions initialize FX2 peripheral registers from a CODE-space table at startup. The table format is identical: `[addr_hi] [addr_lo] [data_byte]` triplets terminated by `0x0000`. - -| Firmware | Table Address | Key Registers Set | -|----------|--------------|-------------------| -| v2.06 | `CODE:0B46` | IFCONFIG, EP2CFG, EP2FIFOCFG, REVCTL, I2CTL | -| Rev.2 | `CODE:0B48` | Same set, 2 bytes later | -| v2.13 | `CODE:0B88` | Same set, different offsets | - -All versions set the same final values: `IFCONFIG=0xEE`, `EP2CFG=0xE2`, `EP2FIFOCFG=0x0C`, `REVCTL=0x03`, `I2CTL=0x01`. - -## Anti-Tampering (v2.13 Only) - -All v2.13 sub-variants contain this string at firmware offset `0x1880`: - -``` -"Tampering is detected. Attempt is logged. Warranty is voided ! \n" -``` - -This is followed by I2C register write commands (`01 10 aa 82 02 41 41 83`). The mechanism is absent from v2.06, Rev.2, and the custom firmware. - -## Version Identification - -The `GET_FW_VERS` command (`0x92`) returns 6 bytes of hardcoded constants: - -``` -Byte 0: version minor_minor (e.g., 0x04) -Byte 1: version minor (e.g., 0x06) -Byte 2: version major (e.g., 0x02) -Byte 3: build day (e.g., 0x0D = 13) -Byte 4: build month (e.g., 0x07 = July) -Byte 5: build year - 2000 (e.g., 0x07 = 2007) -``` - -Full version = `byte[2] << 16 | byte[1] << 8 | byte[0]`. Build date = `(2000 + byte[5]) / byte[4] / byte[3]`. +--- +title: Firmware Version Comparison +description: Side-by-side comparison of all known SkyWalker-1 firmware revisions including stock and custom builds. +--- + +import { Tabs, TabItem, Badge, Aside } from '@astrojs/starlight/components'; + +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` | +| v2.13.03 (FW3) | `0x020D01` | 2010-03-12 | `0x0203` | 83 | 9,369 bytes | `0x52` | +| Rev.2 v2.10.04 | `0x020A04` | 2010-03-12 | `0x0202` | 107 | 8,843 bytes | `0x4F` | +| v2.06.04 | `0x020604` | 2007-07-13 | `0x0203` | 61 | 9,472 bytes | `0x72` | + + + +## Version-by-Version Details + + + + +### Custom v3.05.0 + +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. + + + + +### Custom v3.01.0 + +Open-source replacement firmware built with SDCC + fx2lib. RAM-loaded for testing. + +| Property | Value | +|----------|-------| +| Version ID | `0x030100` | +| Build date | 2026-02-12 | +| Toolchain | SDCC + fx2lib | +| Source | `firmware/skywalker1.c` (1351 lines) | +| Binary size | ~3 KB | +| Load method | RAM upload via `tools/fw_load.py` | +| DiSEqC data pin | P0.7 (v2.06 assignment) | + +**Additions over stock:** +- Seven new diagnostic commands (`0xB0`--`0xB6`) +- Incremental debug boot modes (wValue `0x80`--`0x85` for BOOT_8PSK) +- I2C timeout protection (6000-iteration countdown vs. infinite spin) +- I2C bus scan for device discovery +- Spectrum sweep and blind scan capabilities +- Raw BCM4500 register access + +See the [Custom v3.01](/firmware/custom-v301/) page for full details. + + + + +### v2.13.01 (FW1) + +The most feature-complete stock firmware, targeting the original I2C-connected SkyWalker-1 hardware. + +| Property | Value | +|----------|-------| +| Version ID | `0x020D01` | +| Build date | 2010-03-12 | +| Functions | 82-88 | +| Binary size | 9,322 bytes | +| Stack pointer | `0x50` | +| Config byte IRAM | `0x4F` | +| Descriptor base | `0x0E00` | +| Init table address | `CODE:0B88` | +| Vendor commands | 30 (`0x80`--`0x9D`) | +| DiSEqC data pin | P0.0 | + +**New features over v2.06:** +- Three new vendor commands: `0x99` (GET_DEMOD_STATUS), `0x9A` (INIT_DEMOD), `0x9C` (DELAY_COMMAND) +- INT0 repurposed for demodulator availability polling (40 attempts at addresses 0x7F and 0x3F) +- USB re-enumeration moved to `FUN_CODE_2031` (called as normal function before main loop) +- Demodulator signature verification (`FUN_CODE_1799`) with 20 retry attempts +- Descriptor checksum verification (`FUN_CODE_1ca0`) with 20 retry attempts +- Hardware revision detection via descriptor byte (flag `_1_3`) +- Consolidated BCM4500 status polling (1 register instead of 3) +- Anti-tampering string at firmware offset 0x1880 + +See the [FW2.13 Variants](/firmware/fw213-variants/) page for sub-variant comparison. + + + + +### Rev.2 v2.10.04 + +Firmware for the Rev.2 hardware variant (PID `0x0202`). Architecturally sits between v2.06 and v2.13. + +| Property | Value | +|----------|-------| +| Version ID | `0x020A04` | +| Build date | 2010-03-12 | +| Functions | 107 (most of any version) | +| Binary size | 8,843 bytes (smallest) | +| Stack pointer | `0x4F` | +| Config byte IRAM | `0x4E` | +| Descriptor base | `0x0E00` | +| Init table address | `CODE:0B48` | +| Vendor commands | 27 (`0x80`--`0x9A`) | +| DiSEqC data pin | P0.4 | + +**Characteristics:** +- Highest function count due to granular decomposition (10-30 byte helper functions) +- Smallest binary despite having the most functions +- Retained v2.06's INT0 USB re-enumeration behavior +- Already adopted v2.13's descriptor base (`0x0E00`) and similar stack pointer +- Contains `FUN_CODE_0800`: a massive 874-byte configuration dispatcher with embedded main loop +- Lacks v2.13's demodulator polling, retry loops, and additional vendor commands (0x9B--0x9D out of range) +- 0x99/0x9A present as prototype implementations + +See the [Rev.2 Analysis](/firmware/rev2-analysis/) page for the complete 107-function inventory. + + + + +### v2.06.04 + +The original SkyWalker-1 firmware extracted from the device's onboard EEPROM. + +| Property | Value | +|----------|-------| +| Version ID | `0x020604` | +| Build date | 2007-07-13 | +| Functions | 61 | +| Binary size | 9,472 bytes | +| Stack pointer | `0x72` | +| Config byte IRAM | `0x6D` | +| Descriptor base | `0x1200` | +| Init table address | `CODE:0B46` | +| Vendor commands | 30 (`0x80`--`0x9D`) | +| DiSEqC data pin | P0.7 | + +**Characteristics:** +- Simplest firmware with the fewest functions +- INT0 handler performs USB re-enumeration (CPUCS pulse) +- No demodulator probe at boot +- No retry loops or integrity verification +- BCM4500 status polling reads 3 registers (0xA2, 0xA8, 0xA4) up to 6 times +- Commands 0x99, 0x9A, 0x9C route to STALL +- Command 0x9D reads descriptor byte and sets mode flag based on hardware revision (4, 5, or 6) + + + +## Architectural Differences + +| 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 + +The Linux kernel driver defines two firmware version thresholds in `gp8psk-fe.h`: + +```c title="Kernel firmware version constants" +GP8PSK_FW_REV1 = 0x020604 // v2.06.4 +GP8PSK_FW_REV2 = 0x020704 // v2.07.4 +``` + +If the firmware version reported by `GET_FW_VERS` (command `0x92`) is >= `GP8PSK_FW_REV2`, the kernel enables Rev.2-specific code paths. All v2.10 and v2.13 firmwares are newer than either constant. + + + +## Binary Similarity Matrix + +Byte-level comparison across the shared code length (percentage of identical bytes): + +| | v2.06 | v2.13.1 | v2.13.2 | v2.13.3 | Rev.2 | +|---|---|---|---|---|---| +| **v2.06** | -- | 4.8% | 4.3% | 4.3% | 6.0% | +| **v2.13.1** | | -- | 57.2% | 59.4% | 8.0% | +| **v2.13.2** | | | -- | 83.5% | 5.8% | +| **v2.13.3** | | | | -- | 5.8% | +| **Rev.2** | | | | | -- | + + + +## Key Function Correspondence + +Functions that serve the same role but reside at different addresses: + +| Role | v2.06 | Rev.2 | v2.13 | +|------|-------|-------|-------| +| RESET vector / main | `0x188D` | `0x155F` | `0x170D` | +| Main init + loop | `0x09A7` | `0x09A9` | `0x0800` | +| USB descriptor setup | `0x13C3` | `0x10D9` | `0x11AB` | +| Standard USB handler | `0x032A` | `0x0319` | `0x034E` | +| Vendor cmd dispatch | `0x0056` | `0x0056` | `0x0056` | +| Main loop poll | `0x2297` | -- | `0x21EC` | +| GPIF/FIFO management | `0x1919` | `0x0D7C` | `0x1800` | +| BCM4500 firmware loader | `0x0DDD` | `0x0C64` | `0x0CA4` | +| BCM4500 status polling | `0x2000` | -- | `0x208D` | +| Delay loop | `0x1DFB` | `0x1BDA` | `0x14B9` | + +## INT0 Handler Evolution + +The INT0 interrupt vector (`CODE:0003`) was repurposed between firmware generations: + + + + +**USB Re-enumeration** -- pulses CPUCS bit 3 to trigger controlled USB disconnect/reconnect: + +```c title="INT0 handler (v2.06 and Rev.2)" +void INT0_vec(void) { + if (flag == 0) CPUCS |= 0x08; // CPUCS bit 3 + else CPUCS |= 0x0A; // CPUCS bits 3+1 + delay(5, 0xDC); // ~1500 cycles + EPIRQ = 0xFF; // Clear endpoint IRQs + USBIRQ = 0xFF; // Clear USB IRQs + EXIF &= 0xEF; // Clear external interrupt flag + CPUCS &= 0xF7; // Clear CPUCS bit 3 +} +``` + + + + +**Demodulator Availability Polling** -- probes two I2C addresses up to 40 times: + +```c title="INT0 handler (v2.13)" +void INT0_vector(void) { + for (counter = 0x28; counter != 0; counter--) { + byte result = I2C_read(0x7F); + if (result != 0x01) { + result = I2C_read(0x3F); + if (result != 0x01) break; + } + } + no_demod_flag = (counter == 0); +} +``` + +The USB re-enumeration logic was moved to `FUN_CODE_2031` and called as a normal function before the main loop. + + + +## XRAM Initialization Table + +All versions initialize FX2 peripheral registers from a CODE-space table at startup. The table format is identical: `[addr_hi] [addr_lo] [data_byte]` triplets terminated by `0x0000`. + +| Firmware | Table Address | Key Registers Set | +|----------|--------------|-------------------| +| v2.06 | `CODE:0B46` | IFCONFIG, EP2CFG, EP2FIFOCFG, REVCTL, I2CTL | +| Rev.2 | `CODE:0B48` | Same set, 2 bytes later | +| v2.13 | `CODE:0B88` | Same set, different offsets | + +All versions set the same final values: `IFCONFIG=0xEE`, `EP2CFG=0xE2`, `EP2FIFOCFG=0x0C`, `REVCTL=0x03`, `I2CTL=0x01`. + +## Anti-Tampering (v2.13 Only) + +All v2.13 sub-variants contain this string at firmware offset `0x1880`: + +``` +"Tampering is detected. Attempt is logged. Warranty is voided ! \n" +``` + +This is followed by I2C register write commands (`01 10 aa 82 02 41 41 83`). The mechanism is absent from v2.06, Rev.2, and the custom firmware. + +## Version Identification + +The `GET_FW_VERS` command (`0x92`) returns 6 bytes of hardcoded constants: + +``` +Byte 0: version minor_minor (e.g., 0x04) +Byte 1: version minor (e.g., 0x06) +Byte 2: version major (e.g., 0x02) +Byte 3: build day (e.g., 0x0D = 13) +Byte 4: build month (e.g., 0x07 = July) +Byte 5: build year - 2000 (e.g., 0x07 = 2007) +``` + +Full version = `byte[2] << 16 | byte[1] << 8 | byte[0]`. Build date = `(2000 + byte[5]) / byte[4] / byte[3]`. diff --git a/site/src/content/docs/guides/applications.mdx b/site/src/content/docs/guides/applications.mdx index 4f5a4ef..509dc3e 100644 --- a/site/src/content/docs/guides/applications.mdx +++ b/site/src/content/docs/guides/applications.mdx @@ -1,322 +1,322 @@ ---- -title: Applications & Use Cases -description: What can the SkyWalker-1 actually do? Satellite TV, multi-standard signal analysis, radio astronomy, RF measurement, and more. ---- - -import { Aside, Badge, Card, CardGrid, Tabs, TabItem } from '@astrojs/starlight/components'; - -The SkyWalker-1 shipped as a DVB-S satellite TV receiver. With [custom firmware](/firmware/custom-v305/) and -the reverse-engineered USB/I2C interface, it becomes something more interesting: a programmable RF instrument -covering 950-2150 MHz with ten demodulation modes, 256 Ksps to 30 Msps symbol rates, and full Python control. - -Here's what you can actually do with it. - -## Satellite TV Reception - -The obvious one. The SkyWalker-1 receives free-to-air (FTA) DVB-S content — unencrypted satellite television -and radio that anyone with a dish can watch. - -### What's Up There - - - - Most FTA content in North America lives on Ku-band satellites. A standard 30-36 inch dish and - universal LNB is all you need. - - | Satellite | Position | What's On It | - |-----------|----------|-------------| - | Galaxy 19 | 97.0°W | The FTA motherlode. ~135+ channels: Chinese, Korean, South Asian, religious, shopping, some English | - | Galaxy 16 | 99.0°W | Religious programming, international | - | SES-2 | 87.0°W | International, government | - | AMC-18 | 105.0°W | Mixed FTA and encrypted | - - Typical tuning parameters: 11836 MHz V-pol, 20770 ksps, DVB-S QPSK FEC 3/4. - - - C-band requires a larger dish (6-12 feet) and a C-band LNB, but carries content that never made it - to Ku-band — including DigiCipher II muxes that the SkyWalker-1 uniquely supports. - - | Satellite | Position | What's On It | - |-----------|----------|-------------| - | AMC-18 | 105.0°W | DCII cable distribution, some FTA | - | SES-2 | 87.0°W | International, government feeds | - | Galaxy 16 | 99.0°W | Mixed distribution | - - The FCC C-band transition compressed services into the upper 3.98-4.2 GHz range. Additional - spectrum auctions are proposed for 2027 — C-band FTA is on borrowed time. - - - -### FTA Resources - -Current channel listings change frequently. These sites track what's active: - -- [LyngSat](https://www.lyngsat.com/) — comprehensive transponder and channel database -- [SatExpat](https://www.satexpat.com/) — FTA channel listings with satellite footprints -- [FTAList](https://ftalist.com/) — North American FTA community and channel guide - - - -## Multi-Standard Signal Analysis - -This is where the SkyWalker-1 becomes genuinely rare hardware. The BCM4500 demodulates standards that are -nearly extinct in available consumer equipment — standards that are still actively broadcasting. - - - - Cable headend distribution format (Comcast HITS, Motorola). One of very few modern devices with DCII - support. "Zero Key" unencrypted services are directly receivable. - - - Digital Satellite Service — legacy DirecTV format with 127-byte transport packets (vs 188-byte DVB). - Extraordinarily rare outside DirecTV hardware. - - - DISH Network transponder format. Encrypted content, but demodulator lock and transport stream capture - work — useful for signal analysis and protocol research. - - - Earlier turbo-coded variant. Better spectral efficiency than standard DVB-S QPSK, still used on - some distribution paths. - - - -### Why This Matters - -These standards are still active on-air, but the hardware to receive them is disappearing. Off-the-shelf -satellite receivers dropped DCII and DSS support years ago. The SkyWalker-1, through its BCM4500 demodulator, -retains these capabilities — making it a **preservation and research tool** for signal formats that will -eventually go silent. - -The [TS Analyzer](/tools/ts-analyzer/) can parse transport streams from all supported modulation types, -making it possible to compare DVB-S, DCII, and DSS packet structures side by side. - -See [BCM4500 Demodulator](/bcm4500/demodulator/) for register-level details on how each modulation type -is configured. - -## Wild Feed & Backhaul Hunting - -Satellite transponders carry more than scheduled programming. Temporary unencrypted uplinks — "wild feeds" — -appear and disappear throughout the day: - -- **Live news remotes**: Raw camera feeds from field reporters, unedited and uncensored -- **Sports backhauls**: Stadium camera feeds before production mixing -- **Network distribution**: Programs fed to affiliates before air time -- **Event coverage**: Press conferences, hearings, launches - -The SkyWalker-1's blind scan capability and wide symbol rate range (256 Ksps - 30 Msps) make it well-suited -for finding these transient signals. The [Carrier Survey](/tools/survey/) tool automates the sweep-and-lock -cycle across a full satellite. - - - -## Radio Astronomy - -The 950-2150 MHz IF range — or, without an LNB, the direct input range — overlaps with several -astrophysically interesting frequencies. The BCM4500's AGC registers respond to any RF energy at the -tuned frequency, regardless of whether it carries a demodulatable signal. - -### Hydrogen 21 cm - - - -Neutral hydrogen emits at **1420.405 MHz** — directly in the IF range with no LNB. Connect an L-band -antenna (patch, helical, or horn) to the F-connector and the SkyWalker-1 becomes a hydrogen line -radiometer. The velocity-dispersed emission from the Milky Way's spiral arms is detectable even -with the BCM4500's ~346 kHz resolution bandwidth. - -See [Hydrogen 21 cm Radiometer](/tools/h21cm/) for the full tool reference. - -### Ku-Band Solar Observation - -Point a standard satellite dish + LNB at the Sun. At 10-12 GHz, solar thermal emission produces a -detectable **6+ dB rise** above the cold-sky background. Solar flares produce wideband bursts that -are even more dramatic. - -The SkyWalker-1's advantage over an RTL-SDR here is bandwidth: the 30 Msps sweep capability covers -a much wider swath of spectrum (~30 MHz effective) compared to the RTL-SDR's ~2.4 MHz, making it -easier to detect and characterize broadband solar events. - -Use the [Spectrum Analysis](/tools/spectrum-analysis/) sweep mode to build solar emission profiles. - -### Moon Thermal Emission - -The Moon is a calibrated thermal source at microwave frequencies. Measuring its emission relative to -cold sky provides a reference point for system noise temperature estimation — a standard radio -astronomy calibration technique. - -## RF Test & Measurement - -The custom firmware turns the SkyWalker-1 into a basic but useful L-band test instrument. - -### L-Band Spectrum Analyzer - - - -Sweep 950-2150 MHz in configurable steps, recording AGC power at each frequency. Not calibrated -to absolute dBm, but relative measurements are consistent enough for transponder identification, -interference detection, and comparative analysis. - -See [Spectrum Analysis](/tools/spectrum-analysis/) for sweep techniques and interpretation. - -### CW Injection Test Bench - - - -Connect a NanoVNA as a CW source through an [HMC472A digital attenuator](https://hmc472.l.zmesh.systems/) -to the SkyWalker-1's F-connector. The `rf_testbench.py` tool automates five test sequences: -AGC linearity mapping, IF band flatness, frequency accuracy, minimum detectable signal, and -BPSK mode 9 probing. The HMC472A provides 0-31.5 dB of programmable attenuation in 0.5 dB -steps via its REST API, giving precision level control without swapping fixed pads. - -See [RF Test Bench](/tools/rf-testbench/) for hardware setup, calibration, and test descriptions. - -### LNB Characterization - -Measure gain flatness across the IF band by sweeping a known satellite's transponder plan and -comparing received power levels. Track LO drift over temperature by monitoring a stable carrier's -frequency offset over 24 hours with the [Beacon Logger](/tools/beacon-logger/). - -The I2C-exposed tuner and demodulator registers make internal signal chain parameters directly -readable — something most consumer receivers hide completely. - -### Transponder Fingerprinting - -Each satellite transponder has unique RF characteristics: center frequency, symbol rate, rolloff, -power level, modulation type. The [Carrier Survey](/tools/survey/) tool builds a catalog of these -parameters. Over time, this creates a fingerprint database useful for satellite identification -and change detection. - -### 5G Interference Monitoring - -The FCC's C-band auction reallocated 3.7-3.98 GHz to 5G operators. Spillover from 5G base stations -into the satellite C-band (3.98-4.2 GHz) is an increasing concern for satellite operators and -earth station licensees. With a C-band LNB, the SkyWalker-1 can sweep the IF band and detect -interference signatures. - -## Propagation Science & Weather - -Long-duration signal monitoring produces datasets that map directly to atmospheric physics. - -### Rain Fade Analysis - - - -Lock onto a stable Ku-band transponder and log SNR at 1 Hz for days or weeks. Ku-band signals -attenuate predictably in rain — the ITU-R P.618 model describes the relationship between rainfall -rate and attenuation at specific frequencies. Real measurement data validates (or challenges) -these models for your specific location and dish geometry. - -### Diurnal Thermal Effects - -LNB gain varies with temperature. A 24-hour beacon log correlated with ambient temperature data -reveals the thermal gain coefficient of your specific LNB — useful for separating real propagation -events from equipment drift. - -### Link Budget Validation - -Compare long-term average received signal levels against calculated link budgets (EIRP, free space -loss, atmospheric absorption, antenna gain, system noise temperature). The gap between prediction -and measurement is where engineering meets reality. - -See [Beacon Logger](/tools/beacon-logger/) for unattended multi-day logging with auto-relock. - -## Education & Research - -The SkyWalker-1 exposes the complete satellite signal chain from RF input to MPEG-2 transport stream -output, with every intermediate stage accessible over I2C. - -### University Lab Platform - -A single SkyWalker-1 + dish + LNB covers a semester of satellite communications topics with -live signals: - -| Topic | What's Observable | -|-------|------------------| -| QPSK/8PSK demodulation | Lock status, constellation quality via SNR | -| Forward error correction | Viterbi, Reed-Solomon, Turbo code — switchable by modulation type | -| Link budgets | Real measurements vs. theoretical calculations | -| MPEG-2 transport streams | Live PSI/SI table parsing, PID analysis | -| Spectrum analysis | Transponder identification from raw power sweeps | -| Antenna pointing | Signal strength vs. azimuth/elevation in real time | - -### Transport Stream Protocol Research - -The SkyWalker-1's multi-standard support makes it uniquely suited for comparative protocol analysis: - -- **DVB-S**: 188-byte MPEG-2 TS packets, standard PID structure -- **DigiCipher II**: Motorola proprietary transport, conditional access -- **DSS**: 127-byte packets — shorter than DVB, different header format - -Tools like [TSDuck](https://tsduck.io/) and dvbsnoop can parse captured streams. The [TS Analyzer](/tools/ts-analyzer/) -handles the initial capture and PSI extraction. - -### Accessible Signal Chain - -The I2C bus provides direct read access to tuner, demodulator, and FEC status registers. Students can -observe the AGC settling, watch the demodulator acquire lock, and read error correction statistics — -the internal workings of the signal chain, visible in real time. See [I2C Bus Architecture](/i2c/bus-architecture/) -and [Signal Monitoring](/bcm4500/signal-monitoring/) for register details. - -## What's NOT Compatible - -Setting honest expectations is more valuable than overselling. - - - -| Signal / Application | Why Not | -|---------------------|---------| -| **DVB-S2** | Incompatible FEC — uses LDPC instead of Reed-Solomon/Viterbi. This is a growing percentage of satellite content. | -| **GOES weather satellite imagery** | LRIT uses BPSK/CCSDS (not DVB-S), GRB uses DVB-S2. Cannot decode imagery. However, the BCM4500's BPSK mode 9 uses the same inner FEC (Viterbi rate 1/2 K=7) as LRIT — the signal chain gets four stages deep before breaking at RS decoder block size and CCSDS framing. The LRIT carrier at 1694.1 MHz is within the [direct input range](/tools/h21cm/#antenna-setup) and can be used for antenna alignment and propagation monitoring. See [RF Test Bench](/tools/rf-testbench/) for BPSK mode 9 probing. | -| **QO-100 from North America** | Es'hail-2 is at 25.9°E — visible from Europe, Africa, and the Middle East, but not North America. See [QO-100 DATV Reception](/guides/qo100-datv/) for coverage details. | -| **Military/government feeds** | Encrypted and increasingly DVB-S2 or proprietary modulation. | -| **ATSC / DVB-T terrestrial** | Completely different modulation family (OFDM), different frequency band. | -| **Analog satellite TV** | The BCM4500 is a digital demodulator. Analog satellite is also effectively extinct. | - -## Modulation Support Reference - - - - | Modulation | Standard | Typical Use | FTA Content? | - |-----------|----------|-------------|-------------| - | DVB-S QPSK | DVB-S EN 300 421 | Free-to-air satellite TV worldwide | Yes — most FTA content | - | Turbo QPSK | Proprietary (Comstream) | Distribution, some DISH | Rare | - | Turbo 8PSK | Proprietary | DISH Network | No — encrypted | - | DCII Combo | Motorola DigiCipher II | Cable headend distribution | Some ("Zero Key") | - | DCII Split I | Motorola DigiCipher II | Cable headend distribution | Some | - | DCII Split Q | Motorola DigiCipher II | Cable headend distribution | Some | - | DCII Offset QPSK | Motorola DigiCipher II | Cable headend distribution | Some | - | DSS QPSK | Hughes DSS | Legacy DirecTV | No — service winding down | - - - | What You Want to Do | Modulation to Select | Symbol Rate Range | - |--------------------|--------------------|------------------| - | Watch FTA satellite TV | DVB-S QPSK | 2-30 Msps | - | Analyze DISH Network signals | Turbo 8PSK | 20-30 Msps | - | Receive DCII cable distribution | DCII Combo/Split/Offset | 2-30 Msps | - | Study DSS transport format | DSS QPSK | 20 Msps typical | - | Hydrogen 21 cm (no LNB) | N/A — AGC power only | Any (for carrier lock attempt) | - | Spectrum sweep / signal detection | N/A — AGC power only | Set during tune, not critical | - - - -## See Also - -- [RF Specifications](/hardware/rf-specifications/) — frequency range, symbol rate limits, LNB power -- [BCM4500 Demodulator](/bcm4500/demodulator/) — register-level modulation configuration -- [Spectrum Analysis](/tools/spectrum-analysis/) — sweep techniques and transponder scanning -- [RF Test Bench](/tools/rf-testbench/) — CW injection testing with NanoVNA + HMC472A -- [Experimenter's Roadmap](/guides/experimenter-roadmap/) — future experiment tiers and creative applications -- [MCP Server](/tools/mcp-server/) — programmatic access to all hardware functions +--- +title: Applications & Use Cases +description: What can the SkyWalker-1 actually do? Satellite TV, multi-standard signal analysis, radio astronomy, RF measurement, and more. +--- + +import { Aside, Badge, Card, CardGrid, Tabs, TabItem } from '@astrojs/starlight/components'; + +The SkyWalker-1 shipped as a DVB-S satellite TV receiver. With [custom firmware](/firmware/custom-v305/) and +the reverse-engineered USB/I2C interface, it becomes something more interesting: a programmable RF instrument +covering 950-2150 MHz with ten demodulation modes, 256 Ksps to 30 Msps symbol rates, and full Python control. + +Here's what you can actually do with it. + +## Satellite TV Reception + +The obvious one. The SkyWalker-1 receives free-to-air (FTA) DVB-S content — unencrypted satellite television +and radio that anyone with a dish can watch. + +### What's Up There + + + + Most FTA content in North America lives on Ku-band satellites. A standard 30-36 inch dish and + universal LNB is all you need. + + | Satellite | Position | What's On It | + |-----------|----------|-------------| + | Galaxy 19 | 97.0°W | The FTA motherlode. ~135+ channels: Chinese, Korean, South Asian, religious, shopping, some English | + | Galaxy 16 | 99.0°W | Religious programming, international | + | SES-2 | 87.0°W | International, government | + | AMC-18 | 105.0°W | Mixed FTA and encrypted | + + Typical tuning parameters: 11836 MHz V-pol, 20770 ksps, DVB-S QPSK FEC 3/4. + + + C-band requires a larger dish (6-12 feet) and a C-band LNB, but carries content that never made it + to Ku-band — including DigiCipher II muxes that the SkyWalker-1 uniquely supports. + + | Satellite | Position | What's On It | + |-----------|----------|-------------| + | AMC-18 | 105.0°W | DCII cable distribution, some FTA | + | SES-2 | 87.0°W | International, government feeds | + | Galaxy 16 | 99.0°W | Mixed distribution | + + The FCC C-band transition compressed services into the upper 3.98-4.2 GHz range. Additional + spectrum auctions are proposed for 2027 — C-band FTA is on borrowed time. + + + +### FTA Resources + +Current channel listings change frequently. These sites track what's active: + +- [LyngSat](https://www.lyngsat.com/) — comprehensive transponder and channel database +- [SatExpat](https://www.satexpat.com/) — FTA channel listings with satellite footprints +- [FTAList](https://ftalist.com/) — North American FTA community and channel guide + + + +## Multi-Standard Signal Analysis + +This is where the SkyWalker-1 becomes genuinely rare hardware. The BCM4500 demodulates standards that are +nearly extinct in available consumer equipment — standards that are still actively broadcasting. + + + + Cable headend distribution format (Comcast HITS, Motorola). One of very few modern devices with DCII + support. "Zero Key" unencrypted services are directly receivable. + + + Digital Satellite Service — legacy DirecTV format with 127-byte transport packets (vs 188-byte DVB). + Extraordinarily rare outside DirecTV hardware. + + + DISH Network transponder format. Encrypted content, but demodulator lock and transport stream capture + work — useful for signal analysis and protocol research. + + + Earlier turbo-coded variant. Better spectral efficiency than standard DVB-S QPSK, still used on + some distribution paths. + + + +### Why This Matters + +These standards are still active on-air, but the hardware to receive them is disappearing. Off-the-shelf +satellite receivers dropped DCII and DSS support years ago. The SkyWalker-1, through its BCM4500 demodulator, +retains these capabilities — making it a **preservation and research tool** for signal formats that will +eventually go silent. + +The [TS Analyzer](/tools/ts-analyzer/) can parse transport streams from all supported modulation types, +making it possible to compare DVB-S, DCII, and DSS packet structures side by side. + +See [BCM4500 Demodulator](/bcm4500/demodulator/) for register-level details on how each modulation type +is configured. + +## Wild Feed & Backhaul Hunting + +Satellite transponders carry more than scheduled programming. Temporary unencrypted uplinks — "wild feeds" — +appear and disappear throughout the day: + +- **Live news remotes**: Raw camera feeds from field reporters, unedited and uncensored +- **Sports backhauls**: Stadium camera feeds before production mixing +- **Network distribution**: Programs fed to affiliates before air time +- **Event coverage**: Press conferences, hearings, launches + +The SkyWalker-1's blind scan capability and wide symbol rate range (256 Ksps - 30 Msps) make it well-suited +for finding these transient signals. The [Carrier Survey](/tools/survey/) tool automates the sweep-and-lock +cycle across a full satellite. + + + +## Radio Astronomy + +The 950-2150 MHz IF range — or, without an LNB, the direct input range — overlaps with several +astrophysically interesting frequencies. The BCM4500's AGC registers respond to any RF energy at the +tuned frequency, regardless of whether it carries a demodulatable signal. + +### Hydrogen 21 cm + + + +Neutral hydrogen emits at **1420.405 MHz** — directly in the IF range with no LNB. Connect an L-band +antenna (patch, helical, or horn) to the F-connector and the SkyWalker-1 becomes a hydrogen line +radiometer. The velocity-dispersed emission from the Milky Way's spiral arms is detectable even +with the BCM4500's ~346 kHz resolution bandwidth. + +See [Hydrogen 21 cm Radiometer](/tools/h21cm/) for the full tool reference. + +### Ku-Band Solar Observation + +Point a standard satellite dish + LNB at the Sun. At 10-12 GHz, solar thermal emission produces a +detectable **6+ dB rise** above the cold-sky background. Solar flares produce wideband bursts that +are even more dramatic. + +The SkyWalker-1's advantage over an RTL-SDR here is bandwidth: the 30 Msps sweep capability covers +a much wider swath of spectrum (~30 MHz effective) compared to the RTL-SDR's ~2.4 MHz, making it +easier to detect and characterize broadband solar events. + +Use the [Spectrum Analysis](/tools/spectrum-analysis/) sweep mode to build solar emission profiles. + +### Moon Thermal Emission + +The Moon is a calibrated thermal source at microwave frequencies. Measuring its emission relative to +cold sky provides a reference point for system noise temperature estimation — a standard radio +astronomy calibration technique. + +## RF Test & Measurement + +The custom firmware turns the SkyWalker-1 into a basic but useful L-band test instrument. + +### L-Band Spectrum Analyzer + + + +Sweep 950-2150 MHz in configurable steps, recording AGC power at each frequency. Not calibrated +to absolute dBm, but relative measurements are consistent enough for transponder identification, +interference detection, and comparative analysis. + +See [Spectrum Analysis](/tools/spectrum-analysis/) for sweep techniques and interpretation. + +### CW Injection Test Bench + + + +Connect a NanoVNA as a CW source through an [HMC472A digital attenuator](https://hmc472.l.zmesh.systems/) +to the SkyWalker-1's F-connector. The `rf_testbench.py` tool automates five test sequences: +AGC linearity mapping, IF band flatness, frequency accuracy, minimum detectable signal, and +BPSK mode 9 probing. The HMC472A provides 0-31.5 dB of programmable attenuation in 0.5 dB +steps via its REST API, giving precision level control without swapping fixed pads. + +See [RF Test Bench](/tools/rf-testbench/) for hardware setup, calibration, and test descriptions. + +### LNB Characterization + +Measure gain flatness across the IF band by sweeping a known satellite's transponder plan and +comparing received power levels. Track LO drift over temperature by monitoring a stable carrier's +frequency offset over 24 hours with the [Beacon Logger](/tools/beacon-logger/). + +The I2C-exposed tuner and demodulator registers make internal signal chain parameters directly +readable — something most consumer receivers hide completely. + +### Transponder Fingerprinting + +Each satellite transponder has unique RF characteristics: center frequency, symbol rate, rolloff, +power level, modulation type. The [Carrier Survey](/tools/survey/) tool builds a catalog of these +parameters. Over time, this creates a fingerprint database useful for satellite identification +and change detection. + +### 5G Interference Monitoring + +The FCC's C-band auction reallocated 3.7-3.98 GHz to 5G operators. Spillover from 5G base stations +into the satellite C-band (3.98-4.2 GHz) is an increasing concern for satellite operators and +earth station licensees. With a C-band LNB, the SkyWalker-1 can sweep the IF band and detect +interference signatures. + +## Propagation Science & Weather + +Long-duration signal monitoring produces datasets that map directly to atmospheric physics. + +### Rain Fade Analysis + + + +Lock onto a stable Ku-band transponder and log SNR at 1 Hz for days or weeks. Ku-band signals +attenuate predictably in rain — the ITU-R P.618 model describes the relationship between rainfall +rate and attenuation at specific frequencies. Real measurement data validates (or challenges) +these models for your specific location and dish geometry. + +### Diurnal Thermal Effects + +LNB gain varies with temperature. A 24-hour beacon log correlated with ambient temperature data +reveals the thermal gain coefficient of your specific LNB — useful for separating real propagation +events from equipment drift. + +### Link Budget Validation + +Compare long-term average received signal levels against calculated link budgets (EIRP, free space +loss, atmospheric absorption, antenna gain, system noise temperature). The gap between prediction +and measurement is where engineering meets reality. + +See [Beacon Logger](/tools/beacon-logger/) for unattended multi-day logging with auto-relock. + +## Education & Research + +The SkyWalker-1 exposes the complete satellite signal chain from RF input to MPEG-2 transport stream +output, with every intermediate stage accessible over I2C. + +### University Lab Platform + +A single SkyWalker-1 + dish + LNB covers a semester of satellite communications topics with +live signals: + +| Topic | What's Observable | +|-------|------------------| +| QPSK/8PSK demodulation | Lock status, constellation quality via SNR | +| Forward error correction | Viterbi, Reed-Solomon, Turbo code — switchable by modulation type | +| Link budgets | Real measurements vs. theoretical calculations | +| MPEG-2 transport streams | Live PSI/SI table parsing, PID analysis | +| Spectrum analysis | Transponder identification from raw power sweeps | +| Antenna pointing | Signal strength vs. azimuth/elevation in real time | + +### Transport Stream Protocol Research + +The SkyWalker-1's multi-standard support makes it uniquely suited for comparative protocol analysis: + +- **DVB-S**: 188-byte MPEG-2 TS packets, standard PID structure +- **DigiCipher II**: Motorola proprietary transport, conditional access +- **DSS**: 127-byte packets — shorter than DVB, different header format + +Tools like [TSDuck](https://tsduck.io/) and dvbsnoop can parse captured streams. The [TS Analyzer](/tools/ts-analyzer/) +handles the initial capture and PSI extraction. + +### Accessible Signal Chain + +The I2C bus provides direct read access to tuner, demodulator, and FEC status registers. Students can +observe the AGC settling, watch the demodulator acquire lock, and read error correction statistics — +the internal workings of the signal chain, visible in real time. See [I2C Bus Architecture](/i2c/bus-architecture/) +and [Signal Monitoring](/bcm4500/signal-monitoring/) for register details. + +## What's NOT Compatible + +Setting honest expectations is more valuable than overselling. + + + +| Signal / Application | Why Not | +|---------------------|---------| +| **DVB-S2** | Incompatible FEC — uses LDPC instead of Reed-Solomon/Viterbi. This is a growing percentage of satellite content. | +| **GOES weather satellite imagery** | LRIT uses BPSK/CCSDS (not DVB-S), GRB uses DVB-S2. Cannot decode imagery. However, the BCM4500's BPSK mode 9 uses the same inner FEC (Viterbi rate 1/2 K=7) as LRIT — the signal chain gets four stages deep before breaking at RS decoder block size and CCSDS framing. The LRIT carrier at 1694.1 MHz is within the [direct input range](/tools/h21cm/#antenna-setup) and can be used for antenna alignment and propagation monitoring. See [RF Test Bench](/tools/rf-testbench/) for BPSK mode 9 probing. | +| **QO-100 from North America** | Es'hail-2 is at 25.9°E — visible from Europe, Africa, and the Middle East, but not North America. See [QO-100 DATV Reception](/guides/qo100-datv/) for coverage details. | +| **Military/government feeds** | Encrypted and increasingly DVB-S2 or proprietary modulation. | +| **ATSC / DVB-T terrestrial** | Completely different modulation family (OFDM), different frequency band. | +| **Analog satellite TV** | The BCM4500 is a digital demodulator. Analog satellite is also effectively extinct. | + +## Modulation Support Reference + + + + | Modulation | Standard | Typical Use | FTA Content? | + |-----------|----------|-------------|-------------| + | DVB-S QPSK | DVB-S EN 300 421 | Free-to-air satellite TV worldwide | Yes — most FTA content | + | Turbo QPSK | Proprietary (Comstream) | Distribution, some DISH | Rare | + | Turbo 8PSK | Proprietary | DISH Network | No — encrypted | + | DCII Combo | Motorola DigiCipher II | Cable headend distribution | Some ("Zero Key") | + | DCII Split I | Motorola DigiCipher II | Cable headend distribution | Some | + | DCII Split Q | Motorola DigiCipher II | Cable headend distribution | Some | + | DCII Offset QPSK | Motorola DigiCipher II | Cable headend distribution | Some | + | DSS QPSK | Hughes DSS | Legacy DirecTV | No — service winding down | + + + | What You Want to Do | Modulation to Select | Symbol Rate Range | + |--------------------|--------------------|------------------| + | Watch FTA satellite TV | DVB-S QPSK | 2-30 Msps | + | Analyze DISH Network signals | Turbo 8PSK | 20-30 Msps | + | Receive DCII cable distribution | DCII Combo/Split/Offset | 2-30 Msps | + | Study DSS transport format | DSS QPSK | 20 Msps typical | + | Hydrogen 21 cm (no LNB) | N/A — AGC power only | Any (for carrier lock attempt) | + | Spectrum sweep / signal detection | N/A — AGC power only | Set during tune, not critical | + + + +## See Also + +- [RF Specifications](/hardware/rf-specifications/) — frequency range, symbol rate limits, LNB power +- [BCM4500 Demodulator](/bcm4500/demodulator/) — register-level modulation configuration +- [Spectrum Analysis](/tools/spectrum-analysis/) — sweep techniques and transponder scanning +- [RF Test Bench](/tools/rf-testbench/) — CW injection testing with NanoVNA + HMC472A +- [Experimenter's Roadmap](/guides/experimenter-roadmap/) — future experiment tiers and creative applications +- [MCP Server](/tools/mcp-server/) — programmatic access to all hardware functions diff --git a/site/src/content/docs/guides/experimenter-roadmap.mdx b/site/src/content/docs/guides/experimenter-roadmap.mdx index 357161e..2891694 100644 --- a/site/src/content/docs/guides/experimenter-roadmap.mdx +++ b/site/src/content/docs/guides/experimenter-roadmap.mdx @@ -1,218 +1,218 @@ ---- -title: Experimenter's Roadmap -description: What else can the SkyWalker-1 hardware do? A field guide to creative RF experiments beyond satellite TV reception. ---- - -import { Aside, Badge, Card, CardGrid, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; - -The SkyWalker-1 reverse engineering project has produced a fully documented, custom-firmware-driven, -Python-controllable RF instrument. With v3.05.0 deployed and all 55 Hamilton safety tests passing, -the question becomes: **what can experimenters actually do with this platform beyond satellite TV reception?** - -The approach: start from the hardware capabilities, ask "what physical processes produce or interact -with signals these capabilities can measure," and reason outward to creative applications. - -## Hardware as a Platform - -The SkyWalker-1 is simultaneously: - -- A **power meter** (16-bit AGC, ~30-40 dB dynamic range) -- A **spectrum analyzer** (~346 kHz RBW, 950-2150 MHz) -- A **satellite receiver** (10 modulation types, 256 ksps - 30 Msps) -- A **time-series data logger** (signal_monitor at ~50 Hz) -- A **dish positioning system** (DiSEqC 1.2 motor, USALS GotoX) - -All controllable from Python. - - - -## What's Directly Receivable (No LNB) - -The 950-2150 MHz IF range contains far more than satellite TV when you connect an antenna directly: - -| Frequency | What's There | Detectable? | -|---|---|---| -| **1420.405 MHz** | Hydrogen 21 cm line — galactic emission | Yes (AGC power) | -| 1575.42 MHz | GPS L1 | Yes (energy) | -| 1176.45 MHz | GPS L5 / Galileo E5a | Yes (energy) | -| 1227.6 MHz | GPS L2 | Yes (energy) | -| 1602 MHz | GLONASS L1 | Yes (energy) | -| 1525-1559 MHz | Inmarsat downlink | Yes (energy) | -| 1616-1626 MHz | Iridium downlink | Yes (burst energy) | -| 1670-1710 MHz | GOES LRIT, NOAA HRPT | Yes (carrier) | -| 1240-1300 MHz | Amateur 23 cm band | Yes (energy) | - -## Phase 1 Tools (Available Now) - -### Hydrogen 21 cm Drift-Scan Radiometer - - - -Neutral hydrogen emits at 1420.405 MHz — directly in the IF range. The Milky Way's spiral arms -create a velocity-dispersed emission profile detectable even with the BCM4500's resolution bandwidth. - - -1. Connect an L-band antenna (patch, helical, or horn) directly to the F-connector -2. Run `python h21cm.py` for a single sweep centered on 1420.405 MHz -3. Look for elevated power across the 1419-1421 MHz range (the hydrogen emission) -4. Use `--drift --duration 3600` for a one-hour drift scan as Earth rotates -5. Use `--averages 8` for 8x averaging to pull the signal from the noise - - -```bash -# Single sweep with 8x averaging -python tools/h21cm.py --averages 8 - -# One-hour drift scan with CSV output -python tools/h21cm.py --drift --duration 3600 --averages 4 --output h21cm-data.csv - -# Include control band comparison -python tools/h21cm.py --averages 8 --control -``` - -The velocity of the detected hydrogen is calculated from Doppler shift: -**v = c × (f_rest − f_observed) / f_rest**. This maps directly to the rotation curve -of the Milky Way. - -### Beacon Logger - - - -Lock onto a stable Ku-band broadcast transponder and log SNR/AGC at configurable intervals -for hours, days, or weeks. Produces propagation datasets useful for: - -- **Rain fade analysis** — correlate attenuation with rainfall rate -- **Diurnal thermal drift** — track LNB gain vs. temperature over 24 hours -- **Antenna mount stability** — detect dish drift from wind/thermal expansion -- **ITU propagation model validation** — contribute real measurements - -```bash -# Log a Ku-band beacon for 24 hours, 1 sample/sec, report every minute -python tools/beacon_logger.py --freq 1265000 --sr 20000000 \ - --output beacon-24h.csv --json-output beacon-stats.jsonl \ - --duration 86400 - -# Generate a systemd unit file for unattended operation -python tools/beacon_logger.py --generate-systemd \ - --freq 1265000 --sr 20000000 --output /var/log/beacon.csv -``` - -The logger automatically re-locks on signal loss and computes per-interval statistics -(min/max/mean/stddev of SNR). - -### Multi-Satellite Arc Survey - - - -Automated "satellite census": points the dish motor to each GEO orbital longitude, -runs a full-band six-stage survey at each position, and aggregates results into a comprehensive sky map. - -```bash -# Survey specific North American slots -python tools/arc_survey.py --observer-lon -96.8 --slots "97W,99W,101W,103W" - -# Survey an arc at 3-degree intervals -python tools/arc_survey.py --observer-lon -96.8 --arc -120 -60 --step 3 - -# List common NA orbital slots -python tools/arc_survey.py --list-slots - -# Resume an interrupted survey -python tools/arc_survey.py --resume ~/.skywalker1/arc-surveys/arc-survey-2026-02-17.json -``` - - - -### MCP Server - - - -The `skywalker-mcp` FastMCP server wraps the entire hardware API as MCP tools, making -every function accessible to LLMs. This is the foundation for autonomous RF exploration. - -```bash -# Install and run (from project directory) -cd mcp/skywalker-mcp && uv run skywalker-mcp - -# Add to Claude Code -claude mcp add skywalker-mcp -- uv run --directory mcp/skywalker-mcp skywalker-mcp - -# Test with headless mode -claude -p "What firmware version is loaded?" \ - --mcp-config .mcp.json --allowedTools "mcp__skywalker-mcp__*" -``` - -**20 MCP tools** covering: -- Device status and signal quality -- Spectrum sweep with peak detection -- Frequency tuning across 10 modulation types -- Blind scan for unknown carriers -- Six-stage carrier survey with catalog persistence -- Dish motor control (jog, goto, USALS) -- LNB configuration -- I2C bus scanning -- Transport stream capture and PSI parsing -- Frequency identification against allocation tables - -**MCP Resources:** -- `skywalker://status` — live device state -- `skywalker://catalog/latest` — most recent survey -- `skywalker://allocations/lband` — frequency allocation table -- `skywalker://modulations` — supported modulation types - -**MCP Prompts:** -- `explore_rf_environment` — autonomous RF exploration strategy -- `hydrogen_line_observation` — guided hydrogen 21 cm procedure - -## Future Tiers - -### Tier B: Creative Combinations (no firmware changes) - -| Idea | Description | -|---|---| -| **Gradient-descent dish auto-peaking** | Closed-loop: tune to beacon, read AGC, step motor, converge | -| **Rain fade monitor** | Dual-frequency SNR logging + weather API correlation | -| **NIT-driven survey** | Parse NIT from one carrier → skip to ALL transponders | -| **Spectral anomaly detection** | Build baseline, flag N-sigma deviations, catch interference | -| **PCR timing analysis** | Extract clock jitter from transport stream timestamps | - -### Tier C: Firmware Enhancements (v4.x) - -| Feature | Impact | Size | -|---|---|---| -| Continuous AGC streaming via EP2 | Real-time waterfall displays | ~100 bytes | -| Firmware-side moving average | 12 dB noise floor improvement (16x avg) | ~50 bytes | -| GPIO event counter via Timer1 | General-purpose frequency counter | ~80 bytes | -| Multi-byte I2C transactions | External sensor integration | ~120 bytes | -| Fast power-only read (no retune) | 2x measurement rate | ~30 bytes | - -### Tier D: External Hardware - -- **I2C environmental sensors** (BME280/SHT40) for LNB drift vs. temperature -- **External TCXO/OCXO reference** for calibrated LNB frequency (±50 Hz vs ±5 MHz) -- **Noise source + Y-factor calibration** for absolute power measurements -- **GPIO antenna switch matrix** for automated antenna comparison - -## Who Gets What - - - -Iridium burst detector, arc survey, anomaly detection, MCP autonomous explorer - - -Hydrogen line, dish auto-peaking, QO-100 monitoring, arc survey - - -Hydrogen line, GNSS monitoring, arc survey, beacon logger - - -Beacon logger, rain fade monitor, polarization analysis, PCR timing - - +--- +title: Experimenter's Roadmap +description: What else can the SkyWalker-1 hardware do? A field guide to creative RF experiments beyond satellite TV reception. +--- + +import { Aside, Badge, Card, CardGrid, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; + +The SkyWalker-1 reverse engineering project has produced a fully documented, custom-firmware-driven, +Python-controllable RF instrument. With v3.05.0 deployed and all 55 Hamilton safety tests passing, +the question becomes: **what can experimenters actually do with this platform beyond satellite TV reception?** + +The approach: start from the hardware capabilities, ask "what physical processes produce or interact +with signals these capabilities can measure," and reason outward to creative applications. + +## Hardware as a Platform + +The SkyWalker-1 is simultaneously: + +- A **power meter** (16-bit AGC, ~30-40 dB dynamic range) +- A **spectrum analyzer** (~346 kHz RBW, 950-2150 MHz) +- A **satellite receiver** (10 modulation types, 256 ksps - 30 Msps) +- A **time-series data logger** (signal_monitor at ~50 Hz) +- A **dish positioning system** (DiSEqC 1.2 motor, USALS GotoX) + +All controllable from Python. + + + +## What's Directly Receivable (No LNB) + +The 950-2150 MHz IF range contains far more than satellite TV when you connect an antenna directly: + +| Frequency | What's There | Detectable? | +|---|---|---| +| **1420.405 MHz** | Hydrogen 21 cm line — galactic emission | Yes (AGC power) | +| 1575.42 MHz | GPS L1 | Yes (energy) | +| 1176.45 MHz | GPS L5 / Galileo E5a | Yes (energy) | +| 1227.6 MHz | GPS L2 | Yes (energy) | +| 1602 MHz | GLONASS L1 | Yes (energy) | +| 1525-1559 MHz | Inmarsat downlink | Yes (energy) | +| 1616-1626 MHz | Iridium downlink | Yes (burst energy) | +| 1670-1710 MHz | GOES LRIT, NOAA HRPT | Yes (carrier) | +| 1240-1300 MHz | Amateur 23 cm band | Yes (energy) | + +## Phase 1 Tools (Available Now) + +### Hydrogen 21 cm Drift-Scan Radiometer + + + +Neutral hydrogen emits at 1420.405 MHz — directly in the IF range. The Milky Way's spiral arms +create a velocity-dispersed emission profile detectable even with the BCM4500's resolution bandwidth. + + +1. Connect an L-band antenna (patch, helical, or horn) directly to the F-connector +2. Run `python h21cm.py` for a single sweep centered on 1420.405 MHz +3. Look for elevated power across the 1419-1421 MHz range (the hydrogen emission) +4. Use `--drift --duration 3600` for a one-hour drift scan as Earth rotates +5. Use `--averages 8` for 8x averaging to pull the signal from the noise + + +```bash +# Single sweep with 8x averaging +python tools/h21cm.py --averages 8 + +# One-hour drift scan with CSV output +python tools/h21cm.py --drift --duration 3600 --averages 4 --output h21cm-data.csv + +# Include control band comparison +python tools/h21cm.py --averages 8 --control +``` + +The velocity of the detected hydrogen is calculated from Doppler shift: +**v = c × (f_rest − f_observed) / f_rest**. This maps directly to the rotation curve +of the Milky Way. + +### Beacon Logger + + + +Lock onto a stable Ku-band broadcast transponder and log SNR/AGC at configurable intervals +for hours, days, or weeks. Produces propagation datasets useful for: + +- **Rain fade analysis** — correlate attenuation with rainfall rate +- **Diurnal thermal drift** — track LNB gain vs. temperature over 24 hours +- **Antenna mount stability** — detect dish drift from wind/thermal expansion +- **ITU propagation model validation** — contribute real measurements + +```bash +# Log a Ku-band beacon for 24 hours, 1 sample/sec, report every minute +python tools/beacon_logger.py --freq 1265000 --sr 20000000 \ + --output beacon-24h.csv --json-output beacon-stats.jsonl \ + --duration 86400 + +# Generate a systemd unit file for unattended operation +python tools/beacon_logger.py --generate-systemd \ + --freq 1265000 --sr 20000000 --output /var/log/beacon.csv +``` + +The logger automatically re-locks on signal loss and computes per-interval statistics +(min/max/mean/stddev of SNR). + +### Multi-Satellite Arc Survey + + + +Automated "satellite census": points the dish motor to each GEO orbital longitude, +runs a full-band six-stage survey at each position, and aggregates results into a comprehensive sky map. + +```bash +# Survey specific North American slots +python tools/arc_survey.py --observer-lon -96.8 --slots "97W,99W,101W,103W" + +# Survey an arc at 3-degree intervals +python tools/arc_survey.py --observer-lon -96.8 --arc -120 -60 --step 3 + +# List common NA orbital slots +python tools/arc_survey.py --list-slots + +# Resume an interrupted survey +python tools/arc_survey.py --resume ~/.skywalker1/arc-surveys/arc-survey-2026-02-17.json +``` + + + +### MCP Server + + + +The `skywalker-mcp` FastMCP server wraps the entire hardware API as MCP tools, making +every function accessible to LLMs. This is the foundation for autonomous RF exploration. + +```bash +# Install and run (from project directory) +cd mcp/skywalker-mcp && uv run skywalker-mcp + +# Add to Claude Code +claude mcp add skywalker-mcp -- uv run --directory mcp/skywalker-mcp skywalker-mcp + +# Test with headless mode +claude -p "What firmware version is loaded?" \ + --mcp-config .mcp.json --allowedTools "mcp__skywalker-mcp__*" +``` + +**20 MCP tools** covering: +- Device status and signal quality +- Spectrum sweep with peak detection +- Frequency tuning across 10 modulation types +- Blind scan for unknown carriers +- Six-stage carrier survey with catalog persistence +- Dish motor control (jog, goto, USALS) +- LNB configuration +- I2C bus scanning +- Transport stream capture and PSI parsing +- Frequency identification against allocation tables + +**MCP Resources:** +- `skywalker://status` — live device state +- `skywalker://catalog/latest` — most recent survey +- `skywalker://allocations/lband` — frequency allocation table +- `skywalker://modulations` — supported modulation types + +**MCP Prompts:** +- `explore_rf_environment` — autonomous RF exploration strategy +- `hydrogen_line_observation` — guided hydrogen 21 cm procedure + +## Future Tiers + +### Tier B: Creative Combinations (no firmware changes) + +| Idea | Description | +|---|---| +| **Gradient-descent dish auto-peaking** | Closed-loop: tune to beacon, read AGC, step motor, converge | +| **Rain fade monitor** | Dual-frequency SNR logging + weather API correlation | +| **NIT-driven survey** | Parse NIT from one carrier → skip to ALL transponders | +| **Spectral anomaly detection** | Build baseline, flag N-sigma deviations, catch interference | +| **PCR timing analysis** | Extract clock jitter from transport stream timestamps | + +### Tier C: Firmware Enhancements (v4.x) + +| Feature | Impact | Size | +|---|---|---| +| Continuous AGC streaming via EP2 | Real-time waterfall displays | ~100 bytes | +| Firmware-side moving average | 12 dB noise floor improvement (16x avg) | ~50 bytes | +| GPIO event counter via Timer1 | General-purpose frequency counter | ~80 bytes | +| Multi-byte I2C transactions | External sensor integration | ~120 bytes | +| Fast power-only read (no retune) | 2x measurement rate | ~30 bytes | + +### Tier D: External Hardware + +- **I2C environmental sensors** (BME280/SHT40) for LNB drift vs. temperature +- **External TCXO/OCXO reference** for calibrated LNB frequency (±50 Hz vs ±5 MHz) +- **Noise source + Y-factor calibration** for absolute power measurements +- **GPIO antenna switch matrix** for automated antenna comparison + +## Who Gets What + + + +Iridium burst detector, arc survey, anomaly detection, MCP autonomous explorer + + +Hydrogen line, dish auto-peaking, QO-100 monitoring, arc survey + + +Hydrogen line, GNSS monitoring, arc survey, beacon logger + + +Beacon logger, rain fade monitor, polarization analysis, PCR timing + + diff --git a/site/src/content/docs/guides/qo100-datv.mdx b/site/src/content/docs/guides/qo100-datv.mdx index 634c1c9..12f5721 100644 --- a/site/src/content/docs/guides/qo100-datv.mdx +++ b/site/src/content/docs/guides/qo100-datv.mdx @@ -1,175 +1,175 @@ ---- -title: QO-100 DATV Reception -description: Receiving amateur digital television from the Es'hail-2 geostationary satellite using the SkyWalker-1 DVB-S receiver. ---- - -import { Tabs, TabItem, Steps, Aside, Badge, CardGrid, Card } from '@astrojs/starlight/components'; - -The Es'hail-2 satellite (25.9 degrees East) carries the first geostationary amateur radio transponders, designated QO-100. The wideband transponder at 10491-10499 MHz carries DATV (Digital Amateur Television) signals that the SkyWalker-1 can receive — with some important caveats about symbol rate limitations and LNB selection. - - - -## Overview - - - - 10491.000 - 10499.500 MHz (8.5 MHz bandwidth). Carries DVB-S DATV stations from amateur operators worldwide. - - - 10489.750 MHz CW/PSK beacon. Useful for dish pointing and LNB drift calibration. - - - 25.9 degrees East — visible from Europe, Africa, western Asia, and eastern Americas. - - - 60 cm in central Europe, 1.2m+ at the edges of the footprint. - - - -## LNB Selection - -The SkyWalker-1 accepts IF frequencies in the 950-2150 MHz range. The LNB's local oscillator frequency determines where the QO-100 signals appear in this window. - -| LNB Type | LO Frequency | QO-100 IF Range | Compatible? | -|----------|-------------|-----------------|-------------| -| Universal (low band) | 9750 MHz | 741-749 MHz | No — below 950 MHz minimum | -| Universal (high band) | 10600 MHz | -109 to -101 MHz | No — negative IF | -| Modified universal | 9361 MHz | 1130-1138 MHz | Yes | -| TCXO/PLL stabilized | 9750 MHz | 741-749 MHz | No — same issue | -| Custom downconverter | varies | varies | Check IF range | - - - -### Modified LNB Option - -The most common approach among QO-100 operators is modifying a universal LNB by replacing the crystal oscillator (typically 25 MHz) with one that produces a different LO frequency. A 24.000 MHz crystal in a universal LNB that normally multiplies by 390 (9750 MHz) will produce 9360 MHz instead, placing QO-100 at 1131-1139 MHz IF. - -Popular modified LO frequencies and their resulting IF ranges: - -| Crystal | Multiplier | LO (MHz) | QO-100 IF (MHz) | -|---------|-----------|----------|-----------------| -| 24.000 MHz | x390 | 9360 | 1131-1139 | -| 24.385 MHz | x384 | 9363 | 1128-1136 | -| 25.000 MHz | x374 | 9350 | 1141-1149 | -| 27.000 MHz | x348 | 9396 | 1095-1103 | - -## Quick Start - - -1. **Verify LNB compatibility** — check that your LO frequency places QO-100 in the 950-2150 MHz range: - ```bash - python3 tools/qo100.py calc --lnb-lo 9361 - ``` - -2. **Point the dish** — aim at 25.9 degrees East. Use the motor control tool or manual positioning: - ```bash - sudo python3 tools/motor.py gotox --sat 25.9 --lon -97.5 - ``` - -3. **Scan for carriers** — sweep the QO-100 IF range: - ```bash - sudo python3 tools/qo100.py scan --lnb-lo 9361 - ``` - -4. **Tune to a carrier** — lock onto a detected DATV station: - ```bash - sudo python3 tools/qo100.py tune --freq 1135 --sr 1000 --lnb-lo 9361 - ``` - -5. **Watch live video** — pipe the transport stream to a media player: - ```bash - sudo python3 tools/qo100.py watch --freq 1135 --sr 1000 --lnb-lo 9361 - ``` - - -## CLI Reference - -### Calculate IF Frequencies - -```bash -python3 tools/qo100.py calc --lnb-lo 9361 -``` - -Displays the QO-100 wideband transponder IF range, known DATV frequencies with their IF equivalents, and the beacon frequency. No hardware required — pure calculation. - -### Band Plan - -```bash -python3 tools/qo100.py band-plan --lnb-lo 9361 -``` - -Shows the complete QO-100 wideband transponder band plan with known station frequencies converted to your LNB's IF range. Includes both the wideband DATV segment and the engineering beacon. - -### Scan - -```bash -sudo python3 tools/qo100.py scan --lnb-lo 9361 -``` - -Sweeps the QO-100 IF range with parameters tuned for narrowband DATV: -- 250 kHz frequency steps (vs 5 MHz for broadcast) -- 1000 ksps measurement symbol rate -- Extended dwell time for weak signal detection - -### Tune - -```bash -sudo python3 tools/qo100.py tune --freq 1135 --sr 1000 --lnb-lo 9361 -``` - -Tunes the BCM4500 to the specified IF frequency and symbol rate, then monitors lock status and signal quality. The `--freq` parameter is the IF frequency (after LNB downconversion), not the RF frequency. - -### Watch - -```bash -sudo python3 tools/qo100.py watch --freq 1135 --sr 1000 --lnb-lo 9361 -``` - -Tunes, locks, and pipes the raw MPEG-2 transport stream to `ffplay` (or `mpv` if ffplay is not found). The video player receives the stream on stdin and renders it in real time. - -| Flag | Default | Description | -|------|---------|-------------| -| `--freq` | — | IF frequency in MHz (required) | -| `--sr` | 1000 | Symbol rate in ksps | -| `--lnb-lo` | — | LNB local oscillator in MHz (required) | -| `--player` | auto | Video player command (`ffplay` or `mpv`) | - -## Hardware Limitations - -### Minimum Symbol Rate - -The BCM4500 demodulator has a minimum symbol rate of **256 ksps** (256,000 symbols per second). Many QO-100 DATV stations transmit at lower rates: - -| Typical QO-100 SR | BCM4500 Support | -|-------------------|----------------| -| 125 ksps | Detectable as energy, cannot lock | -| 250 ksps | Marginal — may lock with strong signal | -| 333 ksps | Supported | -| 500 ksps | Supported | -| 1000 ksps | Supported | -| 2000 ksps | Supported | - -The survey tool distinguishes "carrier detected" (energy above noise floor) from "carrier locked" (BCM4500 achieved demodulator lock) in its output. - -### Lock Acquisition Time - -QO-100 signals are weaker than broadcast satellites. The BCM4500 may need 10-30 seconds to achieve lock at low symbol rates, compared to 1-2 seconds for typical broadcast transponders. The `qo100.py tune` command uses extended timeouts automatically. - -### Frequency Stability - -QO-100 DATV signals have tighter frequency tolerance requirements than the SkyWalker-1's default tuning resolution. A PLL-stabilized or TCXO LNB provides better frequency accuracy than a standard DRO LNB, reducing the chance of the demodulator losing lock due to LNB drift. - -## Using the TUI - -The SkyWalker TUI includes QO-100 as a tab within the Survey screen (F10). The QO-100 tab pre-fills the sweep parameters with values optimized for the wideband transponder and includes an info panel showing known station frequencies adjusted for your LNB's local oscillator. - -See [SkyWalker TUI — Survey Screen](/tools/tui/#survey--qo-100) for details. - -## See Also - -- [Motor Control](/tools/motor/) — DiSEqC 1.2 positioner for dish pointing -- [Carrier Survey](/tools/survey/) — automated carrier detection and cataloging -- [LNB Control](/lnb-diseqc/lnb-control/) — LNB power and voltage configuration -- [RF Specifications](/hardware/rf-specifications/) — SkyWalker-1 frequency range and sensitivity +--- +title: QO-100 DATV Reception +description: Receiving amateur digital television from the Es'hail-2 geostationary satellite using the SkyWalker-1 DVB-S receiver. +--- + +import { Tabs, TabItem, Steps, Aside, Badge, CardGrid, Card } from '@astrojs/starlight/components'; + +The Es'hail-2 satellite (25.9 degrees East) carries the first geostationary amateur radio transponders, designated QO-100. The wideband transponder at 10491-10499 MHz carries DATV (Digital Amateur Television) signals that the SkyWalker-1 can receive — with some important caveats about symbol rate limitations and LNB selection. + + + +## Overview + + + + 10491.000 - 10499.500 MHz (8.5 MHz bandwidth). Carries DVB-S DATV stations from amateur operators worldwide. + + + 10489.750 MHz CW/PSK beacon. Useful for dish pointing and LNB drift calibration. + + + 25.9 degrees East — visible from Europe, Africa, western Asia, and eastern Americas. + + + 60 cm in central Europe, 1.2m+ at the edges of the footprint. + + + +## LNB Selection + +The SkyWalker-1 accepts IF frequencies in the 950-2150 MHz range. The LNB's local oscillator frequency determines where the QO-100 signals appear in this window. + +| LNB Type | LO Frequency | QO-100 IF Range | Compatible? | +|----------|-------------|-----------------|-------------| +| Universal (low band) | 9750 MHz | 741-749 MHz | No — below 950 MHz minimum | +| Universal (high band) | 10600 MHz | -109 to -101 MHz | No — negative IF | +| Modified universal | 9361 MHz | 1130-1138 MHz | Yes | +| TCXO/PLL stabilized | 9750 MHz | 741-749 MHz | No — same issue | +| Custom downconverter | varies | varies | Check IF range | + + + +### Modified LNB Option + +The most common approach among QO-100 operators is modifying a universal LNB by replacing the crystal oscillator (typically 25 MHz) with one that produces a different LO frequency. A 24.000 MHz crystal in a universal LNB that normally multiplies by 390 (9750 MHz) will produce 9360 MHz instead, placing QO-100 at 1131-1139 MHz IF. + +Popular modified LO frequencies and their resulting IF ranges: + +| Crystal | Multiplier | LO (MHz) | QO-100 IF (MHz) | +|---------|-----------|----------|-----------------| +| 24.000 MHz | x390 | 9360 | 1131-1139 | +| 24.385 MHz | x384 | 9363 | 1128-1136 | +| 25.000 MHz | x374 | 9350 | 1141-1149 | +| 27.000 MHz | x348 | 9396 | 1095-1103 | + +## Quick Start + + +1. **Verify LNB compatibility** — check that your LO frequency places QO-100 in the 950-2150 MHz range: + ```bash + python3 tools/qo100.py calc --lnb-lo 9361 + ``` + +2. **Point the dish** — aim at 25.9 degrees East. Use the motor control tool or manual positioning: + ```bash + sudo python3 tools/motor.py gotox --sat 25.9 --lon -97.5 + ``` + +3. **Scan for carriers** — sweep the QO-100 IF range: + ```bash + sudo python3 tools/qo100.py scan --lnb-lo 9361 + ``` + +4. **Tune to a carrier** — lock onto a detected DATV station: + ```bash + sudo python3 tools/qo100.py tune --freq 1135 --sr 1000 --lnb-lo 9361 + ``` + +5. **Watch live video** — pipe the transport stream to a media player: + ```bash + sudo python3 tools/qo100.py watch --freq 1135 --sr 1000 --lnb-lo 9361 + ``` + + +## CLI Reference + +### Calculate IF Frequencies + +```bash +python3 tools/qo100.py calc --lnb-lo 9361 +``` + +Displays the QO-100 wideband transponder IF range, known DATV frequencies with their IF equivalents, and the beacon frequency. No hardware required — pure calculation. + +### Band Plan + +```bash +python3 tools/qo100.py band-plan --lnb-lo 9361 +``` + +Shows the complete QO-100 wideband transponder band plan with known station frequencies converted to your LNB's IF range. Includes both the wideband DATV segment and the engineering beacon. + +### Scan + +```bash +sudo python3 tools/qo100.py scan --lnb-lo 9361 +``` + +Sweeps the QO-100 IF range with parameters tuned for narrowband DATV: +- 250 kHz frequency steps (vs 5 MHz for broadcast) +- 1000 ksps measurement symbol rate +- Extended dwell time for weak signal detection + +### Tune + +```bash +sudo python3 tools/qo100.py tune --freq 1135 --sr 1000 --lnb-lo 9361 +``` + +Tunes the BCM4500 to the specified IF frequency and symbol rate, then monitors lock status and signal quality. The `--freq` parameter is the IF frequency (after LNB downconversion), not the RF frequency. + +### Watch + +```bash +sudo python3 tools/qo100.py watch --freq 1135 --sr 1000 --lnb-lo 9361 +``` + +Tunes, locks, and pipes the raw MPEG-2 transport stream to `ffplay` (or `mpv` if ffplay is not found). The video player receives the stream on stdin and renders it in real time. + +| Flag | Default | Description | +|------|---------|-------------| +| `--freq` | — | IF frequency in MHz (required) | +| `--sr` | 1000 | Symbol rate in ksps | +| `--lnb-lo` | — | LNB local oscillator in MHz (required) | +| `--player` | auto | Video player command (`ffplay` or `mpv`) | + +## Hardware Limitations + +### Minimum Symbol Rate + +The BCM4500 demodulator has a minimum symbol rate of **256 ksps** (256,000 symbols per second). Many QO-100 DATV stations transmit at lower rates: + +| Typical QO-100 SR | BCM4500 Support | +|-------------------|----------------| +| 125 ksps | Detectable as energy, cannot lock | +| 250 ksps | Marginal — may lock with strong signal | +| 333 ksps | Supported | +| 500 ksps | Supported | +| 1000 ksps | Supported | +| 2000 ksps | Supported | + +The survey tool distinguishes "carrier detected" (energy above noise floor) from "carrier locked" (BCM4500 achieved demodulator lock) in its output. + +### Lock Acquisition Time + +QO-100 signals are weaker than broadcast satellites. The BCM4500 may need 10-30 seconds to achieve lock at low symbol rates, compared to 1-2 seconds for typical broadcast transponders. The `qo100.py tune` command uses extended timeouts automatically. + +### Frequency Stability + +QO-100 DATV signals have tighter frequency tolerance requirements than the SkyWalker-1's default tuning resolution. A PLL-stabilized or TCXO LNB provides better frequency accuracy than a standard DRO LNB, reducing the chance of the demodulator losing lock due to LNB drift. + +## Using the TUI + +The SkyWalker TUI includes QO-100 as a tab within the Survey screen (F10). The QO-100 tab pre-fills the sweep parameters with values optimized for the wideband transponder and includes an info panel showing known station frequencies adjusted for your LNB's local oscillator. + +See [SkyWalker TUI — Survey Screen](/tools/tui/#survey--qo-100) for details. + +## See Also + +- [Motor Control](/tools/motor/) — DiSEqC 1.2 positioner for dish pointing +- [Carrier Survey](/tools/survey/) — automated carrier detection and cataloging +- [LNB Control](/lnb-diseqc/lnb-control/) — LNB power and voltage configuration +- [RF Specifications](/hardware/rf-specifications/) — SkyWalker-1 frequency range and sensitivity diff --git a/site/src/content/docs/hardware/gpio-pinmap.mdx b/site/src/content/docs/hardware/gpio-pinmap.mdx index 507e929..7ceea2e 100644 --- a/site/src/content/docs/hardware/gpio-pinmap.mdx +++ b/site/src/content/docs/hardware/gpio-pinmap.mdx @@ -1,131 +1,131 @@ ---- -title: GPIO Pin Map -description: Complete FX2 GPIO pin assignments across all firmware versions including stock and custom firmware. ---- - -import { Tabs, TabItem, Badge, Aside } from '@astrojs/starlight/components'; - -The Cypress FX2 uses three GPIO ports for hardware control on the SkyWalker-1: Port 0 (IOA), Port 3 (IOD), and Port B (XRAM-mapped IOB). Pin assignments differ significantly between firmware versions due to PCB revisions and design changes. - - - -## Port 0 / Port A (SFR 0x80, IOA) - - - - -| Pin | v2.06 | Rev.2 v2.10 | v2.13 | Notes | -|-----|-------|-------------|-------|-------| -| P0.0 | -- | LNB control (0x97) | **DiSEqC data** | DiSEqC data pin moved across versions | -| P0.1 | Power enable | Power enable | Power enable | BCM4500 power supply enable | -| P0.2 | Power disable | Power disable (init=0x84) | Power disable | BCM4500 power supply disable | -| P0.3 | **22 kHz tone** | **22 kHz tone** | **22 kHz tone** | Gates external 22 kHz oscillator (all versions) | -| P0.4 | **LNB 13V/18V** | **LNB 13V/18V** + DiSEqC data | **LNB 13V/18V** | Also SET_DN_SWITCH bit-bang (all versions) | -| P0.5 | **BCM4500 RESET** | GPIO status input (0x98) | **BCM4500 RESET** | Reset and feedback pin | -| P0.6 | -- | GPIO control (0x97) | -- | LNB control on Rev.2 only | -| P0.7 | **DiSEqC data** | Streaming indicator | Streaming indicator | DiSEqC data on v2.06 only | - - - - -| Pin | Custom v3.01.0 | Notes | -|-----|----------------|-------| -| P0.0 | -- | Unused | -| P0.1 | **Power enable** | BCM4500 power supply enable (HIGH = on) | -| P0.2 | **Power disable** | BCM4500 power supply disable (LOW = off) | -| P0.3 | **22 kHz tone** | Gates external 22 kHz oscillator | -| P0.4 | **LNB 13V/18V** | Voltage select: LOW = 13V, HIGH = 18V | -| P0.5 | **BCM4500 RESET** | LOW = reset asserted, HIGH = released | -| P0.6 | -- | Unused | -| P0.7 | **DiSEqC data** + streaming | DiSEqC data (matches v2.06); also streaming indicator | - - - - -## Port 3 / Port D (SFR 0xB0, IOD) - -These pins are consistent across all firmware versions: - -| Pin | Function | Active State | Notes | -|-----|----------|-------------|-------| -| P3.0 | Init HIGH | -- | Set during initialization | -| P3.4 | GPIO control | -- | Used by Rev.2 `FUN_CODE_1fcf` | -| P3.5 | **TS_EN** | LOW | Transport stream enable: LOW = active, HIGH = idle | -| P3.6 | **DVB mode** | -- | BCM4500 mode select; DiSEqC port direction (Rev.2) | -| P3.7 | BCM4500 control | HIGH (idle) | De-asserted (HIGH) when streaming stops | - - - -## Port B (XRAM-mapped IOB) - -Port B is used exclusively by the internal debug commands 0x96--0x98. These commands are not used by the Linux or Windows drivers. - - - - -| Pin | Function | Command | -|-----|----------|---------| -| IOB.0 | GPIO status input | GET_GPIO_STATUS (0x98) | -| IOB.1 | LNB control line 1 | SET_GPIO_PINS (0x97) | -| IOB.2 | LNB control line 2 | SET_GPIO_PINS (0x97) | -| IOB.3 | LNB GPIO mode | SET_LNB_GPIO_MODE (0x96) | -| IOB.4 | -- | Unused | - - - - -| Pin | Function | Command | -|-----|----------|---------| -| IOB.0 | -- | Unused | -| IOB.1 | -- | Unused | -| IOB.2 | -- | Unused | -| IOB.3 | -- | Unused | -| IOB.4 | LNB GPIO mode + control | SET_LNB_GPIO_MODE (0x96) + SET_GPIO_PINS (0x97) | - -Rev.2 moved LNB control from Port B to Port A (P0.6, P0.0). - - - - -## DiSEqC Data Pin Summary - -The DiSEqC data pin assignment is the most significant change between firmware versions. The carrier pin (P0.3) remains constant. - -| Firmware Version | Data Pin | Carrier Pin | -|-----------------|----------|-------------| -| v2.06 | P0.7 | P0.3 | -| Rev.2 v2.10 | P0.4 | P0.3 | -| v2.13 | P0.0 | P0.3 | -| Custom v3.01.0 | P0.7 | P0.3 | - -The data pin is used only internally by the firmware's Manchester encoding logic. It controls whether the 22 kHz carrier gate signal (on P0.3) is cut short or held for the full bit period during DiSEqC transmission. See [DiSEqC Protocol](/lnb-diseqc/diseqc-protocol/) for the encoding details. - -## Initial GPIO State - -On power-up, the FX2 initialization code sets: - -| Register | Value | Decode | -|----------|-------|--------| -| IOA (P0) | 0x84 | P0.7=1 (idle), P0.2=1 (power disable active) | -| IOD (P3) | 0xE1 | P3.7:5=1 (all control lines idle), P3.0=1 | -| OEA | 0xBE | P0.1-5,7 configured as outputs | - -These initial states ensure: -- The BCM4500 is held in reset (P0.5 driven by output enable, but P0 init has it low after OEA is set) -- The transport stream bus is idle (P3.5 = HIGH) -- The streaming indicator is off (P0.7 = HIGH) -- All BCM4500 control lines are de-asserted (P3.7:5 = 1) - -## Streaming GPIO State Changes - -| Pin | SFR | Direction | During Streaming | Streaming Stopped | Function | -|-----|-----|-----------|-----------------|-------------------|----------| -| P0.2 | 0x80 | Output | Set during init | -- | BCM4500 config | -| P0.7 | 0x80 | Output | **LOW** | HIGH | Streaming status indicator | -| P3.5 | 0xB0 | Output | Pulsed LOW | HIGH | BCM4500 TS_EN (transport stream enable) | -| P3.6 | 0xB0 | Output | Controlled | HIGH | BCM4500 DVB mode control | -| P3.7 | 0xB0 | Output | Controlled | HIGH | BCM4500 control line | +--- +title: GPIO Pin Map +description: Complete FX2 GPIO pin assignments across all firmware versions including stock and custom firmware. +--- + +import { Tabs, TabItem, Badge, Aside } from '@astrojs/starlight/components'; + +The Cypress FX2 uses three GPIO ports for hardware control on the SkyWalker-1: Port 0 (IOA), Port 3 (IOD), and Port B (XRAM-mapped IOB). Pin assignments differ significantly between firmware versions due to PCB revisions and design changes. + + + +## Port 0 / Port A (SFR 0x80, IOA) + + + + +| Pin | v2.06 | Rev.2 v2.10 | v2.13 | Notes | +|-----|-------|-------------|-------|-------| +| P0.0 | -- | LNB control (0x97) | **DiSEqC data** | DiSEqC data pin moved across versions | +| P0.1 | Power enable | Power enable | Power enable | BCM4500 power supply enable | +| P0.2 | Power disable | Power disable (init=0x84) | Power disable | BCM4500 power supply disable | +| P0.3 | **22 kHz tone** | **22 kHz tone** | **22 kHz tone** | Gates external 22 kHz oscillator (all versions) | +| P0.4 | **LNB 13V/18V** | **LNB 13V/18V** + DiSEqC data | **LNB 13V/18V** | Also SET_DN_SWITCH bit-bang (all versions) | +| P0.5 | **BCM4500 RESET** | GPIO status input (0x98) | **BCM4500 RESET** | Reset and feedback pin | +| P0.6 | -- | GPIO control (0x97) | -- | LNB control on Rev.2 only | +| P0.7 | **DiSEqC data** | Streaming indicator | Streaming indicator | DiSEqC data on v2.06 only | + + + + +| Pin | Custom v3.01.0 | Notes | +|-----|----------------|-------| +| P0.0 | -- | Unused | +| P0.1 | **Power enable** | BCM4500 power supply enable (HIGH = on) | +| P0.2 | **Power disable** | BCM4500 power supply disable (LOW = off) | +| P0.3 | **22 kHz tone** | Gates external 22 kHz oscillator | +| P0.4 | **LNB 13V/18V** | Voltage select: LOW = 13V, HIGH = 18V | +| P0.5 | **BCM4500 RESET** | LOW = reset asserted, HIGH = released | +| P0.6 | -- | Unused | +| P0.7 | **DiSEqC data** + streaming | DiSEqC data (matches v2.06); also streaming indicator | + + + + +## Port 3 / Port D (SFR 0xB0, IOD) + +These pins are consistent across all firmware versions: + +| Pin | Function | Active State | Notes | +|-----|----------|-------------|-------| +| P3.0 | Init HIGH | -- | Set during initialization | +| P3.4 | GPIO control | -- | Used by Rev.2 `FUN_CODE_1fcf` | +| P3.5 | **TS_EN** | LOW | Transport stream enable: LOW = active, HIGH = idle | +| P3.6 | **DVB mode** | -- | BCM4500 mode select; DiSEqC port direction (Rev.2) | +| P3.7 | BCM4500 control | HIGH (idle) | De-asserted (HIGH) when streaming stops | + + + +## Port B (XRAM-mapped IOB) + +Port B is used exclusively by the internal debug commands 0x96--0x98. These commands are not used by the Linux or Windows drivers. + + + + +| Pin | Function | Command | +|-----|----------|---------| +| IOB.0 | GPIO status input | GET_GPIO_STATUS (0x98) | +| IOB.1 | LNB control line 1 | SET_GPIO_PINS (0x97) | +| IOB.2 | LNB control line 2 | SET_GPIO_PINS (0x97) | +| IOB.3 | LNB GPIO mode | SET_LNB_GPIO_MODE (0x96) | +| IOB.4 | -- | Unused | + + + + +| Pin | Function | Command | +|-----|----------|---------| +| IOB.0 | -- | Unused | +| IOB.1 | -- | Unused | +| IOB.2 | -- | Unused | +| IOB.3 | -- | Unused | +| IOB.4 | LNB GPIO mode + control | SET_LNB_GPIO_MODE (0x96) + SET_GPIO_PINS (0x97) | + +Rev.2 moved LNB control from Port B to Port A (P0.6, P0.0). + + + + +## DiSEqC Data Pin Summary + +The DiSEqC data pin assignment is the most significant change between firmware versions. The carrier pin (P0.3) remains constant. + +| Firmware Version | Data Pin | Carrier Pin | +|-----------------|----------|-------------| +| v2.06 | P0.7 | P0.3 | +| Rev.2 v2.10 | P0.4 | P0.3 | +| v2.13 | P0.0 | P0.3 | +| Custom v3.01.0 | P0.7 | P0.3 | + +The data pin is used only internally by the firmware's Manchester encoding logic. It controls whether the 22 kHz carrier gate signal (on P0.3) is cut short or held for the full bit period during DiSEqC transmission. See [DiSEqC Protocol](/lnb-diseqc/diseqc-protocol/) for the encoding details. + +## Initial GPIO State + +On power-up, the FX2 initialization code sets: + +| Register | Value | Decode | +|----------|-------|--------| +| IOA (P0) | 0x84 | P0.7=1 (idle), P0.2=1 (power disable active) | +| IOD (P3) | 0xE1 | P3.7:5=1 (all control lines idle), P3.0=1 | +| OEA | 0xBE | P0.1-5,7 configured as outputs | + +These initial states ensure: +- The BCM4500 is held in reset (P0.5 driven by output enable, but P0 init has it low after OEA is set) +- The transport stream bus is idle (P3.5 = HIGH) +- The streaming indicator is off (P0.7 = HIGH) +- All BCM4500 control lines are de-asserted (P3.7:5 = 1) + +## Streaming GPIO State Changes + +| Pin | SFR | Direction | During Streaming | Streaming Stopped | Function | +|-----|-----|-----------|-----------------|-------------------|----------| +| P0.2 | 0x80 | Output | Set during init | -- | BCM4500 config | +| P0.7 | 0x80 | Output | **LOW** | HIGH | Streaming status indicator | +| P3.5 | 0xB0 | Output | Pulsed LOW | HIGH | BCM4500 TS_EN (transport stream enable) | +| P3.6 | 0xB0 | Output | Controlled | HIGH | BCM4500 DVB mode control | +| P3.7 | 0xB0 | Output | Controlled | HIGH | BCM4500 control line | diff --git a/site/src/content/docs/hardware/overview.mdx b/site/src/content/docs/hardware/overview.mdx index 78cb3e8..7887129 100644 --- a/site/src/content/docs/hardware/overview.mdx +++ b/site/src/content/docs/hardware/overview.mdx @@ -1,98 +1,98 @@ ---- -title: Hardware Overview -description: Board layout, chip identification, supported modulations, and architecture of the Genpix SkyWalker-1 DVB-S USB 2.0 receiver. ---- - -import { Tabs, TabItem, Badge, Aside, CardGrid, Card } from '@astrojs/starlight/components'; - -The Genpix SkyWalker-1 is a standalone USB 2.0 DVB-S satellite receiver built around two primary ICs: a Cypress FX2LP USB microcontroller and a Broadcom BCM4500 satellite demodulator. The FX2 handles all USB communication, LNB control, and DiSEqC signaling, while the BCM4500 performs RF demodulation, forward error correction, and outputs an MPEG-2 transport stream on an 8-bit parallel bus. - - - -## Core Components - -| Component | Part Number | Role | -|-----------|-------------|------| -| MCU | Cypress CY7C68013A (FX2LP) | USB 2.0 Hi-Speed controller, 8051 core at 48 MHz | -| Demodulator | Broadcom BCM4500 | DVB-S / Turbo / DCII / DSS demodulator, 128-pin MQFP | -| EEPROM | 24Cxx-family (I2C address 0x51) | FX2 firmware storage, serial number, calibration data | -| Tuner/LNB | Unknown IC (I2C address 0x10) | Tuner or LNB controller on shared I2C bus | - -## Board Block Diagram - -``` - +--[ I2C EEPROM 0x51 ] - | -USB 2.0 HS | I2C Bus (400 kHz) -Host PC <----> [ CY7C68013A FX2LP ] <-----> [ BCM4500 Demod 0x08 ] - | 8051 @ 48 MHz | | - | GPIF Engine |<-----------+ 8-bit parallel TS - | EP2 Bulk IN | - | GPIO (P0/P3) |---> [ 22 kHz Osc ] ---> LNB/Coax - | |---> [ LNB Voltage Ctrl ] - +-----------------+ - | - +--[ Tuner/LNB IC 0x10 ] -``` - -The GPIF engine inside the FX2 transfers the BCM4500's transport stream output directly into USB bulk endpoint EP2 with zero firmware intervention in the data path. This hardware-managed pipeline provides approximately 5x bandwidth headroom over the maximum DVB-S transport stream rate. - -## Supported Modulations - -| Index | Modulation | Constant | FEC Family | -|-------|-----------|----------|------------| -| 0 | DVB-S QPSK | `ADV_MOD_DVB_QPSK` | Viterbi + Reed-Solomon | -| 1 | Turbo-coded QPSK | `ADV_MOD_TURBO_QPSK` | Turbo | -| 2 | Turbo-coded 8PSK | `ADV_MOD_TURBO_8PSK` | Turbo | -| 3 | Turbo-coded 16QAM | `ADV_MOD_TURBO_16QAM` | Turbo | -| 4 | Digicipher II Combo | `ADV_MOD_DCII_C_QPSK` | DCII | -| 5 | Digicipher II I-stream (split) | `ADV_MOD_DCII_I_QPSK` | DCII | -| 6 | Digicipher II Q-stream (split) | `ADV_MOD_DCII_Q_QPSK` | DCII | -| 7 | Digicipher II Offset QPSK | `ADV_MOD_DCII_C_OQPSK` | DCII | -| 8 | DSS QPSK | `ADV_MOD_DSS_QPSK` | Viterbi + Reed-Solomon | -| 9 | DVB-S BPSK | `ADV_MOD_DVB_BPSK` | Viterbi + Reed-Solomon | - - - -## Architecture Overview - - - - EP0 for vendor commands (tuning, LNB control, status). EP2 for bulk MPEG-2 transport stream data. VID `0x09C0`, PID `0x0203`. See [USB Interface](/usb/interface/). - - - Indirect register access via I2C (0xA6/0xA7/0xA8 protocol). Two FEC decoder paths: turbo codes and legacy Viterbi/Reed-Solomon. See [Demodulator Interface](/bcm4500/demodulator/). - - - Hardware-managed data path. 8-bit parallel bus from BCM4500 to EP2 FIFO via GPIF master read. AUTOIN auto-commits full packets to USB. See [GPIF Streaming](/bcm4500/gpif-streaming/). - - - 400 kHz bus connecting FX2 to BCM4500 (0x08), tuner IC (0x10), and EEPROM (0x51). Bit-banged DiSEqC on separate GPIO pins. See [I2C Bus Architecture](/i2c/bus-architecture/). - - - -## FEC Architecture - -The BCM4500 contains two distinct FEC decoder paths: - -1. **Advanced Modulation Turbo FEC Decoder** -- Iterative turbo code decoder supporting QPSK (rates 1/4, 1/2, 3/4), 8PSK (rates 2/3, 3/4, 5/6, 8/9), 16QAM (rate 3/4), with Reed-Solomon (t=10) outer code. - -2. **Legacy DVB/DIRECTV/DCII-Compliant FEC Decoder** -- Concatenated Viterbi inner decoder (convolutional code, rates 1/2 through 7/8) plus Reed-Solomon outer decoder. - -There is no LDPC or BCH decoder hardware. The turbo FEC codes are Broadcom/EchoStar proprietary and are not part of any open standard. - -## Firmware - -The SkyWalker-1 boots from an on-board I2C EEPROM containing firmware in Cypress C2 format. No host-side firmware files are required. Multiple stock firmware versions have been identified: - -| Firmware | Version ID | Build Date | Notes | -|----------|-----------|------------|-------| -| v2.06.04 | 0x020604 | 2007-07-13 | Original release, 61 functions | -| v2.13.01 | 0x020D01 | 2010-03-12 | Latest revision, 82-88 functions | -| Custom v3.01.0 | 0x030100 | 2026-02-12 | Open-source SDCC + fx2lib, RAM-loaded | - -See [Firmware Version Comparison](/firmware/version-comparison/) for a full analysis of the differences between stock firmware versions. +--- +title: Hardware Overview +description: Board layout, chip identification, supported modulations, and architecture of the Genpix SkyWalker-1 DVB-S USB 2.0 receiver. +--- + +import { Tabs, TabItem, Badge, Aside, CardGrid, Card } from '@astrojs/starlight/components'; + +The Genpix SkyWalker-1 is a standalone USB 2.0 DVB-S satellite receiver built around two primary ICs: a Cypress FX2LP USB microcontroller and a Broadcom BCM4500 satellite demodulator. The FX2 handles all USB communication, LNB control, and DiSEqC signaling, while the BCM4500 performs RF demodulation, forward error correction, and outputs an MPEG-2 transport stream on an 8-bit parallel bus. + + + +## Core Components + +| Component | Part Number | Role | +|-----------|-------------|------| +| MCU | Cypress CY7C68013A (FX2LP) | USB 2.0 Hi-Speed controller, 8051 core at 48 MHz | +| Demodulator | Broadcom BCM4500 | DVB-S / Turbo / DCII / DSS demodulator, 128-pin MQFP | +| EEPROM | 24Cxx-family (I2C address 0x51) | FX2 firmware storage, serial number, calibration data | +| Tuner/LNB | Unknown IC (I2C address 0x10) | Tuner or LNB controller on shared I2C bus | + +## Board Block Diagram + +``` + +--[ I2C EEPROM 0x51 ] + | +USB 2.0 HS | I2C Bus (400 kHz) +Host PC <----> [ CY7C68013A FX2LP ] <-----> [ BCM4500 Demod 0x08 ] + | 8051 @ 48 MHz | | + | GPIF Engine |<-----------+ 8-bit parallel TS + | EP2 Bulk IN | + | GPIO (P0/P3) |---> [ 22 kHz Osc ] ---> LNB/Coax + | |---> [ LNB Voltage Ctrl ] + +-----------------+ + | + +--[ Tuner/LNB IC 0x10 ] +``` + +The GPIF engine inside the FX2 transfers the BCM4500's transport stream output directly into USB bulk endpoint EP2 with zero firmware intervention in the data path. This hardware-managed pipeline provides approximately 5x bandwidth headroom over the maximum DVB-S transport stream rate. + +## Supported Modulations + +| Index | Modulation | Constant | FEC Family | +|-------|-----------|----------|------------| +| 0 | DVB-S QPSK | `ADV_MOD_DVB_QPSK` | Viterbi + Reed-Solomon | +| 1 | Turbo-coded QPSK | `ADV_MOD_TURBO_QPSK` | Turbo | +| 2 | Turbo-coded 8PSK | `ADV_MOD_TURBO_8PSK` | Turbo | +| 3 | Turbo-coded 16QAM | `ADV_MOD_TURBO_16QAM` | Turbo | +| 4 | Digicipher II Combo | `ADV_MOD_DCII_C_QPSK` | DCII | +| 5 | Digicipher II I-stream (split) | `ADV_MOD_DCII_I_QPSK` | DCII | +| 6 | Digicipher II Q-stream (split) | `ADV_MOD_DCII_Q_QPSK` | DCII | +| 7 | Digicipher II Offset QPSK | `ADV_MOD_DCII_C_OQPSK` | DCII | +| 8 | DSS QPSK | `ADV_MOD_DSS_QPSK` | Viterbi + Reed-Solomon | +| 9 | DVB-S BPSK | `ADV_MOD_DVB_BPSK` | Viterbi + Reed-Solomon | + + + +## Architecture Overview + + + + EP0 for vendor commands (tuning, LNB control, status). EP2 for bulk MPEG-2 transport stream data. VID `0x09C0`, PID `0x0203`. See [USB Interface](/usb/interface/). + + + Indirect register access via I2C (0xA6/0xA7/0xA8 protocol). Two FEC decoder paths: turbo codes and legacy Viterbi/Reed-Solomon. See [Demodulator Interface](/bcm4500/demodulator/). + + + Hardware-managed data path. 8-bit parallel bus from BCM4500 to EP2 FIFO via GPIF master read. AUTOIN auto-commits full packets to USB. See [GPIF Streaming](/bcm4500/gpif-streaming/). + + + 400 kHz bus connecting FX2 to BCM4500 (0x08), tuner IC (0x10), and EEPROM (0x51). Bit-banged DiSEqC on separate GPIO pins. See [I2C Bus Architecture](/i2c/bus-architecture/). + + + +## FEC Architecture + +The BCM4500 contains two distinct FEC decoder paths: + +1. **Advanced Modulation Turbo FEC Decoder** -- Iterative turbo code decoder supporting QPSK (rates 1/4, 1/2, 3/4), 8PSK (rates 2/3, 3/4, 5/6, 8/9), 16QAM (rate 3/4), with Reed-Solomon (t=10) outer code. + +2. **Legacy DVB/DIRECTV/DCII-Compliant FEC Decoder** -- Concatenated Viterbi inner decoder (convolutional code, rates 1/2 through 7/8) plus Reed-Solomon outer decoder. + +There is no LDPC or BCH decoder hardware. The turbo FEC codes are Broadcom/EchoStar proprietary and are not part of any open standard. + +## Firmware + +The SkyWalker-1 boots from an on-board I2C EEPROM containing firmware in Cypress C2 format. No host-side firmware files are required. Multiple stock firmware versions have been identified: + +| Firmware | Version ID | Build Date | Notes | +|----------|-----------|------------|-------| +| v2.06.04 | 0x020604 | 2007-07-13 | Original release, 61 functions | +| v2.13.01 | 0x020D01 | 2010-03-12 | Latest revision, 82-88 functions | +| Custom v3.01.0 | 0x030100 | 2026-02-12 | Open-source SDCC + fx2lib, RAM-loaded | + +See [Firmware Version Comparison](/firmware/version-comparison/) for a full analysis of the differences between stock firmware versions. diff --git a/site/src/content/docs/hardware/rf-coverage.mdx b/site/src/content/docs/hardware/rf-coverage.mdx index 9fb5bed..1b5a02c 100644 --- a/site/src/content/docs/hardware/rf-coverage.mdx +++ b/site/src/content/docs/hardware/rf-coverage.mdx @@ -1,110 +1,110 @@ ---- -title: RF Coverage and Limitations -description: How LNB frequency shifting maps the 950-2150 MHz IF range to different RF bands, and what the hardware can and cannot do beyond satellite reception. ---- - -import { Aside } from '@astrojs/starlight/components'; - -The SkyWalker-1 was designed as a DVB-S satellite receiver, but the BCM4500's signal monitoring registers work whether or not a signal is locked. This makes the hardware useful as a crude spectrum analyzer and RF power detector across any band an LNB can translate into the 950--2150 MHz IF window. Understanding what the hardware actually measures -- and where the measurement breaks down -- is essential to interpreting sweep results correctly. - -## How the Power Detector Works - -The BCM4500 demodulator contains an AGC (Automatic Gain Control) loop that adjusts the tuner gain to maintain a constant signal level at the ADC. The AGC register values (indirect registers 0x02--0x05) reflect how much gain the system is applying. This mechanism operates continuously, regardless of whether the demodulator achieves signal lock. - -The key insight: **higher AGC values mean weaker signals** (the system needs more gain to reach the target level). The AGC responds to the total received power within the tuner's passband at the tuned frequency. It does not discriminate between modulated carriers, noise floors, or interference -- it measures raw RF energy. - -The SIGNAL_MONITOR command (0xB7) reads both the SNR and AGC registers in a single USB transfer, avoiding the round-trip overhead of separate indirect register reads. The TUNE_MONITOR command (0xB8) combines tune + settle + read into one operation, forming the building block for spectrum sweeps. See [Signal Monitoring](/bcm4500/signal-monitoring/) for the register-level details. - -## LNB Frequency Shifting - -A Low-Noise Block downconverter (LNB) mounted at the dish feed performs frequency translation: it mixes the incoming RF signal with its internal local oscillator (LO), producing an intermediate frequency (IF) output that travels down the coaxial cable to the receiver. - -``` -actual_rf = if_frequency + lnb_lo -if_frequency = actual_rf - lnb_lo -``` - -The SkyWalker-1 tunes within a fixed IF range: 950--2150 MHz. The actual RF band covered depends entirely on which LNB is connected and what its LO frequency is. The receiver has no knowledge of the LNB's LO -- the host software must account for the frequency offset when computing tune parameters and interpreting results. - -## RF Coverage Map - -| Configuration | LNB LO (MHz) | IF Range (MHz) | Actual RF Covered (MHz) | Typical Use | -|---|---|---|---|---| -| Ku-band low | 9,750 | 950--2,150 | 10,700--11,900 | Satellite TV low band | -| Ku-band high | 10,600 | 950--2,150 | 11,550--12,750 | Satellite TV high band | -| C-band | 5,150 | 950--2,150 | 6,100--7,300 | C-band satellite | -| No LNB (direct) | 0 | 950--2,150 | 950--2,150 | L-band direct input | -| Custom (9.0 GHz) | 9,000 | 950--2,150 | 9,950--11,150 | QO-100 DATV | - - - -## L-Band Direct Input - -With no LNB -- cable connected directly to an antenna or feed -- the hardware covers the raw 950--2,150 MHz IF range. This encompasses several interesting allocations: - -| Range (MHz) | Allocation | Detectable? | Demodulatable? | -|---|---|---|---| -| 1,240--1,300 | Amateur 23 cm band | Yes (energy) | No (SSB/CW/FM) | -| 1,525--1,559 | Inmarsat downlink | Yes (energy) | No (proprietary) | -| 1,559--1,610 | GNSS (GPS L1, Galileo E1) | Yes (spread spectrum) | No (CDMA/BOC) | -| 1,610--1,626 | Iridium downlink | Yes (energy) | No (TDMA/FDMA) | -| 1,670--1,710 | MetSat (GOES LRIT, NOAA HRPT) | Yes (carrier) | No (non-DVB framing) | -| 1,710--1,785 | LTE/AWS uplink | Yes (energy) | No (OFDM) | -| 1,920--2,025 | UMTS uplink | Yes (energy) | No (WCDMA) | - -"Detectable" means the AGC registers respond to RF energy at that frequency -- the hardware sees something. "Demodulatable" means the BCM4500 can lock onto the signal and produce a decoded transport stream. Only DVB-S, Turbo-coded, DCII, and DSS signals can be demodulated. Everything else shows up as an energy level without any ability to extract data. - -### The QO-100 Exception - -The Es'hail-2/QO-100 amateur satellite at 25.9 degrees East carries DVB-S DATV signals in the 10,491--10,499 MHz range. These are actual DVB-S QPSK signals at low symbol rates (333--2,000 ksps) -- the one case where amateur satellite signals use a modulation the SkyWalker-1 can natively demodulate. - -The problem is IF range. With a standard 9,750 MHz universal LNB, 10,494 MHz maps to 744 MHz IF -- below the SkyWalker-1's 950 MHz minimum. A custom LNB with a ~9.0 GHz local oscillator puts the same signal at ~1,494 MHz IF, comfortably in range. - - - -## Hardware Limitations - -The SkyWalker-1 is a satellite receiver repurposed as a measurement tool. The results are useful but carry inherent constraints worth understanding before interpreting sweep data. - -### Resolution Bandwidth - -The minimum symbol rate is 256 ksps, giving a minimum resolution bandwidth of approximately 346 kHz (`symbol_rate * 1.35` roll-off factor). This is far coarser than a dedicated spectrum analyzer, which might offer 1 kHz or even 10 Hz RBW. Narrowband signals -- SSB at 3 kHz, CW at 500 Hz, FM repeater outputs at 25 kHz -- cannot be individually resolved. They appear as a single energy bump within the ~346 kHz measurement window, indistinguishable from each other or from broadband noise at similar levels. - -### Dynamic Range - -The AGC provides approximately 30--40 dB of usable dynamic range. A professional spectrum analyzer offers 70+ dB. The practical consequence: weak signals near strong ones get masked. A satellite transponder 25 dB above the noise floor is clearly visible; a signal 5 dB above the noise floor next to a strong adjacent carrier may not be. - -### Sweep Speed - -Each tune-measure step takes approximately 12 ms (tune settling + dwell + USB transfer). A full 950--2,150 MHz sweep at 5 MHz steps requires about 240 steps at 12 ms each, totaling approximately 2.9 seconds. This is adequate for mapping satellite transponders or identifying persistent carriers, but too slow for capturing transient signals or frequency-hopping transmissions. - -### What It Does Well - -| Strength | Detail | -|---|---| -| Built-in LNB power | 13V/18V, 22 kHz tone, DiSEqC 1.0/1.2 -- complete satellite receiver chain with no external hardware | -| Native DVB-S demodulation | Can lock, decode, and stream DVB-S, Turbo, DCII, and DSS signals as MPEG-2 transport streams | -| Power measurement | Detects RF energy across the full 950--2,150 MHz IF range regardless of modulation | -| Transport stream capture | GPIF streaming provides real MPEG-2 TS data for locked signals | -| Low cost | Repurposes existing satellite receiver hardware for spectrum awareness | - -### What It Cannot Do - -| Limitation | Detail | -|---|---| -| Not an SDR | Cannot capture raw IQ samples or demodulate arbitrary waveforms | -| Fixed demod pipeline | Only DVB-S / Turbo / DCII / DSS modulations -- no FM, SSB, CW, OFDM, ATSC | -| Coarse RBW | Minimum ~346 kHz resolution bandwidth; narrowband signals are unresolvable | -| Limited dynamic range | ~30--40 dB usable vs 70+ dB for dedicated spectrum analyzers | -| No DVB-S2 | Incompatible FEC (LDPC vs Reed-Solomon) -- see [DVB-S2 Incompatibility](/driver/dvb-s2/) | - -## See Also - -- [Tuning Tool](/tools/tuning/) -- the primary user-facing tool for tuning, monitoring, and capture -- [RF Specifications](/hardware/rf-specifications/) -- electrical parameters, signal path, and LNB current limits -- [Signal Monitoring](/bcm4500/signal-monitoring/) -- AGC and SNR register details used by the power detector -- [LNB Control](/lnb-diseqc/lnb-control/) -- voltage, tone, and DiSEqC command interface +--- +title: RF Coverage and Limitations +description: How LNB frequency shifting maps the 950-2150 MHz IF range to different RF bands, and what the hardware can and cannot do beyond satellite reception. +--- + +import { Aside } from '@astrojs/starlight/components'; + +The SkyWalker-1 was designed as a DVB-S satellite receiver, but the BCM4500's signal monitoring registers work whether or not a signal is locked. This makes the hardware useful as a crude spectrum analyzer and RF power detector across any band an LNB can translate into the 950--2150 MHz IF window. Understanding what the hardware actually measures -- and where the measurement breaks down -- is essential to interpreting sweep results correctly. + +## How the Power Detector Works + +The BCM4500 demodulator contains an AGC (Automatic Gain Control) loop that adjusts the tuner gain to maintain a constant signal level at the ADC. The AGC register values (indirect registers 0x02--0x05) reflect how much gain the system is applying. This mechanism operates continuously, regardless of whether the demodulator achieves signal lock. + +The key insight: **higher AGC values mean weaker signals** (the system needs more gain to reach the target level). The AGC responds to the total received power within the tuner's passband at the tuned frequency. It does not discriminate between modulated carriers, noise floors, or interference -- it measures raw RF energy. + +The SIGNAL_MONITOR command (0xB7) reads both the SNR and AGC registers in a single USB transfer, avoiding the round-trip overhead of separate indirect register reads. The TUNE_MONITOR command (0xB8) combines tune + settle + read into one operation, forming the building block for spectrum sweeps. See [Signal Monitoring](/bcm4500/signal-monitoring/) for the register-level details. + +## LNB Frequency Shifting + +A Low-Noise Block downconverter (LNB) mounted at the dish feed performs frequency translation: it mixes the incoming RF signal with its internal local oscillator (LO), producing an intermediate frequency (IF) output that travels down the coaxial cable to the receiver. + +``` +actual_rf = if_frequency + lnb_lo +if_frequency = actual_rf - lnb_lo +``` + +The SkyWalker-1 tunes within a fixed IF range: 950--2150 MHz. The actual RF band covered depends entirely on which LNB is connected and what its LO frequency is. The receiver has no knowledge of the LNB's LO -- the host software must account for the frequency offset when computing tune parameters and interpreting results. + +## RF Coverage Map + +| Configuration | LNB LO (MHz) | IF Range (MHz) | Actual RF Covered (MHz) | Typical Use | +|---|---|---|---|---| +| Ku-band low | 9,750 | 950--2,150 | 10,700--11,900 | Satellite TV low band | +| Ku-band high | 10,600 | 950--2,150 | 11,550--12,750 | Satellite TV high band | +| C-band | 5,150 | 950--2,150 | 6,100--7,300 | C-band satellite | +| No LNB (direct) | 0 | 950--2,150 | 950--2,150 | L-band direct input | +| Custom (9.0 GHz) | 9,000 | 950--2,150 | 9,950--11,150 | QO-100 DATV | + + + +## L-Band Direct Input + +With no LNB -- cable connected directly to an antenna or feed -- the hardware covers the raw 950--2,150 MHz IF range. This encompasses several interesting allocations: + +| Range (MHz) | Allocation | Detectable? | Demodulatable? | +|---|---|---|---| +| 1,240--1,300 | Amateur 23 cm band | Yes (energy) | No (SSB/CW/FM) | +| 1,525--1,559 | Inmarsat downlink | Yes (energy) | No (proprietary) | +| 1,559--1,610 | GNSS (GPS L1, Galileo E1) | Yes (spread spectrum) | No (CDMA/BOC) | +| 1,610--1,626 | Iridium downlink | Yes (energy) | No (TDMA/FDMA) | +| 1,670--1,710 | MetSat (GOES LRIT, NOAA HRPT) | Yes (carrier) | No (non-DVB framing) | +| 1,710--1,785 | LTE/AWS uplink | Yes (energy) | No (OFDM) | +| 1,920--2,025 | UMTS uplink | Yes (energy) | No (WCDMA) | + +"Detectable" means the AGC registers respond to RF energy at that frequency -- the hardware sees something. "Demodulatable" means the BCM4500 can lock onto the signal and produce a decoded transport stream. Only DVB-S, Turbo-coded, DCII, and DSS signals can be demodulated. Everything else shows up as an energy level without any ability to extract data. + +### The QO-100 Exception + +The Es'hail-2/QO-100 amateur satellite at 25.9 degrees East carries DVB-S DATV signals in the 10,491--10,499 MHz range. These are actual DVB-S QPSK signals at low symbol rates (333--2,000 ksps) -- the one case where amateur satellite signals use a modulation the SkyWalker-1 can natively demodulate. + +The problem is IF range. With a standard 9,750 MHz universal LNB, 10,494 MHz maps to 744 MHz IF -- below the SkyWalker-1's 950 MHz minimum. A custom LNB with a ~9.0 GHz local oscillator puts the same signal at ~1,494 MHz IF, comfortably in range. + + + +## Hardware Limitations + +The SkyWalker-1 is a satellite receiver repurposed as a measurement tool. The results are useful but carry inherent constraints worth understanding before interpreting sweep data. + +### Resolution Bandwidth + +The minimum symbol rate is 256 ksps, giving a minimum resolution bandwidth of approximately 346 kHz (`symbol_rate * 1.35` roll-off factor). This is far coarser than a dedicated spectrum analyzer, which might offer 1 kHz or even 10 Hz RBW. Narrowband signals -- SSB at 3 kHz, CW at 500 Hz, FM repeater outputs at 25 kHz -- cannot be individually resolved. They appear as a single energy bump within the ~346 kHz measurement window, indistinguishable from each other or from broadband noise at similar levels. + +### Dynamic Range + +The AGC provides approximately 30--40 dB of usable dynamic range. A professional spectrum analyzer offers 70+ dB. The practical consequence: weak signals near strong ones get masked. A satellite transponder 25 dB above the noise floor is clearly visible; a signal 5 dB above the noise floor next to a strong adjacent carrier may not be. + +### Sweep Speed + +Each tune-measure step takes approximately 12 ms (tune settling + dwell + USB transfer). A full 950--2,150 MHz sweep at 5 MHz steps requires about 240 steps at 12 ms each, totaling approximately 2.9 seconds. This is adequate for mapping satellite transponders or identifying persistent carriers, but too slow for capturing transient signals or frequency-hopping transmissions. + +### What It Does Well + +| Strength | Detail | +|---|---| +| Built-in LNB power | 13V/18V, 22 kHz tone, DiSEqC 1.0/1.2 -- complete satellite receiver chain with no external hardware | +| Native DVB-S demodulation | Can lock, decode, and stream DVB-S, Turbo, DCII, and DSS signals as MPEG-2 transport streams | +| Power measurement | Detects RF energy across the full 950--2,150 MHz IF range regardless of modulation | +| Transport stream capture | GPIF streaming provides real MPEG-2 TS data for locked signals | +| Low cost | Repurposes existing satellite receiver hardware for spectrum awareness | + +### What It Cannot Do + +| Limitation | Detail | +|---|---| +| Not an SDR | Cannot capture raw IQ samples or demodulate arbitrary waveforms | +| Fixed demod pipeline | Only DVB-S / Turbo / DCII / DSS modulations -- no FM, SSB, CW, OFDM, ATSC | +| Coarse RBW | Minimum ~346 kHz resolution bandwidth; narrowband signals are unresolvable | +| Limited dynamic range | ~30--40 dB usable vs 70+ dB for dedicated spectrum analyzers | +| No DVB-S2 | Incompatible FEC (LDPC vs Reed-Solomon) -- see [DVB-S2 Incompatibility](/driver/dvb-s2/) | + +## See Also + +- [Tuning Tool](/tools/tuning/) -- the primary user-facing tool for tuning, monitoring, and capture +- [RF Specifications](/hardware/rf-specifications/) -- electrical parameters, signal path, and LNB current limits +- [Signal Monitoring](/bcm4500/signal-monitoring/) -- AGC and SNR register details used by the power detector +- [LNB Control](/lnb-diseqc/lnb-control/) -- voltage, tone, and DiSEqC command interface diff --git a/site/src/content/docs/hardware/rf-specifications.mdx b/site/src/content/docs/hardware/rf-specifications.mdx index 911eea2..3f308af 100644 --- a/site/src/content/docs/hardware/rf-specifications.mdx +++ b/site/src/content/docs/hardware/rf-specifications.mdx @@ -1,94 +1,94 @@ ---- -title: RF Specifications -description: RF input parameters, LNB voltage and current limits, and symbol rate range for the SkyWalker-1. ---- - -import { Aside } from '@astrojs/starlight/components'; - -## RF Input Parameters - -| Parameter | Value | -|-----------|-------| -| IF frequency range | 950 -- 2150 MHz | -| Symbol rate | 256 Ksps -- 30 Msps | -| Input connector | IEC F-type female | - -The IF frequency is the intermediate frequency after LNB downconversion. The host computes it as `(RF_freq - LO_freq) * multiplier` and sends it in the [TUNE_8PSK](/bcm4500/tuning-protocol/) command payload. - -## LNB Power Supply - -| Parameter | Value | -|-----------|-------| -| LNB voltage (standard) | 13V / 18V | -| LNB voltage (boosted) | 14V / 19V (with USE_EXTRA_VOLT) | -| Maximum continuous current | 450 mA | -| Maximum burst current | 750 mA | - - - -LNB voltage is controlled via GPIO P0.4 on all firmware versions. The voltage selection determines the polarization: - -| wValue | Voltage | GPIO P0.4 | Polarization | -|--------|---------|-----------|-------------| -| 0 | 13V | LOW | Vertical / Circular-Right | -| 1 | 18V | HIGH | Horizontal / Circular-Left | - -The USE_EXTRA_VOLT command (0x94) enables a +1V boost for long cable runs by toggling bit 3 of the LNB control register at XRAM 0xE0B6 (0x62 = normal, 0x6A = boosted). See [Vendor Commands](/usb/vendor-commands/) for the command interface. - -## Switch Control - -The SkyWalker-1 supports multiple satellite switching protocols: - -| Protocol | Implementation | Command | -|----------|---------------|---------| -| 22 kHz tone | GPIO P0.3 gates external oscillator | SET_22KHZ_TONE (0x8C) | -| Tone Burst (mini DiSEqC) | Timer2-based carrier gating | SEND_DISEQC_COMMAND (0x8D) | -| DiSEqC 1.0 | Timer2 Manchester encoding | SEND_DISEQC_COMMAND (0x8D) | -| DiSEqC 1.2 | Timer2 Manchester encoding | SEND_DISEQC_COMMAND (0x8D) | -| Legacy Dish Network | 7-bit serial bit-bang on P0.4 | SET_DN_SWITCH (0x8F) | - -## 22 kHz Tone - -The 22 kHz tone signal is generated by an external oscillator on the PCB, gated by GPIO P0.3. The firmware does not generate the 22 kHz carrier directly -- it only enables or disables the oscillator output. - -| wValue | State | GPIO P0.3 | Band Selection | -|--------|-------|-----------|------| -| 0 | OFF | LOW | Low band (9.75 GHz LO on universal LNB) | -| 1 | ON | HIGH | High band (10.6 GHz LO on universal LNB) | - -## Signal Path - -``` -Satellite - | - v -[ LNB on Dish ] <-- 13V/18V + 22kHz from SkyWalker-1 - | - | Coax (950-2150 MHz IF) - v -[ SkyWalker-1 F-connector ] - | - v -[ BCM4500 Demodulator ] -- demod + FEC decode - | - | 8-bit parallel MPEG-2 TS - v -[ FX2 GPIF Engine ] -- zero-copy DMA to EP2 - | - | USB 2.0 High-Speed Bulk - v -[ Host PC ] -``` - -The USB/GPIF data path has substantial headroom over the satellite link throughput: - -| Metric | Value | -|--------|-------| -| USB 2.0 HS bulk (practical) | ~280 Mbps (~35 MB/s) | -| GPIF engine (theoretical) | 48 MHz x 8 bits = 384 Mbps | -| Typical DVB-S TS rate | 1--5 MB/s | -| Maximum symbol rate (30 Msps) | ~58 Mbps | - -The bottleneck for all supported modulation modes is the satellite link, not the USB data path. +--- +title: RF Specifications +description: RF input parameters, LNB voltage and current limits, and symbol rate range for the SkyWalker-1. +--- + +import { Aside } from '@astrojs/starlight/components'; + +## RF Input Parameters + +| Parameter | Value | +|-----------|-------| +| IF frequency range | 950 -- 2150 MHz | +| Symbol rate | 256 Ksps -- 30 Msps | +| Input connector | IEC F-type female | + +The IF frequency is the intermediate frequency after LNB downconversion. The host computes it as `(RF_freq - LO_freq) * multiplier` and sends it in the [TUNE_8PSK](/bcm4500/tuning-protocol/) command payload. + +## LNB Power Supply + +| Parameter | Value | +|-----------|-------| +| LNB voltage (standard) | 13V / 18V | +| LNB voltage (boosted) | 14V / 19V (with USE_EXTRA_VOLT) | +| Maximum continuous current | 450 mA | +| Maximum burst current | 750 mA | + + + +LNB voltage is controlled via GPIO P0.4 on all firmware versions. The voltage selection determines the polarization: + +| wValue | Voltage | GPIO P0.4 | Polarization | +|--------|---------|-----------|-------------| +| 0 | 13V | LOW | Vertical / Circular-Right | +| 1 | 18V | HIGH | Horizontal / Circular-Left | + +The USE_EXTRA_VOLT command (0x94) enables a +1V boost for long cable runs by toggling bit 3 of the LNB control register at XRAM 0xE0B6 (0x62 = normal, 0x6A = boosted). See [Vendor Commands](/usb/vendor-commands/) for the command interface. + +## Switch Control + +The SkyWalker-1 supports multiple satellite switching protocols: + +| Protocol | Implementation | Command | +|----------|---------------|---------| +| 22 kHz tone | GPIO P0.3 gates external oscillator | SET_22KHZ_TONE (0x8C) | +| Tone Burst (mini DiSEqC) | Timer2-based carrier gating | SEND_DISEQC_COMMAND (0x8D) | +| DiSEqC 1.0 | Timer2 Manchester encoding | SEND_DISEQC_COMMAND (0x8D) | +| DiSEqC 1.2 | Timer2 Manchester encoding | SEND_DISEQC_COMMAND (0x8D) | +| Legacy Dish Network | 7-bit serial bit-bang on P0.4 | SET_DN_SWITCH (0x8F) | + +## 22 kHz Tone + +The 22 kHz tone signal is generated by an external oscillator on the PCB, gated by GPIO P0.3. The firmware does not generate the 22 kHz carrier directly -- it only enables or disables the oscillator output. + +| wValue | State | GPIO P0.3 | Band Selection | +|--------|-------|-----------|------| +| 0 | OFF | LOW | Low band (9.75 GHz LO on universal LNB) | +| 1 | ON | HIGH | High band (10.6 GHz LO on universal LNB) | + +## Signal Path + +``` +Satellite + | + v +[ LNB on Dish ] <-- 13V/18V + 22kHz from SkyWalker-1 + | + | Coax (950-2150 MHz IF) + v +[ SkyWalker-1 F-connector ] + | + v +[ BCM4500 Demodulator ] -- demod + FEC decode + | + | 8-bit parallel MPEG-2 TS + v +[ FX2 GPIF Engine ] -- zero-copy DMA to EP2 + | + | USB 2.0 High-Speed Bulk + v +[ Host PC ] +``` + +The USB/GPIF data path has substantial headroom over the satellite link throughput: + +| Metric | Value | +|--------|-------| +| USB 2.0 HS bulk (practical) | ~280 Mbps (~35 MB/s) | +| GPIF engine (theoretical) | 48 MHz x 8 bits = 384 Mbps | +| Typical DVB-S TS rate | 1--5 MB/s | +| Maximum symbol rate (30 Msps) | ~58 Mbps | + +The bottleneck for all supported modulation modes is the satellite link, not the USB data path. diff --git a/site/src/content/docs/i2c/bus-architecture.mdx b/site/src/content/docs/i2c/bus-architecture.mdx index 58c171f..9f4400c 100644 --- a/site/src/content/docs/i2c/bus-architecture.mdx +++ b/site/src/content/docs/i2c/bus-architecture.mdx @@ -1,156 +1,156 @@ ---- -title: I2C Bus Architecture -description: FX2 I2C controller details, bus topology, device addresses, and the combined write-read protocol. ---- - -import { Aside, Steps } from '@astrojs/starlight/components'; - -The SkyWalker-1 uses a single I2C bus connecting the FX2 microcontroller (master) to three slave devices. The FX2's hardware I2C controller handles all bus transactions through XRAM-mapped registers. - -## FX2 I2C Controller - -| SFR | Address | Function | -|-----|---------|----------| -| I2CS | 0xE678 (XRAM) | I2C control/status register | -| I2DAT | 0xE679 (XRAM) | I2C data register | -| I2CTL | 0xE67A (XRAM) | I2C control (speed selection) | - -### I2CS Control/Status Bits - -| Bit | Name | Function | -|-----|------|----------| -| bmSTART | bit 7 | Write: initiate START condition | -| bmSTOP | bit 6 | Write: initiate STOP condition | -| bmLASTRD | bit 5 | Write: signal last read byte (NACK after next read) | -| bmDONE | bit 2 | Read: byte transfer complete | -| bmACK | bit 1 | Read: ACK received from slave | -| bmBERR | bit 0 | Read: bus error detected | - -## Bus Speed - -The I2C bus operates at 400 kHz. The speed is set through two mechanisms: - -1. **C2 EEPROM header** -- Config byte at offset 7 = 0x40, which the FX2 boot ROM uses to configure the I2C speed during initial EEPROM read. -2. **Firmware/software** -- I2CTL = `bm400KHZ` (written by the custom firmware and init table). - -## Known Bus Devices - -| 7-bit Address | Wire Write | Wire Read | Identity | -|---------------|-----------|----------|----------| -| 0x08 | 0x10 | 0x11 | BCM4500 demodulator | -| 0x10 | 0x20 | 0x21 | Tuner or LNB controller | -| 0x51 | 0xA2 | 0xA3 | Configuration EEPROM (24Cxx-family) | - -These addresses were confirmed via the I2C_BUS_SCAN command (0xB4) in custom firmware v3.01.0, which probes all 7-bit addresses from 0x01 to 0x77. - -### BCM4500 Demodulator (0x08) - -The primary device for all demodulation operations. Register access uses the [indirect register protocol](/bcm4500/demodulator/) through registers 0xA6, 0xA7, and 0xA8. Direct registers 0xA2, 0xA4, and 0xF9 provide status information. - -The v2.13 firmware also probes alternate addresses 0x7F and 0x3F at startup to detect which demodulator variant is present. - -### Tuner/LNB Controller (0x10) - -Likely a tuner IC or LNB controller. In normal operation, the BCM4500 accesses this device internally for tuning. It is also directly addressable on the shared I2C bus. The kernel driver does not directly communicate with this device. - -### Configuration EEPROM (0x51) - -A 24Cxx-family serial EEPROM that stores: -- Device serial number (read by GET_SERIAL_NUMBER, 0x93) -- Hardware platform ID (read by GET_FPGA_VERS, 0x95) -- Calibration data - -## Bus Topology - -``` - FX2 I2C Master - (I2CS/I2DAT/I2CTL) - | - +----------+-----------+ - | | | - BCM4500 Tuner/LNB EEPROM - (0x08) (0x10) (0x51) -``` - -All three devices share the same SCL/SDA lines. The FX2's hardware I2C controller manages bus arbitration and clock stretching detection. - -## Combined Write-Read Protocol - -All BCM4500 register reads use the I2C combined write-read protocol with a repeated START condition. This is required because the BCM4500 uses a register-addressed protocol where the register number must be sent in a write phase before the read phase. - -``` -Complete I2C transaction for reading register 0xA2 from device 0x08: - - Phase 1 (Write): - [S] [0x10] [ACK] [0xA2] [ACK] - | | | | | - | | | | +-- BCM4500 ACKs register address - | | | +--------- Register address byte - | | +---------------- BCM4500 ACKs its device address - | +----------------------- Device address (0x08 << 1) = 0x10 (write) - +---------------------------- START condition - - Phase 2 (Read with Repeated START): - [Sr] [0x11] [ACK] [DATA] [NACK] [P] - | | | | | | - | | | | | +-- STOP condition - | | | | +--------- Master NACKs (last byte) - | | | +---------------- Register data - | | +----------------------- BCM4500 ACKs its device address - | +------------------------------ Device address (0x08 << 1 | 1) = 0x11 (read) - +------------------------------------ REPEATED START (no STOP between phases) -``` - -The repeated START (Sr) is essential. A STOP between phases would release the bus, and the BCM4500 would lose the register address context. - -### FX2 SFR Sequence for Combined Read - -```c title="Combined Write-Read Implementation" -// Phase 1: Write register address -I2CS |= bmSTART; // Generate START -I2DAT = 0x10; // Write: device addr + W -// wait bmDONE, check bmACK -I2DAT = 0xA2; // Write: register address -// wait bmDONE, check bmACK - -// Phase 2: Read with repeated START -I2CS |= bmSTART; // Generate REPEATED START (no STOP first!) -I2DAT = 0x11; // Write: device addr + R -// wait bmDONE, check bmACK -I2CS |= bmLASTRD; // Signal this is the last read byte -tmp = I2DAT; // Dummy read (triggers first clock burst) -// wait bmDONE -I2CS |= bmSTOP; // Generate STOP after reading -data = I2DAT; // Read actual data byte -// wait bmSTOP to clear -``` - - - -## Timeout Protection - -The fx2lib I2C functions poll `bmDONE` with no timeout: - -```c title="fx2lib Original Code (Vulnerable)" -while (!(I2CS & bmDONE) && !cancel_i2c_trans); -``` - -Since `cancel_i2c_trans` is never set during normal operation, these loops are effectively infinite. If a slave holds SCL low or a bus error prevents bmDONE from asserting, the firmware hangs. - -The custom firmware replaces all fx2lib I2C functions with timeout-protected wrappers: - -```c title="Timeout-Protected I2C Wait" -#define I2C_TIMEOUT 6000 - -static BOOL i2c_wait_done(void) { - WORD timeout = I2C_TIMEOUT; - while (!(I2CS & bmDONE)) { - if (--timeout == 0) return FALSE; - } - return TRUE; -} -``` - -A WORD counter of 6000 decremented in a tight SDCC-compiled loop at 48 MHz gives approximately 5--10 ms per wait. At 400 kHz I2C, a single byte transfer takes 22.5 us, so the timeout provides over 200x margin for normal operations while bounding the worst case. +--- +title: I2C Bus Architecture +description: FX2 I2C controller details, bus topology, device addresses, and the combined write-read protocol. +--- + +import { Aside, Steps } from '@astrojs/starlight/components'; + +The SkyWalker-1 uses a single I2C bus connecting the FX2 microcontroller (master) to three slave devices. The FX2's hardware I2C controller handles all bus transactions through XRAM-mapped registers. + +## FX2 I2C Controller + +| SFR | Address | Function | +|-----|---------|----------| +| I2CS | 0xE678 (XRAM) | I2C control/status register | +| I2DAT | 0xE679 (XRAM) | I2C data register | +| I2CTL | 0xE67A (XRAM) | I2C control (speed selection) | + +### I2CS Control/Status Bits + +| Bit | Name | Function | +|-----|------|----------| +| bmSTART | bit 7 | Write: initiate START condition | +| bmSTOP | bit 6 | Write: initiate STOP condition | +| bmLASTRD | bit 5 | Write: signal last read byte (NACK after next read) | +| bmDONE | bit 2 | Read: byte transfer complete | +| bmACK | bit 1 | Read: ACK received from slave | +| bmBERR | bit 0 | Read: bus error detected | + +## Bus Speed + +The I2C bus operates at 400 kHz. The speed is set through two mechanisms: + +1. **C2 EEPROM header** -- Config byte at offset 7 = 0x40, which the FX2 boot ROM uses to configure the I2C speed during initial EEPROM read. +2. **Firmware/software** -- I2CTL = `bm400KHZ` (written by the custom firmware and init table). + +## Known Bus Devices + +| 7-bit Address | Wire Write | Wire Read | Identity | +|---------------|-----------|----------|----------| +| 0x08 | 0x10 | 0x11 | BCM4500 demodulator | +| 0x10 | 0x20 | 0x21 | Tuner or LNB controller | +| 0x51 | 0xA2 | 0xA3 | Configuration EEPROM (24Cxx-family) | + +These addresses were confirmed via the I2C_BUS_SCAN command (0xB4) in custom firmware v3.01.0, which probes all 7-bit addresses from 0x01 to 0x77. + +### BCM4500 Demodulator (0x08) + +The primary device for all demodulation operations. Register access uses the [indirect register protocol](/bcm4500/demodulator/) through registers 0xA6, 0xA7, and 0xA8. Direct registers 0xA2, 0xA4, and 0xF9 provide status information. + +The v2.13 firmware also probes alternate addresses 0x7F and 0x3F at startup to detect which demodulator variant is present. + +### Tuner/LNB Controller (0x10) + +Likely a tuner IC or LNB controller. In normal operation, the BCM4500 accesses this device internally for tuning. It is also directly addressable on the shared I2C bus. The kernel driver does not directly communicate with this device. + +### Configuration EEPROM (0x51) + +A 24Cxx-family serial EEPROM that stores: +- Device serial number (read by GET_SERIAL_NUMBER, 0x93) +- Hardware platform ID (read by GET_FPGA_VERS, 0x95) +- Calibration data + +## Bus Topology + +``` + FX2 I2C Master + (I2CS/I2DAT/I2CTL) + | + +----------+-----------+ + | | | + BCM4500 Tuner/LNB EEPROM + (0x08) (0x10) (0x51) +``` + +All three devices share the same SCL/SDA lines. The FX2's hardware I2C controller manages bus arbitration and clock stretching detection. + +## Combined Write-Read Protocol + +All BCM4500 register reads use the I2C combined write-read protocol with a repeated START condition. This is required because the BCM4500 uses a register-addressed protocol where the register number must be sent in a write phase before the read phase. + +``` +Complete I2C transaction for reading register 0xA2 from device 0x08: + + Phase 1 (Write): + [S] [0x10] [ACK] [0xA2] [ACK] + | | | | | + | | | | +-- BCM4500 ACKs register address + | | | +--------- Register address byte + | | +---------------- BCM4500 ACKs its device address + | +----------------------- Device address (0x08 << 1) = 0x10 (write) + +---------------------------- START condition + + Phase 2 (Read with Repeated START): + [Sr] [0x11] [ACK] [DATA] [NACK] [P] + | | | | | | + | | | | | +-- STOP condition + | | | | +--------- Master NACKs (last byte) + | | | +---------------- Register data + | | +----------------------- BCM4500 ACKs its device address + | +------------------------------ Device address (0x08 << 1 | 1) = 0x11 (read) + +------------------------------------ REPEATED START (no STOP between phases) +``` + +The repeated START (Sr) is essential. A STOP between phases would release the bus, and the BCM4500 would lose the register address context. + +### FX2 SFR Sequence for Combined Read + +```c title="Combined Write-Read Implementation" +// Phase 1: Write register address +I2CS |= bmSTART; // Generate START +I2DAT = 0x10; // Write: device addr + W +// wait bmDONE, check bmACK +I2DAT = 0xA2; // Write: register address +// wait bmDONE, check bmACK + +// Phase 2: Read with repeated START +I2CS |= bmSTART; // Generate REPEATED START (no STOP first!) +I2DAT = 0x11; // Write: device addr + R +// wait bmDONE, check bmACK +I2CS |= bmLASTRD; // Signal this is the last read byte +tmp = I2DAT; // Dummy read (triggers first clock burst) +// wait bmDONE +I2CS |= bmSTOP; // Generate STOP after reading +data = I2DAT; // Read actual data byte +// wait bmSTOP to clear +``` + + + +## Timeout Protection + +The fx2lib I2C functions poll `bmDONE` with no timeout: + +```c title="fx2lib Original Code (Vulnerable)" +while (!(I2CS & bmDONE) && !cancel_i2c_trans); +``` + +Since `cancel_i2c_trans` is never set during normal operation, these loops are effectively infinite. If a slave holds SCL low or a bus error prevents bmDONE from asserting, the firmware hangs. + +The custom firmware replaces all fx2lib I2C functions with timeout-protected wrappers: + +```c title="Timeout-Protected I2C Wait" +#define I2C_TIMEOUT 6000 + +static BOOL i2c_wait_done(void) { + WORD timeout = I2C_TIMEOUT; + while (!(I2CS & bmDONE)) { + if (--timeout == 0) return FALSE; + } + return TRUE; +} +``` + +A WORD counter of 6000 decremented in a tight SDCC-compiled loop at 48 MHz gives approximately 5--10 ms per wait. At 400 kHz I2C, a single byte transfer takes 22.5 us, so the timeout provides over 200x margin for normal operations while bounding the worst case. diff --git a/site/src/content/docs/i2c/stop-corruption-bug.mdx b/site/src/content/docs/i2c/stop-corruption-bug.mdx index 3c80882..50101a1 100644 --- a/site/src/content/docs/i2c/stop-corruption-bug.mdx +++ b/site/src/content/docs/i2c/stop-corruption-bug.mdx @@ -1,167 +1,167 @@ ---- -title: I2C STOP Corruption Bug -description: Root cause analysis of the spurious I2C STOP condition that corrupted the FX2 controller state during boot. ---- - -import { Steps, Aside, Badge } from '@astrojs/starlight/components'; - -During development of the custom firmware v3.01.0, the BOOT_8PSK (0x89) command caused the FX2 to hang for over 10 seconds, making the USB device completely unresponsive. The root cause was traced to a single line of code: a spurious I2C STOP condition issued when no transaction was active. - - - -## The Problem - -The boot function originally included a "bus reset" step before any I2C communication: - -```c title="Broken Code" -I2CS |= bmSTOP; -i2c_wait_stop(); -``` - -This pattern appears in various FX2 example code and seems reasonable -- send a STOP to ensure the I2C bus is in a known idle state before starting fresh. On the FX2's I2C controller hardware, this is incorrect. - -## Root Cause Analysis - -The root cause was discovered through a series of incremental debug modes added to the BOOT_8PSK handler. Each mode executes a subset of the full boot sequence, isolating which step introduces the failure. - -### Debug Mode Results - -| wValue | Action | Result | Key Observation | -|--------|--------|--------|-----------------| -| 0x80 | No-op: return status only | | Baseline | -| 0x81 | GPIO + power + delays (no I2C) | | Power sequencing is correct | -| 0x82 | GPIO + power + bmSTOP + I2C probe | | bmSTOP corrupts I2C | -| 0x83 | GPIO + power + bmSTOP + probe + init | | Same root cause | -| 0x84 | I2C probe only (chip already powered) | | BCM4500 is alive | -| 0x85 | GPIO + power + probe (**no bmSTOP**) | | Confirms bmSTOP is the cause | - -### Three Key Observations - -1. **Mode 0x82 fails but mode 0x85 succeeds.** These modes are identical except that 0x82 issues `I2CS |= bmSTOP` before the probe and 0x85 does not. The bmSTOP is the only difference. - -2. **Mode 0x84 succeeds immediately after 0x82 fails.** Mode 0x84 performs a plain I2C combined read with no GPIO manipulation or bus reset. If called after a failed 0x82, it succeeds. This proves the BCM4500 was alive and responding -- the FX2 I2C controller was in a bad state, not the bus or the slave. - -3. **Raw I2C reads via command 0xB5 succeed after 0x82 fails.** Command 0xB5 uses the same `i2c_combined_read` function. Running it from the host after a failed 0x82 returns valid data from the BCM4500. - -## What Happens Inside the FX2 - -The FX2's I2C master controller is a hardware peripheral accessed through the I2CS, I2DAT, and I2CTL SFRs. The controller implements an I2C state machine in silicon. Writing bmSTOP to I2CS instructs the hardware to generate a STOP condition (SDA rising while SCL is high). - -When no I2C transaction is active -- no prior START has been issued, and the bus is idle -- writing bmSTOP puts the controller into an inconsistent internal state: - -- The bmSTOP bit may not clear properly (it is supposed to self-clear when the STOP condition completes) -- Subsequent START conditions fail to generate proper clock sequences -- ACK detection from slaves becomes unreliable - -The Cypress TRM describes STOP as a step that follows a completed read or write transaction. It is not documented as a standalone bus-reset mechanism. - -## The Fix - -The fix is a single deletion. Remove the spurious STOP from the boot sequence: - -```c title="Before (Broken)" -/* "Reset" I2C bus */ -I2CS |= bmSTOP; -i2c_wait_stop(); -``` - -```c title="After (Correct)" -/* NOTE: Do NOT send I2CS bmSTOP here. Sending STOP when no - * transaction is active corrupts the FX2 I2C controller state, - * causing subsequent START+ACK detection to fail. The I2C bus - * will be in a clean state when we reach the probe step -- - * any prior transaction ended with STOP. */ -``` - -The correct approach is to simply proceed with a new START condition. If the bus is idle (after power-on or after the previous transaction completed normally), the START succeeds and the controller enters its normal operating state. The hardware handles bus arbitration automatically. - -## Corrected Boot Sequence - -```c title="bcm4500_boot() -- Corrected" -static BOOL bcm4500_boot(void) { - boot_stage = 1; - cancel_i2c_trans = FALSE; - - /* P3.7, P3.6, P3.5 HIGH (idle state for control lines) */ - IOD |= 0xE0; - - /* Assert BCM4500 hardware RESET (P0.5 LOW) */ - OEA |= PIN_BCM_RESET; - IOA &= ~PIN_BCM_RESET; - - /* No I2CS bmSTOP here -- see note above */ - - /* Power on: P0.1 HIGH (enable), P0.2 LOW (disable off) */ - OEA |= (PIN_PWR_EN | PIN_PWR_DIS); - IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN; - - boot_stage = 2; - delay(30); /* power settle */ - - IOA |= PIN_BCM_RESET; /* release reset */ - delay(50); /* BCM4500 POR + mask ROM boot */ - - boot_stage = 3; - /* I2C probe -- if this fails, the chip didn't respond */ - if (!bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) - return FALSE; - - /* ... register init blocks follow ... */ -} -``` - -## Boot Results After Fix - -| Metric | Value | -|--------|-------| -| Boot time | ~90 ms total | -| config_status | 0x03 (STARTED + FW_LOADED) | -| boot_stage | 0xFF (COMPLETE) | -| Direct registers 0xA2-0xA8 | All return 0x02 (powered, not locked) | -| Signal lock | 0x00 (no lock -- dish not aimed) | -| USB responsiveness | No hang; fully responsive throughout | - -## Test Scripts - -The investigation was driven by a series of test scripts in the `tools/` directory: - -| Script | Purpose | -|--------|---------| -| `test_boot_debug.py` | Sends debug modes 0x80--0x83 sequentially | -| `test_i2c_debug.py` | Powers on via 0x81, runs bus scans, tests probe timing | -| `test_i2c_isolate.py` | Tests whether re-reset or insufficient delay causes failure | -| `test_i2c_pinpoint.py` | The definitive test: compares modes 0x84, 0x85, and 0x82 | - -## Timeout Protection - -Even with the bmSTOP fix, timeout protection on all I2C operations is essential. The FX2's I2C controller has no hardware timeout -- if a slave holds SCL low (clock stretching) or a fault prevents bmDONE from asserting, the firmware spins forever. - -The custom firmware replaces all fx2lib I2C functions with timeout-protected wrappers: - -```c title="Timeout-Protected I2C Waits" -#define I2C_TIMEOUT 6000 - -static BOOL i2c_wait_done(void) { - WORD timeout = I2C_TIMEOUT; - while (!(I2CS & bmDONE)) { - if (--timeout == 0) return FALSE; - } - return TRUE; -} - -static BOOL i2c_wait_stop(void) { - WORD timeout = I2C_TIMEOUT; - while (I2CS & bmSTOP) { - if (--timeout == 0) return FALSE; - } - return TRUE; -} -``` - -A WORD counter of 6000 at 48 MHz gives approximately 5--10 ms per wait, providing over 200x margin above the 22.5 us required for a single byte transfer at 400 kHz. - - +--- +title: I2C STOP Corruption Bug +description: Root cause analysis of the spurious I2C STOP condition that corrupted the FX2 controller state during boot. +--- + +import { Steps, Aside, Badge } from '@astrojs/starlight/components'; + +During development of the custom firmware v3.01.0, the BOOT_8PSK (0x89) command caused the FX2 to hang for over 10 seconds, making the USB device completely unresponsive. The root cause was traced to a single line of code: a spurious I2C STOP condition issued when no transaction was active. + + + +## The Problem + +The boot function originally included a "bus reset" step before any I2C communication: + +```c title="Broken Code" +I2CS |= bmSTOP; +i2c_wait_stop(); +``` + +This pattern appears in various FX2 example code and seems reasonable -- send a STOP to ensure the I2C bus is in a known idle state before starting fresh. On the FX2's I2C controller hardware, this is incorrect. + +## Root Cause Analysis + +The root cause was discovered through a series of incremental debug modes added to the BOOT_8PSK handler. Each mode executes a subset of the full boot sequence, isolating which step introduces the failure. + +### Debug Mode Results + +| wValue | Action | Result | Key Observation | +|--------|--------|--------|-----------------| +| 0x80 | No-op: return status only | | Baseline | +| 0x81 | GPIO + power + delays (no I2C) | | Power sequencing is correct | +| 0x82 | GPIO + power + bmSTOP + I2C probe | | bmSTOP corrupts I2C | +| 0x83 | GPIO + power + bmSTOP + probe + init | | Same root cause | +| 0x84 | I2C probe only (chip already powered) | | BCM4500 is alive | +| 0x85 | GPIO + power + probe (**no bmSTOP**) | | Confirms bmSTOP is the cause | + +### Three Key Observations + +1. **Mode 0x82 fails but mode 0x85 succeeds.** These modes are identical except that 0x82 issues `I2CS |= bmSTOP` before the probe and 0x85 does not. The bmSTOP is the only difference. + +2. **Mode 0x84 succeeds immediately after 0x82 fails.** Mode 0x84 performs a plain I2C combined read with no GPIO manipulation or bus reset. If called after a failed 0x82, it succeeds. This proves the BCM4500 was alive and responding -- the FX2 I2C controller was in a bad state, not the bus or the slave. + +3. **Raw I2C reads via command 0xB5 succeed after 0x82 fails.** Command 0xB5 uses the same `i2c_combined_read` function. Running it from the host after a failed 0x82 returns valid data from the BCM4500. + +## What Happens Inside the FX2 + +The FX2's I2C master controller is a hardware peripheral accessed through the I2CS, I2DAT, and I2CTL SFRs. The controller implements an I2C state machine in silicon. Writing bmSTOP to I2CS instructs the hardware to generate a STOP condition (SDA rising while SCL is high). + +When no I2C transaction is active -- no prior START has been issued, and the bus is idle -- writing bmSTOP puts the controller into an inconsistent internal state: + +- The bmSTOP bit may not clear properly (it is supposed to self-clear when the STOP condition completes) +- Subsequent START conditions fail to generate proper clock sequences +- ACK detection from slaves becomes unreliable + +The Cypress TRM describes STOP as a step that follows a completed read or write transaction. It is not documented as a standalone bus-reset mechanism. + +## The Fix + +The fix is a single deletion. Remove the spurious STOP from the boot sequence: + +```c title="Before (Broken)" +/* "Reset" I2C bus */ +I2CS |= bmSTOP; +i2c_wait_stop(); +``` + +```c title="After (Correct)" +/* NOTE: Do NOT send I2CS bmSTOP here. Sending STOP when no + * transaction is active corrupts the FX2 I2C controller state, + * causing subsequent START+ACK detection to fail. The I2C bus + * will be in a clean state when we reach the probe step -- + * any prior transaction ended with STOP. */ +``` + +The correct approach is to simply proceed with a new START condition. If the bus is idle (after power-on or after the previous transaction completed normally), the START succeeds and the controller enters its normal operating state. The hardware handles bus arbitration automatically. + +## Corrected Boot Sequence + +```c title="bcm4500_boot() -- Corrected" +static BOOL bcm4500_boot(void) { + boot_stage = 1; + cancel_i2c_trans = FALSE; + + /* P3.7, P3.6, P3.5 HIGH (idle state for control lines) */ + IOD |= 0xE0; + + /* Assert BCM4500 hardware RESET (P0.5 LOW) */ + OEA |= PIN_BCM_RESET; + IOA &= ~PIN_BCM_RESET; + + /* No I2CS bmSTOP here -- see note above */ + + /* Power on: P0.1 HIGH (enable), P0.2 LOW (disable off) */ + OEA |= (PIN_PWR_EN | PIN_PWR_DIS); + IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN; + + boot_stage = 2; + delay(30); /* power settle */ + + IOA |= PIN_BCM_RESET; /* release reset */ + delay(50); /* BCM4500 POR + mask ROM boot */ + + boot_stage = 3; + /* I2C probe -- if this fails, the chip didn't respond */ + if (!bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) + return FALSE; + + /* ... register init blocks follow ... */ +} +``` + +## Boot Results After Fix + +| Metric | Value | +|--------|-------| +| Boot time | ~90 ms total | +| config_status | 0x03 (STARTED + FW_LOADED) | +| boot_stage | 0xFF (COMPLETE) | +| Direct registers 0xA2-0xA8 | All return 0x02 (powered, not locked) | +| Signal lock | 0x00 (no lock -- dish not aimed) | +| USB responsiveness | No hang; fully responsive throughout | + +## Test Scripts + +The investigation was driven by a series of test scripts in the `tools/` directory: + +| Script | Purpose | +|--------|---------| +| `test_boot_debug.py` | Sends debug modes 0x80--0x83 sequentially | +| `test_i2c_debug.py` | Powers on via 0x81, runs bus scans, tests probe timing | +| `test_i2c_isolate.py` | Tests whether re-reset or insufficient delay causes failure | +| `test_i2c_pinpoint.py` | The definitive test: compares modes 0x84, 0x85, and 0x82 | + +## Timeout Protection + +Even with the bmSTOP fix, timeout protection on all I2C operations is essential. The FX2's I2C controller has no hardware timeout -- if a slave holds SCL low (clock stretching) or a fault prevents bmDONE from asserting, the firmware spins forever. + +The custom firmware replaces all fx2lib I2C functions with timeout-protected wrappers: + +```c title="Timeout-Protected I2C Waits" +#define I2C_TIMEOUT 6000 + +static BOOL i2c_wait_done(void) { + WORD timeout = I2C_TIMEOUT; + while (!(I2CS & bmDONE)) { + if (--timeout == 0) return FALSE; + } + return TRUE; +} + +static BOOL i2c_wait_stop(void) { + WORD timeout = I2C_TIMEOUT; + while (I2CS & bmSTOP) { + if (--timeout == 0) return FALSE; + } + return TRUE; +} +``` + +A WORD counter of 6000 at 48 MHz gives approximately 5--10 ms per wait, providing over 200x margin above the 22.5 us required for a single byte transfer at 400 kHz. + + diff --git a/site/src/content/docs/index.mdx b/site/src/content/docs/index.mdx index f641752..f4c7818 100644 --- a/site/src/content/docs/index.mdx +++ b/site/src/content/docs/index.mdx @@ -1,83 +1,83 @@ ---- -title: SkyWalker-1 Documentation -description: Reverse-engineered technical documentation for the Genpix SkyWalker-1 DVB-S USB 2.0 satellite receiver. -template: splash -hero: - title: SkyWalker-1 - tagline: Open-source reverse-engineered documentation for the Genpix SkyWalker-1 DVB-S USB 2.0 satellite receiver - image: - file: ../../assets/skywalker-1-hero.jpg - alt: Genpix Electronics SkyWalker-1 DVB-S USB satellite receiver - actions: - - text: Get Started - link: /hardware/overview/ - icon: right-arrow - - text: Master Reference - link: /reference/master-reference/ - variant: minimal ---- - -import { CardGrid, Card, Aside } from '@astrojs/starlight/components'; - -The Genpix SkyWalker-1 is a standalone USB 2.0 DVB-S satellite receiver built around a Cypress FX2LP microcontroller and Broadcom BCM4500 demodulator. This project documents its complete internal architecture through Linux kernel driver analysis, Ghidra firmware reverse engineering across five firmware images, Windows BDA driver source review, and custom firmware development. - -The result is a fully open-source technical reference covering every vendor command, register, GPIO pin, and data path -- from the USB control transfers down to the I2C bit timing on the BCM4500's indirect register protocol. - -**Supported modulations:** DVB-S (QPSK), Turbo QPSK, Turbo 8PSK, Turbo 16QAM, Digicipher II (Combo, Split I/Q, Offset QPSK), DSS (QPSK), and DVB-S BPSK. Symbol rates from 256 Ksps to 30 Msps across a 950--2150 MHz IF range. - - - -## Explore the Documentation - - - - FX2LP + BCM4500 architecture, GPIO pin maps, RF specifications, and board block diagram. - - [Hardware Overview](/hardware/overview/) - - - Vendor command reference, boot sequence, configuration status byte, and endpoint layout. - - [USB Interface](/usb/interface/) - - - Demodulator I2C protocol, indirect register access, tuning sequence, and GPIF streaming path. - - [Demodulator](/bcm4500/demodulator/) - - - LNB voltage and tone control, DiSEqC 1.0/1.2 protocol, legacy Dish Network switches. - - [LNB Control](/lnb-diseqc/lnb-control/) - - - Version comparison across five firmware images, custom v3.01.0 development, and storage formats. - - [Firmware Versions](/firmware/version-comparison/) - - - Python utilities for RAM loading, EEPROM flashing, transponder tuning, transport stream analysis, and hardware debugging. - - [Firmware Loader](/tools/firmware-loader/) - - - -## Project Scope - -This documentation covers the complete reverse engineering of the SkyWalker-1 hardware and firmware: - -- **5 firmware images** decompiled in Ghidra: v2.06.04, Rev.2 v2.10.04, and three v2.13 sub-variants -- **30 vendor USB commands** fully decoded with parameter formats and version-specific behavior -- **Custom firmware** (v3.01.0) built from scratch with SDCC + fx2lib, including 7 new diagnostic commands -- **13 Python tools** for firmware management, satellite tuning, transport stream analysis, and hardware debugging -- **I2C STOP corruption bug** discovered and fixed through incremental debug modes on the FX2 controller - -All source code, firmware binaries, analysis reports, and this documentation are available in the project repository. - -## Support Genpix and the Leleka Foundation - -Genpix Electronics is selling their remaining SkyWalker-1 inventory on eBay, with all net proceeds going to the [Leleka Foundation](https://www.leleka.org/) -- a US-based charitable organization providing medical and social support to individuals wounded and displaced by the conflict in Ukraine. - -If you'd like to own one of these capable little receivers and support a good cause at the same time, check the [eBay listing from the official Genpix seller](https://www.ebay.com/itm/196142001978). New-in-box units ship via USPS Priority Mail. Thank you to Genpix for building the SkyWalker-1 and for directing their remaining stock toward something meaningful. +--- +title: SkyWalker-1 Documentation +description: Reverse-engineered technical documentation for the Genpix SkyWalker-1 DVB-S USB 2.0 satellite receiver. +template: splash +hero: + title: SkyWalker-1 + tagline: Open-source reverse-engineered documentation for the Genpix SkyWalker-1 DVB-S USB 2.0 satellite receiver + image: + file: ../../assets/skywalker-1-hero.jpg + alt: Genpix Electronics SkyWalker-1 DVB-S USB satellite receiver + actions: + - text: Get Started + link: /hardware/overview/ + icon: right-arrow + - text: Master Reference + link: /reference/master-reference/ + variant: minimal +--- + +import { CardGrid, Card, Aside } from '@astrojs/starlight/components'; + +The Genpix SkyWalker-1 is a standalone USB 2.0 DVB-S satellite receiver built around a Cypress FX2LP microcontroller and Broadcom BCM4500 demodulator. This project documents its complete internal architecture through Linux kernel driver analysis, Ghidra firmware reverse engineering across five firmware images, Windows BDA driver source review, and custom firmware development. + +The result is a fully open-source technical reference covering every vendor command, register, GPIO pin, and data path -- from the USB control transfers down to the I2C bit timing on the BCM4500's indirect register protocol. + +**Supported modulations:** DVB-S (QPSK), Turbo QPSK, Turbo 8PSK, Turbo 16QAM, Digicipher II (Combo, Split I/Q, Offset QPSK), DSS (QPSK), and DVB-S BPSK. Symbol rates from 256 Ksps to 30 Msps across a 950--2150 MHz IF range. + + + +## Explore the Documentation + + + + FX2LP + BCM4500 architecture, GPIO pin maps, RF specifications, and board block diagram. + + [Hardware Overview](/hardware/overview/) + + + Vendor command reference, boot sequence, configuration status byte, and endpoint layout. + + [USB Interface](/usb/interface/) + + + Demodulator I2C protocol, indirect register access, tuning sequence, and GPIF streaming path. + + [Demodulator](/bcm4500/demodulator/) + + + LNB voltage and tone control, DiSEqC 1.0/1.2 protocol, legacy Dish Network switches. + + [LNB Control](/lnb-diseqc/lnb-control/) + + + Version comparison across five firmware images, custom v3.01.0 development, and storage formats. + + [Firmware Versions](/firmware/version-comparison/) + + + Python utilities for RAM loading, EEPROM flashing, transponder tuning, transport stream analysis, and hardware debugging. + + [Firmware Loader](/tools/firmware-loader/) + + + +## Project Scope + +This documentation covers the complete reverse engineering of the SkyWalker-1 hardware and firmware: + +- **5 firmware images** decompiled in Ghidra: v2.06.04, Rev.2 v2.10.04, and three v2.13 sub-variants +- **30 vendor USB commands** fully decoded with parameter formats and version-specific behavior +- **Custom firmware** (v3.01.0) built from scratch with SDCC + fx2lib, including 7 new diagnostic commands +- **13 Python tools** for firmware management, satellite tuning, transport stream analysis, and hardware debugging +- **I2C STOP corruption bug** discovered and fixed through incremental debug modes on the FX2 controller + +All source code, firmware binaries, analysis reports, and this documentation are available in the project repository. + +## Support Genpix and the Leleka Foundation + +Genpix Electronics is selling their remaining SkyWalker-1 inventory on eBay, with all net proceeds going to the [Leleka Foundation](https://www.leleka.org/) -- a US-based charitable organization providing medical and social support to individuals wounded and displaced by the conflict in Ukraine. + +If you'd like to own one of these capable little receivers and support a good cause at the same time, check the [eBay listing from the official Genpix seller](https://www.ebay.com/itm/196142001978). New-in-box units ship via USPS Priority Mail. Thank you to Genpix for building the SkyWalker-1 and for directing their remaining stock toward something meaningful. diff --git a/site/src/content/docs/lnb-diseqc/diseqc-protocol.mdx b/site/src/content/docs/lnb-diseqc/diseqc-protocol.mdx index 3684736..d0afc42 100644 --- a/site/src/content/docs/lnb-diseqc/diseqc-protocol.mdx +++ b/site/src/content/docs/lnb-diseqc/diseqc-protocol.mdx @@ -1,273 +1,273 @@ ---- -title: DiSEqC Protocol -description: DiSEqC 1.0/1.2 implementation with Manchester-encoded GPIO bit-bang, timing analysis, and command framing. ---- - -import { Steps, Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; - -The SkyWalker-1 implements DiSEqC (Digital Satellite Equipment Control) via Timer2-based GPIO bit-bang on the FX2 microcontroller. The algorithm is identical across all firmware versions -- only the data pin assignment changes per PCB revision. - -[Download DiSEqC PDF](/downloads/DiSEqC%20for%20the%20Skywalker-1.pdf) - -## Protocol Diagrams - -The following diagrams document the Genpix BDA driver's DiSEqC extended property interface: - -![DiSEqC Protocol Diagram Page 1](../../../assets/diagrams/DiSEqC%20for%20the%20Skywalker-1_page_1.svg) - -![DiSEqC Protocol Diagram Page 2](../../../assets/diagrams/DiSEqC%20for%20the%20Skywalker-1_page_2.svg) - -## Signal Architecture - -The firmware does **not** generate the 22 kHz carrier directly. GPIO P0.3 gates an external 22 kHz oscillator circuit on the PCB: - -``` -FX2 Firmware External Hardware Coax Cable -+------------------+ +--------------------+ +------------------+ -| P0.3 (carrier) |---->| 22 kHz oscillator |---->| LNB power line | -| (enable/disable) | | (gated by P0.3) | | (13V/18V + tone) | -| | | | | | -| P0.x (data bit) | | (internal firmware | | | -| (firmware only) | | logic only) | | | -+------------------+ +--------------------+ +------------------+ -``` - -The data pin (P0.7 / P0.4 / P0.0 depending on version) is used only internally by the firmware's Manchester encoding logic. It controls whether the carrier gate signal is cut short or held for the full bit period. - -## Timer2 Configuration - -All firmware versions configure Timer2 identically during USB descriptor setup: - -| Parameter | Value | Notes | -|-----------|-------|-------| -| T2CON | `0x04` | Auto-reload mode, timer running | -| RCAP2H | `0xF8` | Reload high byte | -| RCAP2L | `0x2F` | Reload low byte (reload = 63535) | -| CKCON.T2M | `0` | Timer2 clock = 48 MHz / 12 = 4 MHz | - -**Tick period calculation:** - -``` -FX2 master clock = 48 MHz -CKCON.T2M = 0 -> Timer2 clock = 48 MHz / 12 = 4 MHz -Count per overflow = 65536 - 63535 = 2001 -Tick period = 2001 / 4,000,000 = 500.25 us ~ 500 us -Tick frequency ~ 2.0 kHz -``` - -Timer2 runs continuously from power-on and is never stopped or reconfigured. It serves as a stable 500 us timebase for all DiSEqC operations. - -## Manchester Encoding - -Each DiSEqC bit consists of 3 Timer2 ticks (3 x 500 us = 1.5 ms): - - - -**Data '0'** -- 2/3 tone, 1/3 silence (1.0 ms carrier + 0.5 ms silence): - -``` - Tick 1 Tick 2 Tick 3 - (500 us) (500 us) (500 us) -P0.3: _____|========|========|________| - ^tone ON ^tone OFF - (setup gap) (1.0 ms carrier) (0.5 ms silence) -``` - - -**Data '1'** -- 1/3 tone, 2/3 silence (0.5 ms carrier + 1.0 ms silence): - -``` - Tick 1 Tick 2 Tick 3 - (500 us) (500 us) (500 us) -P0.3: _____|========|________|________| - ^tone ON ^tone OFF early - (setup gap) (0.5 ms carrier) (1.0 ms silence) -``` - - - -### Bit Symbol Implementation - -Decompiled from Rev.2 `FUN_CODE_213c`: - -```c title="DiSEqC bit symbol function" -void diseqc_bit_symbol(void) { - wait_TF2(); // Tick 1: inter-bit gap (500 us) - P0 |= 0x08; // P0.3 = 1 -> 22 kHz carrier ON - wait_TF2(); // Tick 2: carrier period (500 us) - if (data_pin != 0) { // If data = '1': - P0 &= 0xF7; // P0.3 = 0 -> carrier OFF (short pulse) - } - wait_TF2(); // Tick 3: final period (500 us) - P0 &= 0xF7; // P0.3 = 0 -> carrier always OFF at end -} -``` - -The `wait_TF2()` function is the lowest-level timing primitive -- a busy-wait on the Timer2 overflow flag: - -```c title="Timer2 tick wait (identical across all versions)" -void wait_TF2(void) { - while (TF2 == 0) {} // Poll Timer2 overflow flag - TF2 = 0; // Clear flag for next tick -} -``` - -## Byte Transmission - -Each DiSEqC byte is 9 bits: 8 data bits (MSB first) + 1 odd parity bit. - -```c title="DiSEqC byte transmit (from Rev.2 FUN_CODE_07d1)" -void diseqc_send_byte(char first_byte, byte data) { - byte ones_count = 0; - if (first_byte == 0) TF2 = 0; // Sync timer on first byte - - for (char i = 8; i > 0; i--) { // 8 bits, MSB first - if (data & 0x80) { - data_pin = 1; // Set data = '1' - diseqc_bit_symbol(); - ones_count++; - } else { - data_pin = 0; // Set data = '0' - diseqc_bit_symbol(); - } - data <<= 1; // Next bit - } - - data_pin = ~ones_count & 1; // Odd parity: '1' if even count - diseqc_bit_symbol(); // Transmit parity bit -} -``` - -**Timing per byte:** 9 bits x 1.5 ms = 13.5 ms - -## DiSEqC Command Sequence - -The full command flow for sending a DiSEqC message via vendor command `0x8D` (`SEND_DISEQC_COMMAND`): - - - -1. **Read wLength** from USB SETUP packet (`0xE6BE`) to determine message byte count - -2. **Disable 22 kHz carrier** by clearing P0.3 (ensuring a clean start state) - -3. **Pre-transmission delay** of 15 Timer2 ticks (7.5 ms) for LNB voltage settling - -4. **Transmit message bytes** if wLength > 0: iterate through EP0BUF, sending each byte via Manchester-encoded bit-bang (8 data bits + odd parity, 3 Timer2 ticks per bit) - -5. **Or send tone burst** if wLength == 0: - - `wValue == 0`: Tone Burst A (25 Timer2 ticks = 12.5 ms of unmodulated carrier) - - `wValue != 0`: Tone Burst B (transmitted as `0xFF` byte pattern through the bit-bang function) - - - -## USB Command Format - -### Full DiSEqC Message (3-6 bytes) - -``` -USB SETUP: - bmRequestType = 0x40 (vendor, host-to-device) - bRequest = 0x8D (SEND_DISEQC_COMMAND) - wValue = msg[0] (framing byte, typically 0xE0 or 0xE1) - wIndex = 0x0000 - wLength = message length (3-6) - -EP0 Data: - Byte 0: Framing byte (e.g., 0xE0 = command from master, no reply expected) - Byte 1: Address byte (e.g., 0x10 = any LNB, 0x11 = LNB 1) - Byte 2: Command byte (e.g., 0x38 = Write N0, committed switch port) - Byte 3: Data byte 0 (optional, port selection bits) - Byte 4: Data byte 1 (optional) - Byte 5: Data byte 2 (optional) -``` - -### Tone Burst (Mini DiSEqC) - -``` -USB SETUP: - bmRequestType = 0x40 - bRequest = 0x8D - wValue = 0x00 (Burst A) or 0x01 (Burst B) - wIndex = 0x0000 - wLength = 0 (zero length signals tone burst mode) -``` - -## Windows BDA Driver Interface - -The Windows driver exposes DiSEqC through a BDA extended property GUID: - -```c title="BDA Extended Property GUID" -// {0B5221EB-F4C4-4976-B959-EF74427464D9} -#define STATIC_KSPROPSETID_BdaExtendedProperty \ - 0x0B5221EB, 0xF4C4, 0x4976, 0xB9, 0x59, 0xEF, 0x74, 0x42, 0x74, 0x64, 0xD9 -``` - -The DiSEqC command structure: - -```c title="DISEQC_COMMAND structure" -typedef enum enSimpleToneBurst { - SEC_MINI_A, - SEC_MINI_B -} SIMPLE_TONE_BURST; - -typedef struct __DISEQC_COMMAND { - UCHAR ucMessage[6]; // Framing, Address, Command, Data[0..2] - UCHAR ucMessageLength; // 3-6 for DiSEqC; 1 for tone burst -} DISEQC_COMMAND; -``` - -For tone burst commands, set `ucMessageLength = 1` and `ucMessage[0]` to either `SEC_MINI_A` (0x00) or `SEC_MINI_B` (0x01). - -## Data Pin Assignment Per Version - -The DiSEqC algorithm is identical across all firmware versions. Only the data pin changes per PCB revision: - -| Firmware Version | Data Pin | Carrier Pin | Byte Transmit Function | Bit Symbol Function | Timer Wait | -|------------------|----------|-------------|----------------------|--------------------|-----------:| -| v2.06 | **P0.7** | P0.3 | `0x2098` | `0x23B5` | `0x24C6` | -| Rev.2 v2.10 | **P0.4** | P0.3 | `FUN_CODE_07d1` | `FUN_CODE_213c` | `FUN_CODE_225f` | -| v2.13 FW1 | **P0.0** | P0.3 | `FUN_CODE_2060` | `FUN_CODE_22f3` | `func_0x2431` | -| Custom v3.01.0 | **P0.7** | P0.3 | `diseqc_tone_burst()` | (inline) | (inline) | - - - -## CPU Clock Compensation - -The delay function used before DiSEqC transmission adjusts its loop count based on the FX2 CPU clock speed: - -```c title="Clock-aware delay function" -void delay(byte high, byte low) { - byte clkspd = CPUCS & 0x18; // CPUCS[4:3] = clock speed bits - if (clkspd == 0x00) { // 12 MHz: halve the count - // Adjust high:low /= 2 - } else if (clkspd == 0x10) { // 48 MHz: double the count - // Adjust high:low *= 2 - } - // 24 MHz (0x08): use count as-is - while (high:low > 0) { - wait_TF2(); - high:low--; - } -} -``` - -The pre-DiSEqC delay call is `delay(0, 0x0F)` = 15 ticks x 500 us = **7.5 ms**. This allows the LNB voltage to stabilize before DiSEqC signaling begins. - -## Complete Timing Summary - -| Parameter | Value | Source | -|-----------|-------|--------| -| Timer2 clock | 4 MHz (48 MHz / 12) | CKCON default, T2M=0 | -| Timer2 reload | `0xF82F` | RCAP2H:RCAP2L | -| Tick period | 500.25 us | (65536 - 63535) / 4 MHz | -| Bit period | 1.5 ms (3 ticks) | DiSEqC Manchester encoding | -| Byte period | 13.5 ms (9 bits) | 8 data + 1 parity | -| Tone burst duration | 12.5 ms (25 ticks) | Mini-command A/B | -| Pre-TX settling delay | 7.5 ms (15 ticks) | Voltage stabilization | -| Data '0' | 1.0 ms tone + 0.5 ms silence | 2/3 duty cycle | -| Data '1' | 0.5 ms tone + 1.0 ms silence | 1/3 duty cycle | -| Carrier frequency | 22 kHz (external oscillator) | Gated by P0.3 | -| 3-byte DiSEqC message | ~48 ms total | 7.5 ms settle + 3 x 13.5 ms | -| 6-byte DiSEqC message | ~88.5 ms total | 7.5 ms settle + 6 x 13.5 ms | +--- +title: DiSEqC Protocol +description: DiSEqC 1.0/1.2 implementation with Manchester-encoded GPIO bit-bang, timing analysis, and command framing. +--- + +import { Steps, Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +The SkyWalker-1 implements DiSEqC (Digital Satellite Equipment Control) via Timer2-based GPIO bit-bang on the FX2 microcontroller. The algorithm is identical across all firmware versions -- only the data pin assignment changes per PCB revision. + +[Download DiSEqC PDF](/downloads/DiSEqC%20for%20the%20Skywalker-1.pdf) + +## Protocol Diagrams + +The following diagrams document the Genpix BDA driver's DiSEqC extended property interface: + +![DiSEqC Protocol Diagram Page 1](../../../assets/diagrams/DiSEqC%20for%20the%20Skywalker-1_page_1.svg) + +![DiSEqC Protocol Diagram Page 2](../../../assets/diagrams/DiSEqC%20for%20the%20Skywalker-1_page_2.svg) + +## Signal Architecture + +The firmware does **not** generate the 22 kHz carrier directly. GPIO P0.3 gates an external 22 kHz oscillator circuit on the PCB: + +``` +FX2 Firmware External Hardware Coax Cable ++------------------+ +--------------------+ +------------------+ +| P0.3 (carrier) |---->| 22 kHz oscillator |---->| LNB power line | +| (enable/disable) | | (gated by P0.3) | | (13V/18V + tone) | +| | | | | | +| P0.x (data bit) | | (internal firmware | | | +| (firmware only) | | logic only) | | | ++------------------+ +--------------------+ +------------------+ +``` + +The data pin (P0.7 / P0.4 / P0.0 depending on version) is used only internally by the firmware's Manchester encoding logic. It controls whether the carrier gate signal is cut short or held for the full bit period. + +## Timer2 Configuration + +All firmware versions configure Timer2 identically during USB descriptor setup: + +| Parameter | Value | Notes | +|-----------|-------|-------| +| T2CON | `0x04` | Auto-reload mode, timer running | +| RCAP2H | `0xF8` | Reload high byte | +| RCAP2L | `0x2F` | Reload low byte (reload = 63535) | +| CKCON.T2M | `0` | Timer2 clock = 48 MHz / 12 = 4 MHz | + +**Tick period calculation:** + +``` +FX2 master clock = 48 MHz +CKCON.T2M = 0 -> Timer2 clock = 48 MHz / 12 = 4 MHz +Count per overflow = 65536 - 63535 = 2001 +Tick period = 2001 / 4,000,000 = 500.25 us ~ 500 us +Tick frequency ~ 2.0 kHz +``` + +Timer2 runs continuously from power-on and is never stopped or reconfigured. It serves as a stable 500 us timebase for all DiSEqC operations. + +## Manchester Encoding + +Each DiSEqC bit consists of 3 Timer2 ticks (3 x 500 us = 1.5 ms): + + + +**Data '0'** -- 2/3 tone, 1/3 silence (1.0 ms carrier + 0.5 ms silence): + +``` + Tick 1 Tick 2 Tick 3 + (500 us) (500 us) (500 us) +P0.3: _____|========|========|________| + ^tone ON ^tone OFF + (setup gap) (1.0 ms carrier) (0.5 ms silence) +``` + + +**Data '1'** -- 1/3 tone, 2/3 silence (0.5 ms carrier + 1.0 ms silence): + +``` + Tick 1 Tick 2 Tick 3 + (500 us) (500 us) (500 us) +P0.3: _____|========|________|________| + ^tone ON ^tone OFF early + (setup gap) (0.5 ms carrier) (1.0 ms silence) +``` + + + +### Bit Symbol Implementation + +Decompiled from Rev.2 `FUN_CODE_213c`: + +```c title="DiSEqC bit symbol function" +void diseqc_bit_symbol(void) { + wait_TF2(); // Tick 1: inter-bit gap (500 us) + P0 |= 0x08; // P0.3 = 1 -> 22 kHz carrier ON + wait_TF2(); // Tick 2: carrier period (500 us) + if (data_pin != 0) { // If data = '1': + P0 &= 0xF7; // P0.3 = 0 -> carrier OFF (short pulse) + } + wait_TF2(); // Tick 3: final period (500 us) + P0 &= 0xF7; // P0.3 = 0 -> carrier always OFF at end +} +``` + +The `wait_TF2()` function is the lowest-level timing primitive -- a busy-wait on the Timer2 overflow flag: + +```c title="Timer2 tick wait (identical across all versions)" +void wait_TF2(void) { + while (TF2 == 0) {} // Poll Timer2 overflow flag + TF2 = 0; // Clear flag for next tick +} +``` + +## Byte Transmission + +Each DiSEqC byte is 9 bits: 8 data bits (MSB first) + 1 odd parity bit. + +```c title="DiSEqC byte transmit (from Rev.2 FUN_CODE_07d1)" +void diseqc_send_byte(char first_byte, byte data) { + byte ones_count = 0; + if (first_byte == 0) TF2 = 0; // Sync timer on first byte + + for (char i = 8; i > 0; i--) { // 8 bits, MSB first + if (data & 0x80) { + data_pin = 1; // Set data = '1' + diseqc_bit_symbol(); + ones_count++; + } else { + data_pin = 0; // Set data = '0' + diseqc_bit_symbol(); + } + data <<= 1; // Next bit + } + + data_pin = ~ones_count & 1; // Odd parity: '1' if even count + diseqc_bit_symbol(); // Transmit parity bit +} +``` + +**Timing per byte:** 9 bits x 1.5 ms = 13.5 ms + +## DiSEqC Command Sequence + +The full command flow for sending a DiSEqC message via vendor command `0x8D` (`SEND_DISEQC_COMMAND`): + + + +1. **Read wLength** from USB SETUP packet (`0xE6BE`) to determine message byte count + +2. **Disable 22 kHz carrier** by clearing P0.3 (ensuring a clean start state) + +3. **Pre-transmission delay** of 15 Timer2 ticks (7.5 ms) for LNB voltage settling + +4. **Transmit message bytes** if wLength > 0: iterate through EP0BUF, sending each byte via Manchester-encoded bit-bang (8 data bits + odd parity, 3 Timer2 ticks per bit) + +5. **Or send tone burst** if wLength == 0: + - `wValue == 0`: Tone Burst A (25 Timer2 ticks = 12.5 ms of unmodulated carrier) + - `wValue != 0`: Tone Burst B (transmitted as `0xFF` byte pattern through the bit-bang function) + + + +## USB Command Format + +### Full DiSEqC Message (3-6 bytes) + +``` +USB SETUP: + bmRequestType = 0x40 (vendor, host-to-device) + bRequest = 0x8D (SEND_DISEQC_COMMAND) + wValue = msg[0] (framing byte, typically 0xE0 or 0xE1) + wIndex = 0x0000 + wLength = message length (3-6) + +EP0 Data: + Byte 0: Framing byte (e.g., 0xE0 = command from master, no reply expected) + Byte 1: Address byte (e.g., 0x10 = any LNB, 0x11 = LNB 1) + Byte 2: Command byte (e.g., 0x38 = Write N0, committed switch port) + Byte 3: Data byte 0 (optional, port selection bits) + Byte 4: Data byte 1 (optional) + Byte 5: Data byte 2 (optional) +``` + +### Tone Burst (Mini DiSEqC) + +``` +USB SETUP: + bmRequestType = 0x40 + bRequest = 0x8D + wValue = 0x00 (Burst A) or 0x01 (Burst B) + wIndex = 0x0000 + wLength = 0 (zero length signals tone burst mode) +``` + +## Windows BDA Driver Interface + +The Windows driver exposes DiSEqC through a BDA extended property GUID: + +```c title="BDA Extended Property GUID" +// {0B5221EB-F4C4-4976-B959-EF74427464D9} +#define STATIC_KSPROPSETID_BdaExtendedProperty \ + 0x0B5221EB, 0xF4C4, 0x4976, 0xB9, 0x59, 0xEF, 0x74, 0x42, 0x74, 0x64, 0xD9 +``` + +The DiSEqC command structure: + +```c title="DISEQC_COMMAND structure" +typedef enum enSimpleToneBurst { + SEC_MINI_A, + SEC_MINI_B +} SIMPLE_TONE_BURST; + +typedef struct __DISEQC_COMMAND { + UCHAR ucMessage[6]; // Framing, Address, Command, Data[0..2] + UCHAR ucMessageLength; // 3-6 for DiSEqC; 1 for tone burst +} DISEQC_COMMAND; +``` + +For tone burst commands, set `ucMessageLength = 1` and `ucMessage[0]` to either `SEC_MINI_A` (0x00) or `SEC_MINI_B` (0x01). + +## Data Pin Assignment Per Version + +The DiSEqC algorithm is identical across all firmware versions. Only the data pin changes per PCB revision: + +| Firmware Version | Data Pin | Carrier Pin | Byte Transmit Function | Bit Symbol Function | Timer Wait | +|------------------|----------|-------------|----------------------|--------------------|-----------:| +| v2.06 | **P0.7** | P0.3 | `0x2098` | `0x23B5` | `0x24C6` | +| Rev.2 v2.10 | **P0.4** | P0.3 | `FUN_CODE_07d1` | `FUN_CODE_213c` | `FUN_CODE_225f` | +| v2.13 FW1 | **P0.0** | P0.3 | `FUN_CODE_2060` | `FUN_CODE_22f3` | `func_0x2431` | +| Custom v3.01.0 | **P0.7** | P0.3 | `diseqc_tone_burst()` | (inline) | (inline) | + + + +## CPU Clock Compensation + +The delay function used before DiSEqC transmission adjusts its loop count based on the FX2 CPU clock speed: + +```c title="Clock-aware delay function" +void delay(byte high, byte low) { + byte clkspd = CPUCS & 0x18; // CPUCS[4:3] = clock speed bits + if (clkspd == 0x00) { // 12 MHz: halve the count + // Adjust high:low /= 2 + } else if (clkspd == 0x10) { // 48 MHz: double the count + // Adjust high:low *= 2 + } + // 24 MHz (0x08): use count as-is + while (high:low > 0) { + wait_TF2(); + high:low--; + } +} +``` + +The pre-DiSEqC delay call is `delay(0, 0x0F)` = 15 ticks x 500 us = **7.5 ms**. This allows the LNB voltage to stabilize before DiSEqC signaling begins. + +## Complete Timing Summary + +| Parameter | Value | Source | +|-----------|-------|--------| +| Timer2 clock | 4 MHz (48 MHz / 12) | CKCON default, T2M=0 | +| Timer2 reload | `0xF82F` | RCAP2H:RCAP2L | +| Tick period | 500.25 us | (65536 - 63535) / 4 MHz | +| Bit period | 1.5 ms (3 ticks) | DiSEqC Manchester encoding | +| Byte period | 13.5 ms (9 bits) | 8 data + 1 parity | +| Tone burst duration | 12.5 ms (25 ticks) | Mini-command A/B | +| Pre-TX settling delay | 7.5 ms (15 ticks) | Voltage stabilization | +| Data '0' | 1.0 ms tone + 0.5 ms silence | 2/3 duty cycle | +| Data '1' | 0.5 ms tone + 1.0 ms silence | 1/3 duty cycle | +| Carrier frequency | 22 kHz (external oscillator) | Gated by P0.3 | +| 3-byte DiSEqC message | ~48 ms total | 7.5 ms settle + 3 x 13.5 ms | +| 6-byte DiSEqC message | ~88.5 ms total | 7.5 ms settle + 6 x 13.5 ms | diff --git a/site/src/content/docs/lnb-diseqc/legacy-switch.mdx b/site/src/content/docs/lnb-diseqc/legacy-switch.mdx index 486bf1b..9e3c274 100644 --- a/site/src/content/docs/lnb-diseqc/legacy-switch.mdx +++ b/site/src/content/docs/lnb-diseqc/legacy-switch.mdx @@ -1,106 +1,106 @@ ---- -title: Legacy Dish Network Switch -description: SET_DN_SWITCH (0x8F) protocol for Dish Network SW21/SW44 legacy switches. ---- - -import { Badge, Aside, Steps } from '@astrojs/starlight/components'; - -The SkyWalker-1 supports legacy Dish Network satellite switches (SW21, SW44) through vendor command `0x8F` (`SET_DN_SWITCH`). This protocol predates DiSEqC and uses a simpler serial bit-bang scheme on a shared GPIO pin. - -## Protocol Overview - -The legacy Dish Network switch protocol sends a 7-bit serial command via **GPIO P0.4** (the same pin used for LNB voltage selection). The command is bit-banged LSB-first with fixed timing delays. - - - -## USB Command Format - -``` -USB SETUP: - bmRequestType = 0x40 (vendor, host-to-device) - bRequest = 0x8F (SET_DN_SWITCH) - wValue = 7-bit switch command (bits 0-6) - wIndex = 0x0000 - wLength = 0 -``` - -## Bit-Bang Sequence - -The firmware transmits the 7-bit command using the following GPIO sequence: - - - -1. **Start pulse**: Assert P0.4 HIGH, delay approximately 32 CPU cycles (~0.67 us at 48 MHz) - -2. **Gap**: De-assert P0.4 LOW, delay approximately 8 CPU cycles (~0.17 us at 48 MHz) - -3. **Data bits**: Shift out 7 bits LSB-first via P0.4, with approximately 8-cycle delays between each bit transition - -4. **End**: P0.4 returns to its previous LNB voltage state - - - -## Switch Position Mapping - -The legacy protocol supports the SW21 (2-input, 1-output) and SW44 (4-input, 4-output) switches. The 7-bit command encodes the desired input port and satellite selection: - -| Switch Type | Bit Pattern | Function | -|-------------|-------------|----------| -| SW21 | `0bxxxxxxx` with port bit = 0 | Select satellite A input | -| SW21 | `0bxxxxxxx` with port bit = 1 | Select satellite B input | -| SW44 | Multiple bit fields | Select one of 4 inputs to one of 4 outputs | - -The exact bit-field layout is proprietary to Dish Network. The kernel driver constructs the command value in `dishnetwork_send_legacy_command()` based on the requested switch position. - -## Timing Characteristics - -The legacy switch protocol is significantly simpler and faster than DiSEqC: - -| Parameter | Value | -|-----------|-------| -| Start pulse width | ~0.67 us (32 cycles at 48 MHz) | -| Inter-bit gap | ~0.17 us (8 cycles at 48 MHz) | -| Total command time | ~5-10 us for 7 bits | -| GPIO pin | P0.4 (shared with LNB voltage) | - - - -## Kernel Driver Integration - -The Linux kernel driver implements legacy Dish Network support through a dedicated callback: - -```c title="Legacy Dish Network command callback" -// Registered as .send_legacy_dish_cmd in dvb_usb_device_properties -static int gp8psk_send_legacy_dish_cmd(struct dvb_frontend *fe, unsigned long cmd) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct dvb_usb_device *d = adap->dev; - - // Bit 7 (0x80) controls voltage, sent separately - gp8psk_usb_out_op(d, SET_LNB_VOLTAGE, (cmd >> 7) & 1, 0, NULL, 0); - - // Bits 0-6 sent via SET_DN_SWITCH - gp8psk_usb_out_op(d, SET_DN_SWITCH, cmd & 0x7F, 0, NULL, 0); - - return 0; -} -``` - -## Comparison with DiSEqC - -| Feature | Legacy Dish Protocol | DiSEqC 1.0 | -|---------|---------------------|------------| -| Standard | Proprietary (Dish Network) | EUTELSAT standard | -| Encoding | Serial bit-bang, LSB-first | Manchester encoding, MSB-first | -| Carrier | None (direct GPIO) | 22 kHz modulated | -| Message length | 7 bits | 3-6 bytes (24-54 bits + parity) | -| Timing | ~5-10 us total | ~48-88 ms total | -| Switch support | SW21, SW44 | DiSEqC 1.0/1.1/1.2 compatible switches | -| GPIO pin | P0.4 (shared with voltage) | P0.x (data) + P0.3 (carrier) | -| Vendor command | `0x8F` | `0x8D` | - -For modern satellite installations, DiSEqC is the preferred switch protocol. The legacy Dish Network protocol exists for backward compatibility with older SW21 and SW44 switches commonly found in North American installations. See the [DiSEqC Protocol](/lnb-diseqc/diseqc-protocol/) page for the full DiSEqC implementation. +--- +title: Legacy Dish Network Switch +description: SET_DN_SWITCH (0x8F) protocol for Dish Network SW21/SW44 legacy switches. +--- + +import { Badge, Aside, Steps } from '@astrojs/starlight/components'; + +The SkyWalker-1 supports legacy Dish Network satellite switches (SW21, SW44) through vendor command `0x8F` (`SET_DN_SWITCH`). This protocol predates DiSEqC and uses a simpler serial bit-bang scheme on a shared GPIO pin. + +## Protocol Overview + +The legacy Dish Network switch protocol sends a 7-bit serial command via **GPIO P0.4** (the same pin used for LNB voltage selection). The command is bit-banged LSB-first with fixed timing delays. + + + +## USB Command Format + +``` +USB SETUP: + bmRequestType = 0x40 (vendor, host-to-device) + bRequest = 0x8F (SET_DN_SWITCH) + wValue = 7-bit switch command (bits 0-6) + wIndex = 0x0000 + wLength = 0 +``` + +## Bit-Bang Sequence + +The firmware transmits the 7-bit command using the following GPIO sequence: + + + +1. **Start pulse**: Assert P0.4 HIGH, delay approximately 32 CPU cycles (~0.67 us at 48 MHz) + +2. **Gap**: De-assert P0.4 LOW, delay approximately 8 CPU cycles (~0.17 us at 48 MHz) + +3. **Data bits**: Shift out 7 bits LSB-first via P0.4, with approximately 8-cycle delays between each bit transition + +4. **End**: P0.4 returns to its previous LNB voltage state + + + +## Switch Position Mapping + +The legacy protocol supports the SW21 (2-input, 1-output) and SW44 (4-input, 4-output) switches. The 7-bit command encodes the desired input port and satellite selection: + +| Switch Type | Bit Pattern | Function | +|-------------|-------------|----------| +| SW21 | `0bxxxxxxx` with port bit = 0 | Select satellite A input | +| SW21 | `0bxxxxxxx` with port bit = 1 | Select satellite B input | +| SW44 | Multiple bit fields | Select one of 4 inputs to one of 4 outputs | + +The exact bit-field layout is proprietary to Dish Network. The kernel driver constructs the command value in `dishnetwork_send_legacy_command()` based on the requested switch position. + +## Timing Characteristics + +The legacy switch protocol is significantly simpler and faster than DiSEqC: + +| Parameter | Value | +|-----------|-------| +| Start pulse width | ~0.67 us (32 cycles at 48 MHz) | +| Inter-bit gap | ~0.17 us (8 cycles at 48 MHz) | +| Total command time | ~5-10 us for 7 bits | +| GPIO pin | P0.4 (shared with LNB voltage) | + + + +## Kernel Driver Integration + +The Linux kernel driver implements legacy Dish Network support through a dedicated callback: + +```c title="Legacy Dish Network command callback" +// Registered as .send_legacy_dish_cmd in dvb_usb_device_properties +static int gp8psk_send_legacy_dish_cmd(struct dvb_frontend *fe, unsigned long cmd) +{ + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct dvb_usb_device *d = adap->dev; + + // Bit 7 (0x80) controls voltage, sent separately + gp8psk_usb_out_op(d, SET_LNB_VOLTAGE, (cmd >> 7) & 1, 0, NULL, 0); + + // Bits 0-6 sent via SET_DN_SWITCH + gp8psk_usb_out_op(d, SET_DN_SWITCH, cmd & 0x7F, 0, NULL, 0); + + return 0; +} +``` + +## Comparison with DiSEqC + +| Feature | Legacy Dish Protocol | DiSEqC 1.0 | +|---------|---------------------|------------| +| Standard | Proprietary (Dish Network) | EUTELSAT standard | +| Encoding | Serial bit-bang, LSB-first | Manchester encoding, MSB-first | +| Carrier | None (direct GPIO) | 22 kHz modulated | +| Message length | 7 bits | 3-6 bytes (24-54 bits + parity) | +| Timing | ~5-10 us total | ~48-88 ms total | +| Switch support | SW21, SW44 | DiSEqC 1.0/1.1/1.2 compatible switches | +| GPIO pin | P0.4 (shared with voltage) | P0.x (data) + P0.3 (carrier) | +| Vendor command | `0x8F` | `0x8D` | + +For modern satellite installations, DiSEqC is the preferred switch protocol. The legacy Dish Network protocol exists for backward compatibility with older SW21 and SW44 switches commonly found in North American installations. See the [DiSEqC Protocol](/lnb-diseqc/diseqc-protocol/) page for the full DiSEqC implementation. diff --git a/site/src/content/docs/lnb-diseqc/lnb-control.mdx b/site/src/content/docs/lnb-diseqc/lnb-control.mdx index b78ad9b..d631eb5 100644 --- a/site/src/content/docs/lnb-diseqc/lnb-control.mdx +++ b/site/src/content/docs/lnb-diseqc/lnb-control.mdx @@ -1,127 +1,127 @@ ---- -title: LNB Control -description: LNB voltage selection (13V/18V), 22 kHz tone generation, extra volt mode, and current limits. ---- - -import { Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; - -The SkyWalker-1 controls LNB power and band selection through dedicated GPIO pins on the FX2 microcontroller. No I2C transactions are involved in basic LNB control -- voltage and tone are driven directly by port pins. - -## LNB Voltage (13V / 18V) - -LNB voltage selects the polarization of the received signal. It is controlled via **GPIO P0.4** using vendor command `0x8B` (`SET_LNB_VOLTAGE`). - -| wValue | Voltage | GPIO P0.4 | Polarization | -|--------|---------|-----------|--------------| -| `0x00` | 13V | LOW | Vertical / Circular-Right | -| `0x01` | 18V | HIGH | Horizontal / Circular-Left | - -The Linux kernel driver sets the voltage during the tuning sequence based on the requested polarization: - -```c title="Kernel driver polarization mapping" -// From gp8psk-fe.c set_voltage callback -case SEC_VOLTAGE_13: // Vertical / Circular-R - gp8psk_usb_out_op(d, SET_LNB_VOLTAGE, 0, 0, NULL, 0); - break; -case SEC_VOLTAGE_18: // Horizontal / Circular-L - gp8psk_usb_out_op(d, SET_LNB_VOLTAGE, 1, 0, NULL, 0); - break; -``` - -The configuration status byte (returned by `GET_8PSK_CONFIG`, command `0x80`) reflects the current voltage selection in **bit 5** (`0x20`, `bmSEL18V`). - -## Extra Volt Mode (+1V Boost) - -For long cable runs where voltage drop is significant, the SkyWalker-1 supports a +1V boost mode (13V becomes 14V, 18V becomes 19V). This is controlled via vendor command `0x94` (`USE_EXTRA_VOLT`). - -| wValue | XRAM 0xE0B6 | Result | -|--------|-------------|--------| -| `0x00` | `0x62` | Normal voltage (13V / 18V) | -| `0x01` | `0x6A` | Boosted voltage (14V / 19V) | - -The difference between the two XRAM values is **bit 3** (`0x08`), which enables the boost on the LNB power regulator circuitry. - - - -The kernel driver exposes extra volt mode through the `enable_high_lnb_voltage` callback in the DVB frontend properties. - -## 22 kHz Tone Generation - -The 22 kHz tone selects the high or low band on a universal LNB. It is controlled via **GPIO P0.3** using vendor command `0x8C` (`SET_22KHZ_TONE`). - -| wValue | State | GPIO P0.3 | LNB Band | -|--------|-------|-----------|----------| -| `0x00` | OFF | LOW | Low band (9.75 GHz LO on universal LNB) | -| `0x01` | ON | HIGH | High band (10.6 GHz LO on universal LNB) | - - - -The configuration status byte tracks the tone state in **bit 4** (`0x10`, `bm22kHz`). - -## LNB Power Supply Enable - -Before any LNB control is possible, the LNB power supply itself must be enabled via vendor command `0x8A` (`START_INTERSIL`). The kernel driver does this during the boot sequence if bit 2 (`bmIntersilOn`) of the configuration status byte is not already set. - -| wValue | Action | Config Bit | -|--------|--------|------------| -| `0x01` | Enable LNB power supply | Sets `bmIntersilOn` (bit 2) | -| `0x00` | Disable LNB power supply | Clears `bmIntersilOn` (bit 2) | - -In the custom v3.01.0 firmware, enabling the LNB supply also configures the output enable masks for the LNB control GPIO pins: - -```c title="START_INTERSIL handler in custom firmware" -case START_INTERSIL: - if (wval) { - OEA |= (PIN_22KHZ | PIN_LNB_VOLT | PIN_DISEQC); - config_status |= BM_INTERSIL; - } else { - config_status &= ~BM_INTERSIL; - } -``` - -## GPIO Pin Assignments - -The LNB control pins are consistent across firmware versions, though their exact initialization differs: - -| GPIO Pin | Function | Direction | Init State | -|----------|----------|-----------|------------| -| P0.1 | Power supply enable | Output | LOW (off) | -| P0.2 | Power supply disable | Output | HIGH (active disable) | -| P0.3 | 22 kHz tone gate | Output | LOW (tone off) | -| P0.4 | LNB voltage select | Output | LOW (13V) | -| P0.5 | BCM4500 hardware reset | Output | LOW (reset asserted) | - -The initial GPIO state after power-on is `IOA = 0x84` (P0.7 and P0.2 HIGH, all others LOW), with the output enable mask `OEA = 0xBE` (P0.1 through P0.5 and P0.7 as outputs). - -## LNB GPIO Debug Commands - -Three vendor commands provide low-level GPIO access for LNB debugging: - -| Command | Name | Purpose | -|---------|------|---------| -| `0x96` | `SET_LNB_GPIO_MODE` | Configure LNB GPIO output enables | -| `0x97` | `SET_GPIO_PINS` | Direct write to LNB GPIO pins via `wValue` bitmap | -| `0x98` | `GET_GPIO_STATUS` | Read LNB feedback GPIO pin (1 byte) | - -These commands access Port B (XRAM-mapped IOB) rather than Port A. In v2.06 and v2.13, `0x97` controls IOB bits 1 and 2 for LNB, while `0x96` configures IOB bit 3 as the GPIO mode select. Rev.2 uses IOB bit 4 instead. - - - -## Complete LNB Configuration Sequence - -During a typical tune operation, the kernel driver configures the LNB in this order: - -``` -1. SET_LNB_VOLTAGE (0x8B) -- Select 13V or 18V based on polarization -2. SET_22KHZ_TONE (0x8C) -- Select low or high band -3. SEND_DISEQC (0x8D) -- Switch multi-port switch if needed -4. TUNE_8PSK (0x86) -- Send tuning parameters -``` - -The voltage and tone are set before any DiSEqC commands because the LNB must be powered and in the correct band before the switch can respond. See the [DiSEqC Protocol](/lnb-diseqc/diseqc-protocol/) page for details on switch control, and the [Tuning Protocol](/bcm4500/tuning-protocol/) for the complete tune flow. +--- +title: LNB Control +description: LNB voltage selection (13V/18V), 22 kHz tone generation, extra volt mode, and current limits. +--- + +import { Badge, Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +The SkyWalker-1 controls LNB power and band selection through dedicated GPIO pins on the FX2 microcontroller. No I2C transactions are involved in basic LNB control -- voltage and tone are driven directly by port pins. + +## LNB Voltage (13V / 18V) + +LNB voltage selects the polarization of the received signal. It is controlled via **GPIO P0.4** using vendor command `0x8B` (`SET_LNB_VOLTAGE`). + +| wValue | Voltage | GPIO P0.4 | Polarization | +|--------|---------|-----------|--------------| +| `0x00` | 13V | LOW | Vertical / Circular-Right | +| `0x01` | 18V | HIGH | Horizontal / Circular-Left | + +The Linux kernel driver sets the voltage during the tuning sequence based on the requested polarization: + +```c title="Kernel driver polarization mapping" +// From gp8psk-fe.c set_voltage callback +case SEC_VOLTAGE_13: // Vertical / Circular-R + gp8psk_usb_out_op(d, SET_LNB_VOLTAGE, 0, 0, NULL, 0); + break; +case SEC_VOLTAGE_18: // Horizontal / Circular-L + gp8psk_usb_out_op(d, SET_LNB_VOLTAGE, 1, 0, NULL, 0); + break; +``` + +The configuration status byte (returned by `GET_8PSK_CONFIG`, command `0x80`) reflects the current voltage selection in **bit 5** (`0x20`, `bmSEL18V`). + +## Extra Volt Mode (+1V Boost) + +For long cable runs where voltage drop is significant, the SkyWalker-1 supports a +1V boost mode (13V becomes 14V, 18V becomes 19V). This is controlled via vendor command `0x94` (`USE_EXTRA_VOLT`). + +| wValue | XRAM 0xE0B6 | Result | +|--------|-------------|--------| +| `0x00` | `0x62` | Normal voltage (13V / 18V) | +| `0x01` | `0x6A` | Boosted voltage (14V / 19V) | + +The difference between the two XRAM values is **bit 3** (`0x08`), which enables the boost on the LNB power regulator circuitry. + + + +The kernel driver exposes extra volt mode through the `enable_high_lnb_voltage` callback in the DVB frontend properties. + +## 22 kHz Tone Generation + +The 22 kHz tone selects the high or low band on a universal LNB. It is controlled via **GPIO P0.3** using vendor command `0x8C` (`SET_22KHZ_TONE`). + +| wValue | State | GPIO P0.3 | LNB Band | +|--------|-------|-----------|----------| +| `0x00` | OFF | LOW | Low band (9.75 GHz LO on universal LNB) | +| `0x01` | ON | HIGH | High band (10.6 GHz LO on universal LNB) | + + + +The configuration status byte tracks the tone state in **bit 4** (`0x10`, `bm22kHz`). + +## LNB Power Supply Enable + +Before any LNB control is possible, the LNB power supply itself must be enabled via vendor command `0x8A` (`START_INTERSIL`). The kernel driver does this during the boot sequence if bit 2 (`bmIntersilOn`) of the configuration status byte is not already set. + +| wValue | Action | Config Bit | +|--------|--------|------------| +| `0x01` | Enable LNB power supply | Sets `bmIntersilOn` (bit 2) | +| `0x00` | Disable LNB power supply | Clears `bmIntersilOn` (bit 2) | + +In the custom v3.01.0 firmware, enabling the LNB supply also configures the output enable masks for the LNB control GPIO pins: + +```c title="START_INTERSIL handler in custom firmware" +case START_INTERSIL: + if (wval) { + OEA |= (PIN_22KHZ | PIN_LNB_VOLT | PIN_DISEQC); + config_status |= BM_INTERSIL; + } else { + config_status &= ~BM_INTERSIL; + } +``` + +## GPIO Pin Assignments + +The LNB control pins are consistent across firmware versions, though their exact initialization differs: + +| GPIO Pin | Function | Direction | Init State | +|----------|----------|-----------|------------| +| P0.1 | Power supply enable | Output | LOW (off) | +| P0.2 | Power supply disable | Output | HIGH (active disable) | +| P0.3 | 22 kHz tone gate | Output | LOW (tone off) | +| P0.4 | LNB voltage select | Output | LOW (13V) | +| P0.5 | BCM4500 hardware reset | Output | LOW (reset asserted) | + +The initial GPIO state after power-on is `IOA = 0x84` (P0.7 and P0.2 HIGH, all others LOW), with the output enable mask `OEA = 0xBE` (P0.1 through P0.5 and P0.7 as outputs). + +## LNB GPIO Debug Commands + +Three vendor commands provide low-level GPIO access for LNB debugging: + +| Command | Name | Purpose | +|---------|------|---------| +| `0x96` | `SET_LNB_GPIO_MODE` | Configure LNB GPIO output enables | +| `0x97` | `SET_GPIO_PINS` | Direct write to LNB GPIO pins via `wValue` bitmap | +| `0x98` | `GET_GPIO_STATUS` | Read LNB feedback GPIO pin (1 byte) | + +These commands access Port B (XRAM-mapped IOB) rather than Port A. In v2.06 and v2.13, `0x97` controls IOB bits 1 and 2 for LNB, while `0x96` configures IOB bit 3 as the GPIO mode select. Rev.2 uses IOB bit 4 instead. + + + +## Complete LNB Configuration Sequence + +During a typical tune operation, the kernel driver configures the LNB in this order: + +``` +1. SET_LNB_VOLTAGE (0x8B) -- Select 13V or 18V based on polarization +2. SET_22KHZ_TONE (0x8C) -- Select low or high band +3. SEND_DISEQC (0x8D) -- Switch multi-port switch if needed +4. TUNE_8PSK (0x86) -- Send tuning parameters +``` + +The voltage and tone are set before any DiSEqC commands because the LNB must be powered and in the correct band before the switch can respond. See the [DiSEqC Protocol](/lnb-diseqc/diseqc-protocol/) page for details on switch control, and the [Tuning Protocol](/bcm4500/tuning-protocol/) for the complete tune flow. diff --git a/site/src/content/docs/reference/master-reference.md b/site/src/content/docs/reference/master-reference.md index 36edf2a..9d897f7 100644 --- a/site/src/content/docs/reference/master-reference.md +++ b/site/src/content/docs/reference/master-reference.md @@ -1,65 +1,65 @@ ---- -title: Sources -description: Primary sources, analysis documents, and community references for the SkyWalker-1 reverse engineering project. ---- - -# Sources and Bibliography - -All technical content in this documentation was derived from the following primary sources. No public BCM4500 datasheet with complete register definitions exists; register addresses, functions, and protocols were determined through firmware disassembly, I2C bus captures, driver source analysis, and behavioral testing. - ---- - -## Firmware Analysis - -Five firmware images were decompiled and disassembled using Ghidra (8051 processor module): - -| Firmware | Ghidra Port | Source | -|----------|-------------|--------| -| v2.06.04 | 8193 | Extracted from SkyWalker-1 EEPROM via `eeprom_dump.py` | -| Rev.2 v2.10.04 | 8197 | Extracted from Rev.2 hardware EEPROM | -| v2.13.01 (FW1) | 8194 | Extracted from `SW1_update_2_13_x.exe` via `wine_memdump.py` | -| v2.13.02 (FW2) | 8195 | Extracted from `SW1_update_2_13_x.exe` via `wine_memdump.py` | -| v2.13.03 (FW3) | 8196 | Extracted from `SW1_update_2_13_x.exe` via `wine_memdump.py` | - -Binary dumps are in `firmware-dump/`. - -## Driver Source - -| Source | Path / Location | -|--------|----------------| -| Linux kernel 6.16.5 | `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h`, `gp8psk-fe.c`, `gp8psk-fe.h` | -| Linux kernel | `drivers/media/usb/dvb-usb/dvb-usb-firmware.c` | -| Windows BDA driver | `SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp` | -| Windows BDA driver | `SkyWalker1_Final_Release/Include/SkyWalker1Control.h`, `SkyWalker1CommonDef.h` | - -## Hardware Documentation - -- BCM4500 Datasheet: [DatasheetQ](https://html.datasheetq.com/pdf-html/885700/Broadcom/2page/BCM4500.html), [Elcodis](https://elcodis.com/parts/5786421/BCM4500.html) -- BCM4501 Product Page: [Broadcom](https://www.broadcom.com/products/broadband/set-top-box/bcm4501) -- Cypress CY7C68013A (FX2LP) Technical Reference Manual -- Genpix Electronics: https://www.genpix-electronics.com/index.php?act=viewDoc&docId=9 -- Genpix SkyWalker-3 specifications: https://www.genpix-electronics.com/what-is-skywalker-3.html -- Device `dmesg` output from running SkyWalker-1 hardware - -## Analysis Reports - -Internal analysis documents produced during this project (in `docs/` directory): - -1. `gp8psk-driver-analysis.md` -- Linux kernel driver analysis -2. `firmware-analysis-v206-vs-v213.md` -- v2.06 vs v2.13 firmware comparison -3. `rev2-deep-analysis.md` -- Rev.2 deep function inventory (107 functions) -4. `gpif-streaming-analysis.md` -- GPIF/MPEG-2 streaming path -5. `tuning-protocol-analysis.md` -- TUNE_8PSK protocol deep dive -6. `vendor-commands-unknown.md` -- Vendor command decode (0x8F, 0x91--0x98) -7. `kernel-fw01-analysis.md` -- Kernel firmware format and EEPROM boot -8. `firmware-dump/fw_v213_comparison_report.md` -- v2.13 sub-variant comparison -9. `dvb-s2-investigation.md` -- DVB-S2 incompatibility investigation -10. `docs/boot-debug-findings.md` -- Boot/I2C debugging findings -11. `docs/diseqc/diseqc-skywalker-1.md` -- DiSEqC Windows BDA interface -12. `firmware/skywalker1.c` -- Custom firmware v3.01.0 source - -## Community References - -- [LinuxTV mailing list: BCM4500 and DVB-S2](https://www.mail-archive.com/linux-dvb@linuxtv.org/msg24808.html) -- [SatelliteGuys: Turbo 8PSK card reverse engineering](https://www.satelliteguys.us/xen/threads/another-turbo-8psk-card.246879/) -- [SatelliteGuys: Genpix SkyWalker-1 discussion](https://www.satelliteguys.us/xen/threads/genpix-skywalker-1.214196/) +--- +title: Sources +description: Primary sources, analysis documents, and community references for the SkyWalker-1 reverse engineering project. +--- + +# Sources and Bibliography + +All technical content in this documentation was derived from the following primary sources. No public BCM4500 datasheet with complete register definitions exists; register addresses, functions, and protocols were determined through firmware disassembly, I2C bus captures, driver source analysis, and behavioral testing. + +--- + +## Firmware Analysis + +Five firmware images were decompiled and disassembled using Ghidra (8051 processor module): + +| Firmware | Ghidra Port | Source | +|----------|-------------|--------| +| v2.06.04 | 8193 | Extracted from SkyWalker-1 EEPROM via `eeprom_dump.py` | +| Rev.2 v2.10.04 | 8197 | Extracted from Rev.2 hardware EEPROM | +| v2.13.01 (FW1) | 8194 | Extracted from `SW1_update_2_13_x.exe` via `wine_memdump.py` | +| v2.13.02 (FW2) | 8195 | Extracted from `SW1_update_2_13_x.exe` via `wine_memdump.py` | +| v2.13.03 (FW3) | 8196 | Extracted from `SW1_update_2_13_x.exe` via `wine_memdump.py` | + +Binary dumps are in `firmware-dump/`. + +## Driver Source + +| Source | Path / Location | +|--------|----------------| +| Linux kernel 6.16.5 | `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h`, `gp8psk-fe.c`, `gp8psk-fe.h` | +| Linux kernel | `drivers/media/usb/dvb-usb/dvb-usb-firmware.c` | +| Windows BDA driver | `SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp` | +| Windows BDA driver | `SkyWalker1_Final_Release/Include/SkyWalker1Control.h`, `SkyWalker1CommonDef.h` | + +## Hardware Documentation + +- BCM4500 Datasheet: [DatasheetQ](https://html.datasheetq.com/pdf-html/885700/Broadcom/2page/BCM4500.html), [Elcodis](https://elcodis.com/parts/5786421/BCM4500.html) +- BCM4501 Product Page: [Broadcom](https://www.broadcom.com/products/broadband/set-top-box/bcm4501) +- Cypress CY7C68013A (FX2LP) Technical Reference Manual +- Genpix Electronics: https://www.genpix-electronics.com/index.php?act=viewDoc&docId=9 +- Genpix SkyWalker-3 specifications: https://www.genpix-electronics.com/what-is-skywalker-3.html +- Device `dmesg` output from running SkyWalker-1 hardware + +## Analysis Reports + +Internal analysis documents produced during this project (in `docs/` directory): + +1. `gp8psk-driver-analysis.md` -- Linux kernel driver analysis +2. `firmware-analysis-v206-vs-v213.md` -- v2.06 vs v2.13 firmware comparison +3. `rev2-deep-analysis.md` -- Rev.2 deep function inventory (107 functions) +4. `gpif-streaming-analysis.md` -- GPIF/MPEG-2 streaming path +5. `tuning-protocol-analysis.md` -- TUNE_8PSK protocol deep dive +6. `vendor-commands-unknown.md` -- Vendor command decode (0x8F, 0x91--0x98) +7. `kernel-fw01-analysis.md` -- Kernel firmware format and EEPROM boot +8. `firmware-dump/fw_v213_comparison_report.md` -- v2.13 sub-variant comparison +9. `dvb-s2-investigation.md` -- DVB-S2 incompatibility investigation +10. `docs/boot-debug-findings.md` -- Boot/I2C debugging findings +11. `docs/diseqc/diseqc-skywalker-1.md` -- DiSEqC Windows BDA interface +12. `firmware/skywalker1.c` -- Custom firmware v3.01.0 source + +## Community References + +- [LinuxTV mailing list: BCM4500 and DVB-S2](https://www.mail-archive.com/linux-dvb@linuxtv.org/msg24808.html) +- [SatelliteGuys: Turbo 8PSK card reverse engineering](https://www.satelliteguys.us/xen/threads/another-turbo-8psk-card.246879/) +- [SatelliteGuys: Genpix SkyWalker-1 discussion](https://www.satelliteguys.us/xen/threads/genpix-skywalker-1.214196/) diff --git a/site/src/content/docs/tools/arc-survey.mdx b/site/src/content/docs/tools/arc-survey.mdx index 1b7f506..39b917b 100644 --- a/site/src/content/docs/tools/arc-survey.mdx +++ b/site/src/content/docs/tools/arc-survey.mdx @@ -1,95 +1,95 @@ ---- -title: Arc Survey -description: Automated multi-satellite orbital arc survey with motor control and carrier catalog aggregation. ---- - -import { Aside, Steps } from '@astrojs/starlight/components'; - -The `arc_survey.py` tool automates a complete satellite census across the GEO orbital arc. -It points the dish motor to each orbital longitude, runs a full six-stage carrier survey, -saves individual catalogs, and produces an aggregated sky map. - -## Quick Start - -```bash -# Survey 4 specific North American slots -python tools/arc_survey.py --observer-lon -96.8 --slots "97W,99W,101W,103W" - -# Survey a 60-degree arc at 3-degree intervals -python tools/arc_survey.py --observer-lon -96.8 --arc -120 -60 --step 3 - -# List common NA orbital positions -python tools/arc_survey.py --list-slots -``` - -## How It Works - - -1. **Parse orbital slot list** from CLI, JSON file, or arc range -2. **For each slot**: send USALS GotoX command to motor -3. **Wait for settle** (scales with angular distance, minimum 15 seconds) -4. **Run six-stage survey**: coarse sweep → peak detection → fine sweep → blind scan → TS sample → catalog -5. **Save per-slot catalog** to `~/.skywalker1/surveys/` -6. **Save arc survey state** to `~/.skywalker1/arc-surveys/` (for resume) -7. **Print aggregated summary** with per-slot carrier counts - - -## Resume Support - -The tool saves state after every completed slot. If interrupted (Ctrl-C, power loss, etc.): - -```bash -python tools/arc_survey.py --resume ~/.skywalker1/arc-surveys/arc-survey-2026-02-17.json -``` - -Already-surveyed slots are skipped. The survey continues from where it left off. - -## Slot Specification - - - -**CLI list:** -```bash ---slots "97W,99W,101W,103W,105W" -``` - -**JSON file:** -```json -[ - {"name": "97W", "lon": -97.0}, - {"name": "99W", "lon": -99.0}, - {"name": "Galaxy 16", "lon": -99.0} -] -``` - -**Arc range:** -```bash ---arc -120 -60 --step 3 -``` - -## Options - -| Flag | Default | Description | -|---|---|---| -| `--observer-lon` | (required) | Observer longitude (negative = west) | -| `--observer-lat` | 0.0 | Observer latitude | -| `--slots` | — | Comma-separated slot list | -| `--file` | — | JSON file with slot definitions | -| `--arc` | — | Start/stop longitude for range scan | -| `--step` | 3.0 | Degrees between arc positions | -| `--coarse-step` | 5.0 | MHz step for coarse spectrum sweep | -| `--settle-time` | 15 | Minimum motor settle time (seconds) | -| `--resume` | — | Resume from saved state file | -| `--list-slots` | — | Print common NA slots and exit | - -## Output Files - -| Location | Content | -|---|---| -| `~/.skywalker1/surveys/arc-DATE-SLOT.json` | Per-slot carrier catalog | -| `~/.skywalker1/arc-surveys/arc-survey-DATE.json` | Arc survey state (for resume) | - -The per-slot catalogs are standard `CarrierCatalog` JSON files, compatible with -the diff and comparison tools in `carrier_catalog.py`. +--- +title: Arc Survey +description: Automated multi-satellite orbital arc survey with motor control and carrier catalog aggregation. +--- + +import { Aside, Steps } from '@astrojs/starlight/components'; + +The `arc_survey.py` tool automates a complete satellite census across the GEO orbital arc. +It points the dish motor to each orbital longitude, runs a full six-stage carrier survey, +saves individual catalogs, and produces an aggregated sky map. + +## Quick Start + +```bash +# Survey 4 specific North American slots +python tools/arc_survey.py --observer-lon -96.8 --slots "97W,99W,101W,103W" + +# Survey a 60-degree arc at 3-degree intervals +python tools/arc_survey.py --observer-lon -96.8 --arc -120 -60 --step 3 + +# List common NA orbital positions +python tools/arc_survey.py --list-slots +``` + +## How It Works + + +1. **Parse orbital slot list** from CLI, JSON file, or arc range +2. **For each slot**: send USALS GotoX command to motor +3. **Wait for settle** (scales with angular distance, minimum 15 seconds) +4. **Run six-stage survey**: coarse sweep → peak detection → fine sweep → blind scan → TS sample → catalog +5. **Save per-slot catalog** to `~/.skywalker1/surveys/` +6. **Save arc survey state** to `~/.skywalker1/arc-surveys/` (for resume) +7. **Print aggregated summary** with per-slot carrier counts + + +## Resume Support + +The tool saves state after every completed slot. If interrupted (Ctrl-C, power loss, etc.): + +```bash +python tools/arc_survey.py --resume ~/.skywalker1/arc-surveys/arc-survey-2026-02-17.json +``` + +Already-surveyed slots are skipped. The survey continues from where it left off. + +## Slot Specification + + + +**CLI list:** +```bash +--slots "97W,99W,101W,103W,105W" +``` + +**JSON file:** +```json +[ + {"name": "97W", "lon": -97.0}, + {"name": "99W", "lon": -99.0}, + {"name": "Galaxy 16", "lon": -99.0} +] +``` + +**Arc range:** +```bash +--arc -120 -60 --step 3 +``` + +## Options + +| Flag | Default | Description | +|---|---|---| +| `--observer-lon` | (required) | Observer longitude (negative = west) | +| `--observer-lat` | 0.0 | Observer latitude | +| `--slots` | — | Comma-separated slot list | +| `--file` | — | JSON file with slot definitions | +| `--arc` | — | Start/stop longitude for range scan | +| `--step` | 3.0 | Degrees between arc positions | +| `--coarse-step` | 5.0 | MHz step for coarse spectrum sweep | +| `--settle-time` | 15 | Minimum motor settle time (seconds) | +| `--resume` | — | Resume from saved state file | +| `--list-slots` | — | Print common NA slots and exit | + +## Output Files + +| Location | Content | +|---|---| +| `~/.skywalker1/surveys/arc-DATE-SLOT.json` | Per-slot carrier catalog | +| `~/.skywalker1/arc-surveys/arc-survey-DATE.json` | Arc survey state (for resume) | + +The per-slot catalogs are standard `CarrierCatalog` JSON files, compatible with +the diff and comparison tools in `carrier_catalog.py`. diff --git a/site/src/content/docs/tools/beacon-logger.mdx b/site/src/content/docs/tools/beacon-logger.mdx index 04e97bf..ea45322 100644 --- a/site/src/content/docs/tools/beacon-logger.mdx +++ b/site/src/content/docs/tools/beacon-logger.mdx @@ -1,75 +1,75 @@ ---- -title: Beacon Logger -description: Long-term satellite signal logging for propagation research, rain fade analysis, and link budget validation. ---- - -import { Aside, Steps } from '@astrojs/starlight/components'; - -The `beacon_logger.py` tool locks onto a stable satellite transponder and records SNR, AGC levels, -and signal power at configurable intervals. Multi-day datasets reveal rain fade events, diurnal -thermal effects, antenna mount drift, and LNB gain variations. - -## Quick Start - -```bash -# Log a Ku-band transponder for 1 hour, CSV output -python tools/beacon_logger.py --freq 1265000 --sr 20000000 --output beacon.csv - -# 24-hour unattended logging with per-minute statistics -python tools/beacon_logger.py --freq 1265000 --sr 20000000 \ - --output beacon-24h.csv --json-output stats.jsonl --duration 86400 -``` - - - -## Features - -- **Auto-relock**: If signal is lost (weather, dish movement), automatically retunes and relocks -- **Statistics per interval**: min/max/mean/stddev of SNR over each reporting window -- **Dual output**: Raw per-sample CSV + per-interval JSONL statistics -- **Signal handlers**: Clean shutdown on SIGTERM/SIGINT, no data loss -- **Systemd integration**: `--generate-systemd` prints a ready-to-use unit file - -## Options - -| Flag | Default | Description | -|---|---|---| -| `--freq` | (required) | IF frequency in kHz | -| `--sr` | 20000000 | Symbol rate in sps | -| `--mod` | qpsk | Modulation type | -| `--fec` | auto | FEC rate | -| `--output` | — | CSV output file (raw samples) | -| `--json-output` | — | JSONL file (per-interval statistics) | -| `--duration` | 3600 | Logging duration in seconds | -| `--sample-interval` | 1.0 | Seconds between samples | -| `--report-interval` | 60 | Seconds between summary reports | -| `--pol` | — | LNB polarization (H/V) | -| `--band` | — | LNB band (low/high) | -| `--daemon` | — | Suppress stdout for background operation | -| `--generate-systemd` | — | Print systemd unit file and exit | - -## Systemd Daemon Mode - -```bash -# Generate and install the service -python tools/beacon_logger.py --generate-systemd \ - --freq 1265000 --sr 20000000 \ - --output /var/log/skywalker/beacon.csv \ - --duration 999999 > /tmp/beacon-logger.service - -sudo cp /tmp/beacon-logger.service /etc/systemd/system/ -sudo systemctl enable --now beacon-logger -``` - -## Applications - -| Use Case | What to Measure | Interval | -|---|---|---| -| Rain fade | SNR drops during precipitation | 1 Hz | -| LNB thermal drift | AGC shift over temperature cycle | 10s | -| Antenna mount stability | Slow SNR decay over days | 60s | -| Link budget validation | Long-term average vs. predicted | 60s | -| Ionospheric scintillation | Rapid AGC fluctuations | 1 Hz | +--- +title: Beacon Logger +description: Long-term satellite signal logging for propagation research, rain fade analysis, and link budget validation. +--- + +import { Aside, Steps } from '@astrojs/starlight/components'; + +The `beacon_logger.py` tool locks onto a stable satellite transponder and records SNR, AGC levels, +and signal power at configurable intervals. Multi-day datasets reveal rain fade events, diurnal +thermal effects, antenna mount drift, and LNB gain variations. + +## Quick Start + +```bash +# Log a Ku-band transponder for 1 hour, CSV output +python tools/beacon_logger.py --freq 1265000 --sr 20000000 --output beacon.csv + +# 24-hour unattended logging with per-minute statistics +python tools/beacon_logger.py --freq 1265000 --sr 20000000 \ + --output beacon-24h.csv --json-output stats.jsonl --duration 86400 +``` + + + +## Features + +- **Auto-relock**: If signal is lost (weather, dish movement), automatically retunes and relocks +- **Statistics per interval**: min/max/mean/stddev of SNR over each reporting window +- **Dual output**: Raw per-sample CSV + per-interval JSONL statistics +- **Signal handlers**: Clean shutdown on SIGTERM/SIGINT, no data loss +- **Systemd integration**: `--generate-systemd` prints a ready-to-use unit file + +## Options + +| Flag | Default | Description | +|---|---|---| +| `--freq` | (required) | IF frequency in kHz | +| `--sr` | 20000000 | Symbol rate in sps | +| `--mod` | qpsk | Modulation type | +| `--fec` | auto | FEC rate | +| `--output` | — | CSV output file (raw samples) | +| `--json-output` | — | JSONL file (per-interval statistics) | +| `--duration` | 3600 | Logging duration in seconds | +| `--sample-interval` | 1.0 | Seconds between samples | +| `--report-interval` | 60 | Seconds between summary reports | +| `--pol` | — | LNB polarization (H/V) | +| `--band` | — | LNB band (low/high) | +| `--daemon` | — | Suppress stdout for background operation | +| `--generate-systemd` | — | Print systemd unit file and exit | + +## Systemd Daemon Mode + +```bash +# Generate and install the service +python tools/beacon_logger.py --generate-systemd \ + --freq 1265000 --sr 20000000 \ + --output /var/log/skywalker/beacon.csv \ + --duration 999999 > /tmp/beacon-logger.service + +sudo cp /tmp/beacon-logger.service /etc/systemd/system/ +sudo systemctl enable --now beacon-logger +``` + +## Applications + +| Use Case | What to Measure | Interval | +|---|---|---| +| Rain fade | SNR drops during precipitation | 1 Hz | +| LNB thermal drift | AGC shift over temperature cycle | 10s | +| Antenna mount stability | Slow SNR decay over days | 60s | +| Link budget validation | Long-term average vs. predicted | 60s | +| Ionospheric scintillation | Rapid AGC fluctuations | 1 Hz | diff --git a/site/src/content/docs/tools/debugging.mdx b/site/src/content/docs/tools/debugging.mdx index 2d94259..247371d 100644 --- a/site/src/content/docs/tools/debugging.mdx +++ b/site/src/content/docs/tools/debugging.mdx @@ -1,259 +1,259 @@ ---- -title: Debugging Tools -description: Hardware diagnostic tools for boot testing, I2C bus debugging, and firmware extraction. ---- - -import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; - -The `tools/` directory includes a suite of diagnostic scripts developed during the reverse engineering of the SkyWalker-1. These tools were instrumental in isolating the I2C STOP corruption bug in the FX2 controller and verifying correct BCM4500 initialization. - -All debugging tools require `pyusb`, root access, and the kernel `dvb_usb_gp8psk` module to be unloaded. - -```bash -pip install pyusb -sudo modprobe -r dvb_usb_gp8psk gp8psk_fe -``` - - - - -## Boot Testing Tools - -Two scripts test the BCM4500 boot sequence using debug modes in the custom v3.01.0 firmware. - -### test_boot.py -- Full Boot Verification - -Runs a complete BOOT_8PSK sequence and reports success or failure with detailed register readback. - -```bash -sudo python3 tools/test_boot.py -``` - -**What it does:** - - -1. Reads firmware version via `GET_FW_VERS` (0x92) -2. Reads config status via `GET_8PSK_CONFIG` (0x80) -3. Sends `BOOT_8PSK` (0x89, wValue=1) with a 10-second timeout -4. Decodes the 3-byte response: config status, boot stage, probe byte -5. On success: reads BCM4500 direct registers (0xA2--0xA8), indirect registers (pages 0x00--0x0F), I2C diagnostic data, signal strength, and lock status -6. On failure: runs I2C bus scan (0xB4), attempts raw I2C reads to BCM4500 - - -**Boot stage codes:** - -| Stage | Code | Meaning | -|-------|------|---------| -| `NOT_STARTED` | 0x00 | Boot not attempted | -| `GPIO_SETUP` | 0x01 | GPIO pins configured | -| `PWR_SETTLED` | 0x02 | Power-on delay complete | -| `I2C_PROBE` | 0x03 | Probing BCM4500 on I2C | -| `INIT_BLK0` | 0x04 | Writing init block 0 | -| `INIT_BLK1` | 0x05 | Writing init block 1 | -| `INIT_BLK2` | 0x06 | Writing init block 2 | -| `COMPLETE` | 0xFF | Boot finished | - -**Config status flags after successful boot:** - -| Flag | Bit | Expected | -|------|-----|----------| -| `bm8pskStarted` | 0x01 | | -| `bm8pskFW_Loaded` | 0x02 | | - -### test_boot_debug.py -- Incremental Stage Testing - -Sends debug boot modes (wValue=0x80 through 0x83) one at a time to isolate which stage of the boot sequence fails. Can test a single stage or run all sequentially. - -```bash title="Run all debug stages" -sudo python3 tools/test_boot_debug.py -``` - -```bash title="Test only stage 0x82" -sudo python3 tools/test_boot_debug.py 0x82 -``` - -**Debug modes:** - -| wValue | Action | Tests | -|--------|--------|-------| -| `0x80` | No-op: return current state | Firmware responsive? | -| `0x81` | GPIO setup + power + delays (no I2C) | Power rails working? | -| `0x82` | GPIO + I2C bus reset + BCM4500 probe | I2C communication working? | -| `0x83` | GPIO + I2C probe + write init block 0 | Register writes accepted? | - -Each mode returns a 3-byte response: config status, boot stage, and probe byte. The tool reports timing (ms) for each stage and checks if the device is still responsive after failures. - - - - -## I2C Debugging Suite - -Three scripts that implement a progressive isolation methodology to identify I2C bus faults. These were developed in sequence, each informed by the results of the previous one. - -### Methodology - - -1. **Broad scan** (`test_i2c_debug.py`) -- Power on the BCM4500 and probe the entire I2C bus. Does the chip respond at any address? -2. **Isolation** (`test_i2c_isolate.py`) -- Determine whether the fault is in the BCM4500, the I2C bus, or the FX2 controller. Test the chip when already powered vs. after a reset. -3. **Pinpoint** (`test_i2c_pinpoint.py`) -- Identify the exact operation that corrupts communication. Compare I2C read-only, GPIO+reset, and GPIO+bus-reset+probe modes. - - -### test_i2c_debug.py -- Bus Discovery - -Powers on the BCM4500 via GPIO-only mode (0x81), then runs progressively detailed diagnostics. - -```bash -sudo python3 tools/test_i2c_debug.py -``` - -**Test sequence:** - -| Step | Action | Purpose | -|------|--------|---------| -| 1 | Send mode 0x81 (GPIO power on) | Start BCM4500 without any I2C | -| 2 | I2C bus scan (0xB4) immediately | Check for devices right after power-on | -| 3 | Wait 500 ms, scan again | Maybe chip needs more time? | -| 4 | Raw I2C reads to candidate addresses | Try 0x08, 0x09, 0x0A, 0x0B, 0x10, 0x11, 0x68, 0x69, 0x60, 0x61 | -| 5 | Check I2C controller state | Observe bus health | -| 6 | Send mode 0x82 after 1s delay | Test probe with extra settling time | - -**Candidate addresses** cover different BCM4500 pin-strap configurations and common demodulator/tuner addresses. - -### test_i2c_isolate.py -- Fault Isolation - -Determines whether the failure is caused by `bcm_direct_read`, re-reset timing, or the I2C bus reset (bmSTOP). - -```bash -sudo python3 tools/test_i2c_isolate.py -``` - -**Test sequence:** - -| Test | Action | Question | -|------|--------|----------| -| **A** | Power on (0x81), wait 1s, raw read | Is BCM4500 alive from cold start? | -| **B** | Run mode 0x82 (re-resets + probe) | Does `bcm_direct_read` work after reset? | -| **C** | Immediately raw read after 0x82 | Is I2C function itself broken? | -| **D** | Raw reads at 100, 200, 500, 1000, 2000 ms | Is it a timing issue? | -| **E** | Re-power (0x81), wait, then 0x82 | Repeatable? | - -### test_i2c_pinpoint.py -- Root Cause Identification - -The definitive test that identified the I2C STOP corruption bug. Compares three modes on a chip that is already proven alive. - -```bash -sudo python3 tools/test_i2c_pinpoint.py -``` - -**Test sequence:** - - -1. Power on BCM4500 via mode 0x81, confirm alive with raw read -2. **Mode 0x84**: `bcm_direct_read` only (no GPIO, chip already powered) -- tests if the read function itself is broken -3. **Mode 0x85**: GPIO + reset + power but NO I2C bus reset (no `bmSTOP`) -- tests if re-reset breaks things -4. **Mode 0x82**: GPIO + I2C `bmSTOP` + reset + power + probe -- the mode that was failing - - -**Interpretation:** - -| Result Pattern | Diagnosis | -|----------------|-----------| -| 0x84 works, 0x85 works, 0x82 fails | `bmSTOP` (I2C bus reset) is the culprit | -| 0x84 fails | `bcm_direct_read` has a bug | -| 0x85 fails | Re-reset timing needs more delay | - - - - - - -## Windows Memory Dump (wine_memdump.py) - -Extracts firmware from the Genpix Windows driver/updater by running it under Wine and dumping process memory. The Windows updater executable contains embedded firmware images that are unpacked into memory at runtime. - - - -```bash -sudo python3 tools/wine_memdump.py SkyWalkerUpdater.exe -``` - -```bash title="Attach to an already-running Wine process" -sudo python3 tools/wine_memdump.py SkyWalkerUpdater.exe --skip-launch -``` - -```bash title="Custom output directory and wait time" -sudo python3 tools/wine_memdump.py SkyWalkerUpdater.exe -o dumps/ --wait 5 -``` - -### Options - -| Flag | Description | -|------|-------------| -| `EXE` | Windows PE executable to run under Wine (positional, required) | -| `-o, --output-dir DIR` | Output directory for dumps (default: `.`) | -| `--wait SECONDS` | Seconds to wait after launch for unpacking (default: `3`) | -| `--skip-launch` | Attach to an already-running Wine process | - -### How It Works - - -1. Launches the `.exe` under Wine with `WINEDEBUG=-all` to suppress noise -2. Waits for the process to unpack (configurable delay) -3. Finds the Wine process PID by scanning `/proc` -4. Reads `/proc/PID/maps` to identify readable memory regions -5. Reads each region from `/proc/PID/mem` into a contiguous dump -6. Saves the full dump (`wine_memdump.bin`) and region map (`wine_memdump_regions.txt`) -7. Searches the dump for firmware signatures - - -### Firmware Signature Search - -The tool searches for 7 categories of signatures: - -| Search | Pattern | Purpose | -|--------|---------|---------| -| C2 EEPROM headers | `C2 C0 09 03 02` | Find embedded firmware images | -| FX2 init sequence | `78 7F E4 F6 D8 FD 75 81` | FX2 RAM clear/init code | -| RAM clear pattern | `78 7F E4 F6 D8 FD` | Partial match variant | -| C2 load records | LEN + addr `0x0000` + `LJMP` | Record chain starts | -| VID references | `C0 09` near `03 02` | VID/PID byte pairs | -| Version strings | `2.13`, `SkyWalker`, `Genpix`, `8PSK`, etc. | Text markers | -| USB transfer setup | `0x40 0xA0`, `0x40 0x83`, `0x40 0x84` | WinUSB vendor request patterns | - -Each hit is reported with the dump offset, virtual address, containing memory region, and context bytes. - -### Output Files - -| File | Contents | -|------|----------| -| `wine_memdump.bin` | Full concatenated memory dump | -| `wine_memdump_regions.txt` | Region map with virtual addresses, permissions, and dump offsets | - - - - -## Diagnostic Command Reference - -The custom v3.01.0 firmware adds these diagnostic vendor commands used by the test scripts: - -| Command | Code | Direction | Purpose | -|---------|------|-----------|---------| -| `I2C_BUS_SCAN` | 0xB4 | IN | Probe all 128 I2C addresses, return 16-byte bitmap | -| `I2C_RAW_READ` | 0xB5 | IN | Read from any I2C device (wValue=addr, wIndex=reg) | -| `I2C_DIAG` | 0xB6 | IN | Step-by-step indirect register read with intermediate values | -| `RAW_DEMOD_READ` | 0xB1 | IN | Read any BCM4500 indirect register | -| `RAW_DEMOD_WRITE` | 0xB2 | OUT | Write any BCM4500 indirect register | - -These commands are only available when running the custom firmware. Stock firmware will STALL on these command codes. - -## See Also - -- [I2C STOP Corruption Bug](/i2c/stop-corruption-bug/) -- the bug these tools helped discover -- [Boot Sequence](/usb/boot-sequence/) -- the initialization flow being tested -- [Custom Firmware v3.01.0](/firmware/custom-v301/) -- the firmware that enables debug modes -- [I2C Bus Architecture](/i2c/bus-architecture/) -- I2C protocol and controller details -- [Firmware Loader](/tools/firmware-loader/) -- load custom firmware before running debug tools +--- +title: Debugging Tools +description: Hardware diagnostic tools for boot testing, I2C bus debugging, and firmware extraction. +--- + +import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; + +The `tools/` directory includes a suite of diagnostic scripts developed during the reverse engineering of the SkyWalker-1. These tools were instrumental in isolating the I2C STOP corruption bug in the FX2 controller and verifying correct BCM4500 initialization. + +All debugging tools require `pyusb`, root access, and the kernel `dvb_usb_gp8psk` module to be unloaded. + +```bash +pip install pyusb +sudo modprobe -r dvb_usb_gp8psk gp8psk_fe +``` + + + + +## Boot Testing Tools + +Two scripts test the BCM4500 boot sequence using debug modes in the custom v3.01.0 firmware. + +### test_boot.py -- Full Boot Verification + +Runs a complete BOOT_8PSK sequence and reports success or failure with detailed register readback. + +```bash +sudo python3 tools/test_boot.py +``` + +**What it does:** + + +1. Reads firmware version via `GET_FW_VERS` (0x92) +2. Reads config status via `GET_8PSK_CONFIG` (0x80) +3. Sends `BOOT_8PSK` (0x89, wValue=1) with a 10-second timeout +4. Decodes the 3-byte response: config status, boot stage, probe byte +5. On success: reads BCM4500 direct registers (0xA2--0xA8), indirect registers (pages 0x00--0x0F), I2C diagnostic data, signal strength, and lock status +6. On failure: runs I2C bus scan (0xB4), attempts raw I2C reads to BCM4500 + + +**Boot stage codes:** + +| Stage | Code | Meaning | +|-------|------|---------| +| `NOT_STARTED` | 0x00 | Boot not attempted | +| `GPIO_SETUP` | 0x01 | GPIO pins configured | +| `PWR_SETTLED` | 0x02 | Power-on delay complete | +| `I2C_PROBE` | 0x03 | Probing BCM4500 on I2C | +| `INIT_BLK0` | 0x04 | Writing init block 0 | +| `INIT_BLK1` | 0x05 | Writing init block 1 | +| `INIT_BLK2` | 0x06 | Writing init block 2 | +| `COMPLETE` | 0xFF | Boot finished | + +**Config status flags after successful boot:** + +| Flag | Bit | Expected | +|------|-----|----------| +| `bm8pskStarted` | 0x01 | | +| `bm8pskFW_Loaded` | 0x02 | | + +### test_boot_debug.py -- Incremental Stage Testing + +Sends debug boot modes (wValue=0x80 through 0x83) one at a time to isolate which stage of the boot sequence fails. Can test a single stage or run all sequentially. + +```bash title="Run all debug stages" +sudo python3 tools/test_boot_debug.py +``` + +```bash title="Test only stage 0x82" +sudo python3 tools/test_boot_debug.py 0x82 +``` + +**Debug modes:** + +| wValue | Action | Tests | +|--------|--------|-------| +| `0x80` | No-op: return current state | Firmware responsive? | +| `0x81` | GPIO setup + power + delays (no I2C) | Power rails working? | +| `0x82` | GPIO + I2C bus reset + BCM4500 probe | I2C communication working? | +| `0x83` | GPIO + I2C probe + write init block 0 | Register writes accepted? | + +Each mode returns a 3-byte response: config status, boot stage, and probe byte. The tool reports timing (ms) for each stage and checks if the device is still responsive after failures. + + + + +## I2C Debugging Suite + +Three scripts that implement a progressive isolation methodology to identify I2C bus faults. These were developed in sequence, each informed by the results of the previous one. + +### Methodology + + +1. **Broad scan** (`test_i2c_debug.py`) -- Power on the BCM4500 and probe the entire I2C bus. Does the chip respond at any address? +2. **Isolation** (`test_i2c_isolate.py`) -- Determine whether the fault is in the BCM4500, the I2C bus, or the FX2 controller. Test the chip when already powered vs. after a reset. +3. **Pinpoint** (`test_i2c_pinpoint.py`) -- Identify the exact operation that corrupts communication. Compare I2C read-only, GPIO+reset, and GPIO+bus-reset+probe modes. + + +### test_i2c_debug.py -- Bus Discovery + +Powers on the BCM4500 via GPIO-only mode (0x81), then runs progressively detailed diagnostics. + +```bash +sudo python3 tools/test_i2c_debug.py +``` + +**Test sequence:** + +| Step | Action | Purpose | +|------|--------|---------| +| 1 | Send mode 0x81 (GPIO power on) | Start BCM4500 without any I2C | +| 2 | I2C bus scan (0xB4) immediately | Check for devices right after power-on | +| 3 | Wait 500 ms, scan again | Maybe chip needs more time? | +| 4 | Raw I2C reads to candidate addresses | Try 0x08, 0x09, 0x0A, 0x0B, 0x10, 0x11, 0x68, 0x69, 0x60, 0x61 | +| 5 | Check I2C controller state | Observe bus health | +| 6 | Send mode 0x82 after 1s delay | Test probe with extra settling time | + +**Candidate addresses** cover different BCM4500 pin-strap configurations and common demodulator/tuner addresses. + +### test_i2c_isolate.py -- Fault Isolation + +Determines whether the failure is caused by `bcm_direct_read`, re-reset timing, or the I2C bus reset (bmSTOP). + +```bash +sudo python3 tools/test_i2c_isolate.py +``` + +**Test sequence:** + +| Test | Action | Question | +|------|--------|----------| +| **A** | Power on (0x81), wait 1s, raw read | Is BCM4500 alive from cold start? | +| **B** | Run mode 0x82 (re-resets + probe) | Does `bcm_direct_read` work after reset? | +| **C** | Immediately raw read after 0x82 | Is I2C function itself broken? | +| **D** | Raw reads at 100, 200, 500, 1000, 2000 ms | Is it a timing issue? | +| **E** | Re-power (0x81), wait, then 0x82 | Repeatable? | + +### test_i2c_pinpoint.py -- Root Cause Identification + +The definitive test that identified the I2C STOP corruption bug. Compares three modes on a chip that is already proven alive. + +```bash +sudo python3 tools/test_i2c_pinpoint.py +``` + +**Test sequence:** + + +1. Power on BCM4500 via mode 0x81, confirm alive with raw read +2. **Mode 0x84**: `bcm_direct_read` only (no GPIO, chip already powered) -- tests if the read function itself is broken +3. **Mode 0x85**: GPIO + reset + power but NO I2C bus reset (no `bmSTOP`) -- tests if re-reset breaks things +4. **Mode 0x82**: GPIO + I2C `bmSTOP` + reset + power + probe -- the mode that was failing + + +**Interpretation:** + +| Result Pattern | Diagnosis | +|----------------|-----------| +| 0x84 works, 0x85 works, 0x82 fails | `bmSTOP` (I2C bus reset) is the culprit | +| 0x84 fails | `bcm_direct_read` has a bug | +| 0x85 fails | Re-reset timing needs more delay | + + + + + + +## Windows Memory Dump (wine_memdump.py) + +Extracts firmware from the Genpix Windows driver/updater by running it under Wine and dumping process memory. The Windows updater executable contains embedded firmware images that are unpacked into memory at runtime. + + + +```bash +sudo python3 tools/wine_memdump.py SkyWalkerUpdater.exe +``` + +```bash title="Attach to an already-running Wine process" +sudo python3 tools/wine_memdump.py SkyWalkerUpdater.exe --skip-launch +``` + +```bash title="Custom output directory and wait time" +sudo python3 tools/wine_memdump.py SkyWalkerUpdater.exe -o dumps/ --wait 5 +``` + +### Options + +| Flag | Description | +|------|-------------| +| `EXE` | Windows PE executable to run under Wine (positional, required) | +| `-o, --output-dir DIR` | Output directory for dumps (default: `.`) | +| `--wait SECONDS` | Seconds to wait after launch for unpacking (default: `3`) | +| `--skip-launch` | Attach to an already-running Wine process | + +### How It Works + + +1. Launches the `.exe` under Wine with `WINEDEBUG=-all` to suppress noise +2. Waits for the process to unpack (configurable delay) +3. Finds the Wine process PID by scanning `/proc` +4. Reads `/proc/PID/maps` to identify readable memory regions +5. Reads each region from `/proc/PID/mem` into a contiguous dump +6. Saves the full dump (`wine_memdump.bin`) and region map (`wine_memdump_regions.txt`) +7. Searches the dump for firmware signatures + + +### Firmware Signature Search + +The tool searches for 7 categories of signatures: + +| Search | Pattern | Purpose | +|--------|---------|---------| +| C2 EEPROM headers | `C2 C0 09 03 02` | Find embedded firmware images | +| FX2 init sequence | `78 7F E4 F6 D8 FD 75 81` | FX2 RAM clear/init code | +| RAM clear pattern | `78 7F E4 F6 D8 FD` | Partial match variant | +| C2 load records | LEN + addr `0x0000` + `LJMP` | Record chain starts | +| VID references | `C0 09` near `03 02` | VID/PID byte pairs | +| Version strings | `2.13`, `SkyWalker`, `Genpix`, `8PSK`, etc. | Text markers | +| USB transfer setup | `0x40 0xA0`, `0x40 0x83`, `0x40 0x84` | WinUSB vendor request patterns | + +Each hit is reported with the dump offset, virtual address, containing memory region, and context bytes. + +### Output Files + +| File | Contents | +|------|----------| +| `wine_memdump.bin` | Full concatenated memory dump | +| `wine_memdump_regions.txt` | Region map with virtual addresses, permissions, and dump offsets | + + + + +## Diagnostic Command Reference + +The custom v3.01.0 firmware adds these diagnostic vendor commands used by the test scripts: + +| Command | Code | Direction | Purpose | +|---------|------|-----------|---------| +| `I2C_BUS_SCAN` | 0xB4 | IN | Probe all 128 I2C addresses, return 16-byte bitmap | +| `I2C_RAW_READ` | 0xB5 | IN | Read from any I2C device (wValue=addr, wIndex=reg) | +| `I2C_DIAG` | 0xB6 | IN | Step-by-step indirect register read with intermediate values | +| `RAW_DEMOD_READ` | 0xB1 | IN | Read any BCM4500 indirect register | +| `RAW_DEMOD_WRITE` | 0xB2 | OUT | Write any BCM4500 indirect register | + +These commands are only available when running the custom firmware. Stock firmware will STALL on these command codes. + +## See Also + +- [I2C STOP Corruption Bug](/i2c/stop-corruption-bug/) -- the bug these tools helped discover +- [Boot Sequence](/usb/boot-sequence/) -- the initialization flow being tested +- [Custom Firmware v3.01.0](/firmware/custom-v301/) -- the firmware that enables debug modes +- [I2C Bus Architecture](/i2c/bus-architecture/) -- I2C protocol and controller details +- [Firmware Loader](/tools/firmware-loader/) -- load custom firmware before running debug tools diff --git a/site/src/content/docs/tools/eeprom-utilities.mdx b/site/src/content/docs/tools/eeprom-utilities.mdx index af64c15..1bdc4c6 100644 --- a/site/src/content/docs/tools/eeprom-utilities.mdx +++ b/site/src/content/docs/tools/eeprom-utilities.mdx @@ -1,266 +1,266 @@ ---- -title: EEPROM Utilities -description: EEPROM read, write, probe, and verification tools for the FX2 configuration EEPROM. ---- - -import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; - -Three tools manage the SkyWalker-1's I2C EEPROM (24Cxx-family at address `0x51`): `eeprom_write.py` for flashing firmware images, `eeprom_dump.py` for reading EEPROM contents, and `eeprom_probe.py` for testing I2C addressing methods. - -The EEPROM stores the Cypress FX2 boot firmware in C2 IIC format. The FX2 loads this firmware automatically on every power-up. Writing incorrect data to the EEPROM will prevent the device from enumerating on USB. - - - -```bash -pip install pyusb -``` - - - - -## EEPROM Flash Tool - -`eeprom_write.py` is the full-featured EEPROM management tool with four subcommands. - -### Subcommands - -| Subcommand | Purpose | -|------------|---------| -| `info` | Parse and display C2 header from a `.bin` file (offline, no device needed) | -| `backup` | Dump current EEPROM contents to a file | -| `verify` | Compare a `.bin` file against current EEPROM contents | -| `flash` | Write a C2 firmware image to the EEPROM | - -### info -- Inspect a C2 Image File - -Parse and display the C2 header and load records from a firmware binary without connecting to the device. - -```bash -python3 tools/eeprom_write.py info firmware.bin -``` - -``` -C2 Image: firmware.bin -File size: 9512 bytes -======================================== - -Header: - Format: C2 (Large EEPROM, code loads to internal RAM) - VID: 0x09C0 (Genpix) - PID: 0x0203 (SkyWalker-1) - DID: 0x0000 - Config: 0x40 (400kHz I2C) - -Load Records: - [0] 1023 bytes -> 0x0000-0x03FE [02 18 8d 78 7f e4 f6 d8...] - [1] 1023 bytes -> 0x03FF-0x07FD [...] - ... - [9] 115 bytes -> 0x23F7-0x2469 [...] - [10] END MARKER -> entry point: 0xE600 - - Total firmware: 9472 bytes in 10 segments - Entry point: 0xE600 (LJMP target after boot) - - EEPROM footprint: 9512 bytes (0x2528) -``` - -### backup -- Read EEPROM to File - -```bash -sudo python3 tools/eeprom_write.py backup -o my_backup.bin -``` - -```bash title="Specify read size" -sudo python3 tools/eeprom_write.py backup -o my_backup.bin --max-size 16384 -``` - -| Flag | Description | -|------|-------------| -| `-o, --output FILE` | Output file (default: `skywalker1_eeprom.bin`) | -| `--max-size BYTES` | Maximum bytes to read (default: `16384`) | - -### verify -- Compare Image Against EEPROM - -Read the EEPROM and compare byte-by-byte against a local `.bin` file. Reports any mismatches with offset and expected vs. actual values. - -```bash -sudo python3 tools/eeprom_write.py verify firmware.bin -``` - -Exit code `0` = match, `1` = mismatch. - -### flash -- Write Image to EEPROM - -The flash workflow includes built-in safety measures: image validation, VID/PID check, automatic backup, countdown timer, write verification. - - -1. **Validate** the C2 image file (header format, VID/PID match, record integrity) -2. **Connect** to the SkyWalker-1 and check device VID/PID against the image -3. **Backup** the current EEPROM contents to a timestamped file -4. **Wait** 3 seconds with a countdown (Ctrl-C to abort) -5. **Write** the image in 16-byte page-aligned chunks with 10 ms write cycle delays -6. **Verify** by reading back and comparing every byte - - -```bash title="Flash with all safety checks" -sudo python3 tools/eeprom_write.py flash firmware.bin -``` - -```bash title="Dry run (shows what would happen, writes nothing)" -sudo python3 tools/eeprom_write.py flash firmware.bin --dry-run -``` - -```bash title="Skip backup (not recommended)" -sudo python3 tools/eeprom_write.py flash firmware.bin --no-backup -``` - -```bash title="Force flash with VID/PID mismatch" -sudo python3 tools/eeprom_write.py flash firmware.bin --force -``` - -| Flag | Description | -|------|-------------| -| `FILE` | C2 firmware image (positional, required) | -| `--dry-run` | Validate and show plan without writing | -| `--no-backup` | Skip pre-flash EEPROM backup | -| `--force` | Override VID/PID mismatch check | - -### EEPROM Write Parameters - -| Parameter | Value | -|-----------|-------| -| I2C slave address | `0x51` (7-bit) | -| Vendor command (write) | `I2C_WRITE` (`0x83`) | -| Vendor command (read) | `I2C_READ` (`0x84`) | -| Page size | 16 bytes (conservative for 24Cxx) | -| Write cycle time | 10 ms per page | -| Maximum image size | 16,384 bytes (16 KB) | - - - - -## EEPROM Dump Tool - -`eeprom_dump.py` is a simpler read-only tool focused on extracting and analyzing EEPROM contents. - -```bash -sudo python3 tools/eeprom_dump.py -o eeprom.bin -``` - -```bash title="Extract flat binary + 64K Ghidra image" -sudo python3 tools/eeprom_dump.py -o eeprom.bin --extract -``` - -### Options - -| Flag | Description | -|------|-------------| -| `-o, --output FILE` | Output file (default: `skywalker1_eeprom.bin`) | -| `--extract` | Extract firmware as flat binary + full 64K image | -| `--max-size BYTES` | Maximum EEPROM size to read (default: `16384`) | - -### Auto-Detect End of Data - -The tool detects the end of valid data by watching for consecutive `0xFF` chunks (4 or more = end of programmed region). This avoids reading the entire EEPROM when only the first few KB contain firmware. - -### Extract Mode (--extract) - -When `--extract` is specified, the tool additionally produces: - -- **`*_flat.bin`** -- firmware code extracted from C2 records, stored as a flat binary covering only the used address range -- **`*_full64k.bin`** -- full 64 KB memory image (unused regions filled with `0xFF`), suitable for loading in Ghidra at base address `0x0000` - -### Output - -``` -Genpix SkyWalker-1 EEPROM Dump -======================================== -Found device: Bus 1 Addr 12 - -Reading EEPROM (max 16384 bytes)... - End of data at 0x2600 (0xFF padding) - Read 9728 bytes total - Saved raw EEPROM to: eeprom.bin - -======================================== -EEPROM Header: - Format: C2 (Large EEPROM, code loads to internal RAM) - VID: 0x09C0 (Genpix) - PID: 0x0203 (SkyWalker-1) - DID: 0x0000 - Config: 0x40 (400kHz I2C) - -Load Records: - [0] 1023 bytes -> 0x0000-0x03FE [02 18 8d 78 7f e4 f6 d8...] - ... - [10] END MARKER -> entry point: 0xE600 - - Total firmware: 9472 bytes in 10 records - Entry point: 0xE600 (LJMP target after boot) -``` - - - - -## EEPROM Address Probe - -`eeprom_probe.py` is a diagnostic script that tests different I2C addressing methods to determine how the Genpix firmware's `I2C_READ`/`I2C_WRITE` commands interpret `wValue` and `wIndex` parameters for EEPROM access. - -This is a developer tool used during reverse engineering. It runs 7 different addressing approaches and checks each result against known reference bytes from the EEPROM header. - -```bash -sudo python3 tools/eeprom_probe.py -``` - -### Test Approaches - -| Approach | Method | Description | -|----------|--------|-------------| -| 1 | `I2C_WRITE data=[addr_h, addr_l]` then `I2C_READ` | Set EEPROM offset via write data | -| 2 | `wValue=addr, wIndex=slave` | Reversed parameter mapping | -| 3 | `wValue=slave, wIndex=addr` | Standard Genpix mapping | -| 4 | `I2C_READ wIndex=offset` | Offset in wIndex field | -| 5 | `wValue=(slave<<8\|offset)` | Packed slave + offset | -| 6 | `wValue=offset (no slave)` | Offset only, no slave address | -| 7 | Larger reads (64 bytes) | Verify sequential data across page boundaries | - -### Known Reference Bytes - -The script compares against two known patterns: - -- **Offset 0x0000**: `C2 C0 09 03 02 00 00 40` (C2 header: marker, VID, PID, DID, config) -- **Offset 0x0008**: `03 FF 00 00 02 18 8D 30` (first load record) - - - - - - -## Cypress C2 Boot Format - -The EEPROM stores firmware in the Cypress C2 IIC second-stage boot format: - -| Field | Offset | Size | Description | -|-------|--------|------|-------------| -| Marker | 0 | 1 | `0xC2` (large EEPROM, external memory) | -| VID | 1 | 2 | USB Vendor ID, little-endian (`0x09C0`) | -| PID | 3 | 2 | USB Product ID, little-endian (`0x0203`) | -| DID | 5 | 2 | Device ID, little-endian | -| Config | 7 | 1 | Boot config (`0x40` = 400 kHz I2C) | -| Records | 8+ | var | Code segments: `[len_hi][len_lo][addr_hi][addr_lo][data...]` | -| End | last | 4 | `[0x80][0x01][entry_hi][entry_lo]` (entry point = `0xE600`) | - -For a full description of the storage format, see [Firmware Storage Formats](/firmware/storage-formats/). - -## See Also - -- [Firmware Loader](/tools/firmware-loader/) -- RAM-based firmware loading (non-destructive) -- [Storage Formats](/firmware/storage-formats/) -- C2 format, hexline, and FW02 chunk details -- [Version Comparison](/firmware/version-comparison/) -- differences across firmware versions -- [I2C Bus Architecture](/i2c/bus-architecture/) -- I2C protocol details +--- +title: EEPROM Utilities +description: EEPROM read, write, probe, and verification tools for the FX2 configuration EEPROM. +--- + +import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; + +Three tools manage the SkyWalker-1's I2C EEPROM (24Cxx-family at address `0x51`): `eeprom_write.py` for flashing firmware images, `eeprom_dump.py` for reading EEPROM contents, and `eeprom_probe.py` for testing I2C addressing methods. + +The EEPROM stores the Cypress FX2 boot firmware in C2 IIC format. The FX2 loads this firmware automatically on every power-up. Writing incorrect data to the EEPROM will prevent the device from enumerating on USB. + + + +```bash +pip install pyusb +``` + + + + +## EEPROM Flash Tool + +`eeprom_write.py` is the full-featured EEPROM management tool with four subcommands. + +### Subcommands + +| Subcommand | Purpose | +|------------|---------| +| `info` | Parse and display C2 header from a `.bin` file (offline, no device needed) | +| `backup` | Dump current EEPROM contents to a file | +| `verify` | Compare a `.bin` file against current EEPROM contents | +| `flash` | Write a C2 firmware image to the EEPROM | + +### info -- Inspect a C2 Image File + +Parse and display the C2 header and load records from a firmware binary without connecting to the device. + +```bash +python3 tools/eeprom_write.py info firmware.bin +``` + +``` +C2 Image: firmware.bin +File size: 9512 bytes +======================================== + +Header: + Format: C2 (Large EEPROM, code loads to internal RAM) + VID: 0x09C0 (Genpix) + PID: 0x0203 (SkyWalker-1) + DID: 0x0000 + Config: 0x40 (400kHz I2C) + +Load Records: + [0] 1023 bytes -> 0x0000-0x03FE [02 18 8d 78 7f e4 f6 d8...] + [1] 1023 bytes -> 0x03FF-0x07FD [...] + ... + [9] 115 bytes -> 0x23F7-0x2469 [...] + [10] END MARKER -> entry point: 0xE600 + + Total firmware: 9472 bytes in 10 segments + Entry point: 0xE600 (LJMP target after boot) + + EEPROM footprint: 9512 bytes (0x2528) +``` + +### backup -- Read EEPROM to File + +```bash +sudo python3 tools/eeprom_write.py backup -o my_backup.bin +``` + +```bash title="Specify read size" +sudo python3 tools/eeprom_write.py backup -o my_backup.bin --max-size 16384 +``` + +| Flag | Description | +|------|-------------| +| `-o, --output FILE` | Output file (default: `skywalker1_eeprom.bin`) | +| `--max-size BYTES` | Maximum bytes to read (default: `16384`) | + +### verify -- Compare Image Against EEPROM + +Read the EEPROM and compare byte-by-byte against a local `.bin` file. Reports any mismatches with offset and expected vs. actual values. + +```bash +sudo python3 tools/eeprom_write.py verify firmware.bin +``` + +Exit code `0` = match, `1` = mismatch. + +### flash -- Write Image to EEPROM + +The flash workflow includes built-in safety measures: image validation, VID/PID check, automatic backup, countdown timer, write verification. + + +1. **Validate** the C2 image file (header format, VID/PID match, record integrity) +2. **Connect** to the SkyWalker-1 and check device VID/PID against the image +3. **Backup** the current EEPROM contents to a timestamped file +4. **Wait** 3 seconds with a countdown (Ctrl-C to abort) +5. **Write** the image in 16-byte page-aligned chunks with 10 ms write cycle delays +6. **Verify** by reading back and comparing every byte + + +```bash title="Flash with all safety checks" +sudo python3 tools/eeprom_write.py flash firmware.bin +``` + +```bash title="Dry run (shows what would happen, writes nothing)" +sudo python3 tools/eeprom_write.py flash firmware.bin --dry-run +``` + +```bash title="Skip backup (not recommended)" +sudo python3 tools/eeprom_write.py flash firmware.bin --no-backup +``` + +```bash title="Force flash with VID/PID mismatch" +sudo python3 tools/eeprom_write.py flash firmware.bin --force +``` + +| Flag | Description | +|------|-------------| +| `FILE` | C2 firmware image (positional, required) | +| `--dry-run` | Validate and show plan without writing | +| `--no-backup` | Skip pre-flash EEPROM backup | +| `--force` | Override VID/PID mismatch check | + +### EEPROM Write Parameters + +| Parameter | Value | +|-----------|-------| +| I2C slave address | `0x51` (7-bit) | +| Vendor command (write) | `I2C_WRITE` (`0x83`) | +| Vendor command (read) | `I2C_READ` (`0x84`) | +| Page size | 16 bytes (conservative for 24Cxx) | +| Write cycle time | 10 ms per page | +| Maximum image size | 16,384 bytes (16 KB) | + + + + +## EEPROM Dump Tool + +`eeprom_dump.py` is a simpler read-only tool focused on extracting and analyzing EEPROM contents. + +```bash +sudo python3 tools/eeprom_dump.py -o eeprom.bin +``` + +```bash title="Extract flat binary + 64K Ghidra image" +sudo python3 tools/eeprom_dump.py -o eeprom.bin --extract +``` + +### Options + +| Flag | Description | +|------|-------------| +| `-o, --output FILE` | Output file (default: `skywalker1_eeprom.bin`) | +| `--extract` | Extract firmware as flat binary + full 64K image | +| `--max-size BYTES` | Maximum EEPROM size to read (default: `16384`) | + +### Auto-Detect End of Data + +The tool detects the end of valid data by watching for consecutive `0xFF` chunks (4 or more = end of programmed region). This avoids reading the entire EEPROM when only the first few KB contain firmware. + +### Extract Mode (--extract) + +When `--extract` is specified, the tool additionally produces: + +- **`*_flat.bin`** -- firmware code extracted from C2 records, stored as a flat binary covering only the used address range +- **`*_full64k.bin`** -- full 64 KB memory image (unused regions filled with `0xFF`), suitable for loading in Ghidra at base address `0x0000` + +### Output + +``` +Genpix SkyWalker-1 EEPROM Dump +======================================== +Found device: Bus 1 Addr 12 + +Reading EEPROM (max 16384 bytes)... + End of data at 0x2600 (0xFF padding) + Read 9728 bytes total + Saved raw EEPROM to: eeprom.bin + +======================================== +EEPROM Header: + Format: C2 (Large EEPROM, code loads to internal RAM) + VID: 0x09C0 (Genpix) + PID: 0x0203 (SkyWalker-1) + DID: 0x0000 + Config: 0x40 (400kHz I2C) + +Load Records: + [0] 1023 bytes -> 0x0000-0x03FE [02 18 8d 78 7f e4 f6 d8...] + ... + [10] END MARKER -> entry point: 0xE600 + + Total firmware: 9472 bytes in 10 records + Entry point: 0xE600 (LJMP target after boot) +``` + + + + +## EEPROM Address Probe + +`eeprom_probe.py` is a diagnostic script that tests different I2C addressing methods to determine how the Genpix firmware's `I2C_READ`/`I2C_WRITE` commands interpret `wValue` and `wIndex` parameters for EEPROM access. + +This is a developer tool used during reverse engineering. It runs 7 different addressing approaches and checks each result against known reference bytes from the EEPROM header. + +```bash +sudo python3 tools/eeprom_probe.py +``` + +### Test Approaches + +| Approach | Method | Description | +|----------|--------|-------------| +| 1 | `I2C_WRITE data=[addr_h, addr_l]` then `I2C_READ` | Set EEPROM offset via write data | +| 2 | `wValue=addr, wIndex=slave` | Reversed parameter mapping | +| 3 | `wValue=slave, wIndex=addr` | Standard Genpix mapping | +| 4 | `I2C_READ wIndex=offset` | Offset in wIndex field | +| 5 | `wValue=(slave<<8\|offset)` | Packed slave + offset | +| 6 | `wValue=offset (no slave)` | Offset only, no slave address | +| 7 | Larger reads (64 bytes) | Verify sequential data across page boundaries | + +### Known Reference Bytes + +The script compares against two known patterns: + +- **Offset 0x0000**: `C2 C0 09 03 02 00 00 40` (C2 header: marker, VID, PID, DID, config) +- **Offset 0x0008**: `03 FF 00 00 02 18 8D 30` (first load record) + + + + + + +## Cypress C2 Boot Format + +The EEPROM stores firmware in the Cypress C2 IIC second-stage boot format: + +| Field | Offset | Size | Description | +|-------|--------|------|-------------| +| Marker | 0 | 1 | `0xC2` (large EEPROM, external memory) | +| VID | 1 | 2 | USB Vendor ID, little-endian (`0x09C0`) | +| PID | 3 | 2 | USB Product ID, little-endian (`0x0203`) | +| DID | 5 | 2 | Device ID, little-endian | +| Config | 7 | 1 | Boot config (`0x40` = 400 kHz I2C) | +| Records | 8+ | var | Code segments: `[len_hi][len_lo][addr_hi][addr_lo][data...]` | +| End | last | 4 | `[0x80][0x01][entry_hi][entry_lo]` (entry point = `0xE600`) | + +For a full description of the storage format, see [Firmware Storage Formats](/firmware/storage-formats/). + +## See Also + +- [Firmware Loader](/tools/firmware-loader/) -- RAM-based firmware loading (non-destructive) +- [Storage Formats](/firmware/storage-formats/) -- C2 format, hexline, and FW02 chunk details +- [Version Comparison](/firmware/version-comparison/) -- differences across firmware versions +- [I2C Bus Architecture](/i2c/bus-architecture/) -- I2C protocol details diff --git a/site/src/content/docs/tools/firmware-loader.mdx b/site/src/content/docs/tools/firmware-loader.mdx index a27d71a..88978ac 100644 --- a/site/src/content/docs/tools/firmware-loader.mdx +++ b/site/src/content/docs/tools/firmware-loader.mdx @@ -1,255 +1,255 @@ ---- -title: Firmware Loader -description: RAM firmware loader and firmware dump/probe utilities for the Genpix SkyWalker-1. ---- - -import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; - -Two complementary tools handle firmware on the SkyWalker-1: `fw_load.py` loads firmware into FX2 RAM for testing, and `fw_dump.py` extracts firmware and device information from a running device. - -Both tools require `pyusb` and typically need root access (or appropriate udev rules) to communicate with the USB device. - -```bash -pip install pyusb -``` - - - - - - -## RAM Firmware Loader - -`fw_load.py` loads firmware into the Cypress FX2 internal/external RAM via the standard `0xA0` vendor request built into the FX2 silicon boot ROM. This is the primary tool for firmware development: load, test, power-cycle to revert. - -### Subcommands - -| Subcommand | Purpose | -|------------|---------| -| `load` | Load a firmware file into FX2 RAM | -| `reset` | Halt and restart the FX2 CPU | -| `read` | Read and hex-dump FX2 RAM contents | - -### load -- Write Firmware to RAM - - -1. **Halt the CPU** -- writes `0x01` to the CPUCS register at `0xE600` -2. **Write code segments** into RAM in 64-byte chunks via vendor request `0xA0` -3. **Start the CPU** -- writes `0x00` to CPUCS, triggering USB re-enumeration - - -**Supported file formats:** - -| Extension | Format | Load Address | -|-----------|--------|-------------| -| `.ihx`, `.hex` | Intel HEX | Addresses embedded in file | -| `.bix`, `.bin` | Raw binary | `0x0000` (start of internal RAM) | - -**Options:** - -| Flag | Description | -|------|-------------| -| `FILE` | Firmware file path (positional, required) | -| `--no-reset` | Load segments without halting/starting the CPU | -| `--wait SECONDS` | Wait for USB re-enumeration after load | -| `-v, --verbose` | Show per-chunk transfer progress | -| `--force` | Allow loading to devices with unknown VID/PID | - -**Examples:** - -```bash title="Load custom firmware and wait for re-enumeration" -sudo python3 tools/fw_load.py load firmware/build/skywalker1.ihx --wait 3 -``` - -```bash title="Load raw binary with verbose output" -sudo python3 tools/fw_load.py load firmware.bix -v --wait 5 -``` - -```bash title="Load without CPU reset (write segments only)" -sudo python3 tools/fw_load.py load firmware.ihx --no-reset -``` - -**Typical output:** - -``` -SkyWalker-1 RAM Firmware Loader -======================================== - -Firmware: firmware/build/skywalker1.ihx - Segments: 3 - Total size: 3072 bytes - Address: 0x0000 - 0x0BFF - -Found SkyWalker-1: Bus 1 Addr 12 (VID 0x09C0 PID 0x0203) - -[1/3] Halting CPU (CPUCS = 0x01)... - CPU halted - -[2/3] Loading 3 segment(s) into RAM... - 0x0000-0x03FF (1024 bytes) - 0x0400-0x07FF (1024 bytes) - 0x0800-0x0BFF (1024 bytes) - - 3072 bytes loaded - -[3/3] Starting CPU (CPUCS = 0x00)... - CPU released - - Firmware is running. The device will re-enumerate - with new USB descriptors if the firmware does so. -``` - -### reset -- Restart the FX2 CPU - -Halts and restarts the CPU without loading new code. Useful for triggering USB re-enumeration when the device is in a bad state. - -```bash -sudo python3 tools/fw_load.py reset --wait 3 -``` - -| Flag | Description | -|------|-------------| -| `--wait SECONDS` | Wait for re-enumeration after reset | -| `--force` | Allow reset on unknown VID/PID | - -### read -- Hex-Dump FX2 RAM - -Read memory contents from a running FX2. Useful for verifying loaded firmware or inspecting register state. - -```bash title="Read first 256 bytes of internal RAM" -sudo python3 tools/fw_load.py read --addr 0x0000 --len 256 -``` - -```bash title="Check CPUCS register" -sudo python3 tools/fw_load.py read --addr 0xe600 --len 1 -``` - -```bash title="Dump to file" -sudo python3 tools/fw_load.py read --addr 0x0000 --len 8192 -o ram_dump.bin -``` - -| Flag | Description | -|------|-------------| -| `--addr ADDR` | Start address in hex (default: `0x0000`) | -| `--len LENGTH` | Bytes to read (default: `256`) | -| `-o, --output FILE` | Save raw bytes to file | -| `--force` | Allow read on unknown VID/PID | - -### Device Detection - -The loader searches for devices in this order: - -1. **SkyWalker-1** -- VID `0x09C0`, PID `0x0203` -2. **Bare Cypress FX2** -- VID `0x04B4`, PID `0x8613` (unprogrammed/blank EEPROM) - -Use `--force` to override VID/PID checks for devices with non-standard descriptors. - - - - - - -## Firmware Probe and Dump Tool - -`fw_dump.py` queries device information, dumps FX2 RAM contents, and scans for undocumented vendor commands. - -### Options - -| Flag | Description | -|------|-------------| -| `--info` | Query and display device information | -| `--dump FILE` | Dump FX2 RAM to a binary file | -| `--scan` | Brute-force scan all vendor commands (0x00--0xFF) | -| `--start ADDR` | RAM dump start address (default: `0x0000`) | -| `--size SIZE` | RAM dump size in bytes (default: `0x2000` = 8 KB) | -| `--external` | Attempt to dump external RAM (64 KB) | - -Running with no flags defaults to `--info --scan`. - -### Device Info (--info) - -Queries all known Genpix vendor commands and displays firmware version, build date, USB speed, serial number, and the 8PSK configuration status byte with decoded flags. - -```bash -sudo python3 tools/fw_dump.py --info -``` - -``` -=== Genpix SkyWalker-1 Device Info === - - FW Version: 2.06.4 (0x020604) - FW Build: 2007-07-13 - BCD Version: 0206 - Vendor: Genpix - Product: SkyWalker-1 - USB Speed: High (480Mbps) - Serial: 00000000 - Config: 0x03 - [ ON] 8PSK Started - [ ON] BCM4500 FW Loaded - [off] Intersil LNB On - [off] DVB Mode - [off] 22kHz Tone - [off] 18V Selected - [off] DC Tuned - [off] Armed (streaming) -``` - -### Vendor Command Scan (--scan) - -Sends vendor IN requests to every command index from `0x00` to `0xFF` and reports which ones return data. Commands already documented in the reference are labeled `[KNOWN]`; unexpected responses are flagged `[NEW!]`. - -```bash -sudo python3 tools/fw_dump.py --scan -``` - -### RAM Dump (--dump) - -Reads FX2 internal RAM (8 KB at `0x0000`--`0x1FFF`) or external RAM (up to 64 KB with `--external`) via the standard `0xA0` vendor request. - -```bash title="Dump internal RAM" -sudo python3 tools/fw_dump.py --dump internal_ram.bin -``` - -```bash title="Dump external RAM (64 KB)" -sudo python3 tools/fw_dump.py --dump external_ram.bin --external -``` - -```bash title="Dump specific range" -sudo python3 tools/fw_dump.py --dump region.bin --start 0x1000 --size 0x800 -``` - -The tool performs a quick analysis of the dump, reporting non-`0xFF` byte count and checking for the standard FX2 reset vector (`LJMP` at address `0x0000`). - - - - - - -## Kernel Driver Conflict - -The Linux `dvb_usb_gp8psk` module auto-loads when the SkyWalker-1 enumerates on USB. It will race with these tools for device access. - -**Before using any tool**, either blacklist the module or unload it: - -```bash title="Unload for current session" -sudo modprobe -r dvb_usb_gp8psk gp8psk_fe -``` - -```bash title="Permanent blacklist" -echo -e "blacklist dvb_usb_gp8psk\nblacklist gp8psk_fe" | \ - sudo tee /etc/modprobe.d/blacklist-gp8psk.conf -``` - -## See Also - -- [EEPROM Utilities](/tools/eeprom-utilities/) -- for permanent firmware flashing -- [Boot Sequence](/usb/boot-sequence/) -- what happens after firmware loads -- [Custom Firmware v3.01.0](/firmware/custom-v301/) -- the open-source replacement firmware -- [Vendor Commands](/usb/vendor-commands/) -- command reference used by these tools +--- +title: Firmware Loader +description: RAM firmware loader and firmware dump/probe utilities for the Genpix SkyWalker-1. +--- + +import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; + +Two complementary tools handle firmware on the SkyWalker-1: `fw_load.py` loads firmware into FX2 RAM for testing, and `fw_dump.py` extracts firmware and device information from a running device. + +Both tools require `pyusb` and typically need root access (or appropriate udev rules) to communicate with the USB device. + +```bash +pip install pyusb +``` + + + + + + +## RAM Firmware Loader + +`fw_load.py` loads firmware into the Cypress FX2 internal/external RAM via the standard `0xA0` vendor request built into the FX2 silicon boot ROM. This is the primary tool for firmware development: load, test, power-cycle to revert. + +### Subcommands + +| Subcommand | Purpose | +|------------|---------| +| `load` | Load a firmware file into FX2 RAM | +| `reset` | Halt and restart the FX2 CPU | +| `read` | Read and hex-dump FX2 RAM contents | + +### load -- Write Firmware to RAM + + +1. **Halt the CPU** -- writes `0x01` to the CPUCS register at `0xE600` +2. **Write code segments** into RAM in 64-byte chunks via vendor request `0xA0` +3. **Start the CPU** -- writes `0x00` to CPUCS, triggering USB re-enumeration + + +**Supported file formats:** + +| Extension | Format | Load Address | +|-----------|--------|-------------| +| `.ihx`, `.hex` | Intel HEX | Addresses embedded in file | +| `.bix`, `.bin` | Raw binary | `0x0000` (start of internal RAM) | + +**Options:** + +| Flag | Description | +|------|-------------| +| `FILE` | Firmware file path (positional, required) | +| `--no-reset` | Load segments without halting/starting the CPU | +| `--wait SECONDS` | Wait for USB re-enumeration after load | +| `-v, --verbose` | Show per-chunk transfer progress | +| `--force` | Allow loading to devices with unknown VID/PID | + +**Examples:** + +```bash title="Load custom firmware and wait for re-enumeration" +sudo python3 tools/fw_load.py load firmware/build/skywalker1.ihx --wait 3 +``` + +```bash title="Load raw binary with verbose output" +sudo python3 tools/fw_load.py load firmware.bix -v --wait 5 +``` + +```bash title="Load without CPU reset (write segments only)" +sudo python3 tools/fw_load.py load firmware.ihx --no-reset +``` + +**Typical output:** + +``` +SkyWalker-1 RAM Firmware Loader +======================================== + +Firmware: firmware/build/skywalker1.ihx + Segments: 3 + Total size: 3072 bytes + Address: 0x0000 - 0x0BFF + +Found SkyWalker-1: Bus 1 Addr 12 (VID 0x09C0 PID 0x0203) + +[1/3] Halting CPU (CPUCS = 0x01)... + CPU halted + +[2/3] Loading 3 segment(s) into RAM... + 0x0000-0x03FF (1024 bytes) + 0x0400-0x07FF (1024 bytes) + 0x0800-0x0BFF (1024 bytes) + + 3072 bytes loaded + +[3/3] Starting CPU (CPUCS = 0x00)... + CPU released + + Firmware is running. The device will re-enumerate + with new USB descriptors if the firmware does so. +``` + +### reset -- Restart the FX2 CPU + +Halts and restarts the CPU without loading new code. Useful for triggering USB re-enumeration when the device is in a bad state. + +```bash +sudo python3 tools/fw_load.py reset --wait 3 +``` + +| Flag | Description | +|------|-------------| +| `--wait SECONDS` | Wait for re-enumeration after reset | +| `--force` | Allow reset on unknown VID/PID | + +### read -- Hex-Dump FX2 RAM + +Read memory contents from a running FX2. Useful for verifying loaded firmware or inspecting register state. + +```bash title="Read first 256 bytes of internal RAM" +sudo python3 tools/fw_load.py read --addr 0x0000 --len 256 +``` + +```bash title="Check CPUCS register" +sudo python3 tools/fw_load.py read --addr 0xe600 --len 1 +``` + +```bash title="Dump to file" +sudo python3 tools/fw_load.py read --addr 0x0000 --len 8192 -o ram_dump.bin +``` + +| Flag | Description | +|------|-------------| +| `--addr ADDR` | Start address in hex (default: `0x0000`) | +| `--len LENGTH` | Bytes to read (default: `256`) | +| `-o, --output FILE` | Save raw bytes to file | +| `--force` | Allow read on unknown VID/PID | + +### Device Detection + +The loader searches for devices in this order: + +1. **SkyWalker-1** -- VID `0x09C0`, PID `0x0203` +2. **Bare Cypress FX2** -- VID `0x04B4`, PID `0x8613` (unprogrammed/blank EEPROM) + +Use `--force` to override VID/PID checks for devices with non-standard descriptors. + + + + + + +## Firmware Probe and Dump Tool + +`fw_dump.py` queries device information, dumps FX2 RAM contents, and scans for undocumented vendor commands. + +### Options + +| Flag | Description | +|------|-------------| +| `--info` | Query and display device information | +| `--dump FILE` | Dump FX2 RAM to a binary file | +| `--scan` | Brute-force scan all vendor commands (0x00--0xFF) | +| `--start ADDR` | RAM dump start address (default: `0x0000`) | +| `--size SIZE` | RAM dump size in bytes (default: `0x2000` = 8 KB) | +| `--external` | Attempt to dump external RAM (64 KB) | + +Running with no flags defaults to `--info --scan`. + +### Device Info (--info) + +Queries all known Genpix vendor commands and displays firmware version, build date, USB speed, serial number, and the 8PSK configuration status byte with decoded flags. + +```bash +sudo python3 tools/fw_dump.py --info +``` + +``` +=== Genpix SkyWalker-1 Device Info === + + FW Version: 2.06.4 (0x020604) + FW Build: 2007-07-13 + BCD Version: 0206 + Vendor: Genpix + Product: SkyWalker-1 + USB Speed: High (480Mbps) + Serial: 00000000 + Config: 0x03 + [ ON] 8PSK Started + [ ON] BCM4500 FW Loaded + [off] Intersil LNB On + [off] DVB Mode + [off] 22kHz Tone + [off] 18V Selected + [off] DC Tuned + [off] Armed (streaming) +``` + +### Vendor Command Scan (--scan) + +Sends vendor IN requests to every command index from `0x00` to `0xFF` and reports which ones return data. Commands already documented in the reference are labeled `[KNOWN]`; unexpected responses are flagged `[NEW!]`. + +```bash +sudo python3 tools/fw_dump.py --scan +``` + +### RAM Dump (--dump) + +Reads FX2 internal RAM (8 KB at `0x0000`--`0x1FFF`) or external RAM (up to 64 KB with `--external`) via the standard `0xA0` vendor request. + +```bash title="Dump internal RAM" +sudo python3 tools/fw_dump.py --dump internal_ram.bin +``` + +```bash title="Dump external RAM (64 KB)" +sudo python3 tools/fw_dump.py --dump external_ram.bin --external +``` + +```bash title="Dump specific range" +sudo python3 tools/fw_dump.py --dump region.bin --start 0x1000 --size 0x800 +``` + +The tool performs a quick analysis of the dump, reporting non-`0xFF` byte count and checking for the standard FX2 reset vector (`LJMP` at address `0x0000`). + + + + + + +## Kernel Driver Conflict + +The Linux `dvb_usb_gp8psk` module auto-loads when the SkyWalker-1 enumerates on USB. It will race with these tools for device access. + +**Before using any tool**, either blacklist the module or unload it: + +```bash title="Unload for current session" +sudo modprobe -r dvb_usb_gp8psk gp8psk_fe +``` + +```bash title="Permanent blacklist" +echo -e "blacklist dvb_usb_gp8psk\nblacklist gp8psk_fe" | \ + sudo tee /etc/modprobe.d/blacklist-gp8psk.conf +``` + +## See Also + +- [EEPROM Utilities](/tools/eeprom-utilities/) -- for permanent firmware flashing +- [Boot Sequence](/usb/boot-sequence/) -- what happens after firmware loads +- [Custom Firmware v3.01.0](/firmware/custom-v301/) -- the open-source replacement firmware +- [Vendor Commands](/usb/vendor-commands/) -- command reference used by these tools diff --git a/site/src/content/docs/tools/h21cm.mdx b/site/src/content/docs/tools/h21cm.mdx index 972963d..95cc54b 100644 --- a/site/src/content/docs/tools/h21cm.mdx +++ b/site/src/content/docs/tools/h21cm.mdx @@ -1,131 +1,131 @@ ---- -title: Hydrogen 21 cm Radiometer -description: Detect neutral hydrogen emission at 1420.405 MHz using the SkyWalker-1 as an L-band radiometer. ---- - -import { Aside, Card, CardGrid, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; - -The `h21cm.py` tool turns the SkyWalker-1 into a hydrogen line radiometer. Neutral hydrogen -atoms emit radiation at **1420.405 MHz** when the electron's spin flips relative to the proton — -the most fundamental spectral line in radio astronomy, and it falls directly in the IF range. - -## Antenna Setup - -The SkyWalker-1 normally receives satellite TV through an LNB (Low Noise Block downconverter) mounted -at the focal point of a dish. For hydrogen line work, the LNB must be **removed or bypassed entirely** — -it would block the signal. An LNB's waveguide feed is dimensioned for Ku-band wavelengths (~2.5 cm) -and its internal filters reject everything outside the 10.7-12.75 GHz range. At 1420 MHz (wavelength -~21 cm), nothing gets through. - -Instead, connect an L-band antenna directly to the SkyWalker-1's F-connector with coaxial cable. -The tool disables LNB power automatically, so there's no voltage on the cable. - -### Antenna Options - - - - The classic radio astronomy choice. A tin-can "cantenna" or sheet-metal pyramidal horn provides - 10-15 dBi gain with predictable, calculable performance. Easy to build from hardware store materials. - A circular waveguide horn from a ~15 cm diameter can works well at 1420 MHz. - - - Reuse your satellite dish — replace the LNB with a 1420 MHz feed (dipole + reflector, or a small - horn at the focal point). The dish surface accuracy matters less at 21 cm wavelength than at Ku-band, - so even mesh dishes work fine. This gives the highest gain of any option here. - - - A helical antenna is circularly polarized and offers good gain in a compact package. Hydrogen emission - is unpolarized, so a circularly-polarized antenna captures half the power (~3 dB penalty vs linear), - but helix construction is forgiving and well-documented for L-band. - - - Commercial L-band patch antennas (GPS antennas at 1575 MHz are close) are compact and cheap. Lower - gain than the other options (~5-7 dBi), and narrow bandwidth may not cover the full hydrogen emission - profile. Fine for a first detection attempt. - - - - - -### Cable and Connectors - -Run 75Ω coax (RG-6 is standard satellite TV cable) from the antenna to the SkyWalker-1. At 1420 MHz, -cable loss matters more than impedance mismatch — keep runs under 10 meters if possible. RG-6 loses -roughly **0.2 dB per meter** at 1.4 GHz (about 6 dB per 100 feet), so a 10m run costs ~2 dB. -Shorter is better. - -If your antenna uses 50Ω connectors (SMA, N-type), a simple adapter to F-type is fine. The 0.2 dB -impedance mismatch is far less than a meter of extra cable. - -## Quick Start - -```bash -# Single sweep, 8x averaging for best sensitivity -python tools/h21cm.py --averages 8 - -# One-hour drift scan with CSV output -python tools/h21cm.py --drift --duration 3600 --averages 4 --output h21cm-data.csv -``` - -## How It Works - - -1. **LNB power is disabled** — direct input mode, no frequency conversion -2. **Sweeps 1418-1422 MHz** (configurable) at 0.5 MHz steps -3. **Measures AGC power** at each step — the BCM4500 responds to any RF energy -4. **Estimates baseline** from the band edges (where no hydrogen is expected) -5. **Calculates excess power** above baseline — the hydrogen emission -6. **Computes Doppler velocity** for each frequency bin - - -The velocity axis maps frequency to radial velocity via: - -**v = c × (1420.405 − f_observed) / 1420.405** - -Positive velocity = hydrogen moving away (lower frequency). The ~200 km/s spread -in a typical observation maps the rotation curve of the Milky Way. - -## Options - -| Flag | Default | Description | -|---|---|---| -| `--center` | 1420.405 | Center frequency in MHz | -| `--span` | 4.0 | Frequency span in MHz | -| `--step` | 0.5 | Frequency step in MHz | -| `--dwell` | 50 | Integration time per step in ms | -| `--averages` | 1 | Number of sweeps to average (4-16 recommended) | -| `--output` | — | CSV output file | -| `--control` | — | Include control band comparison | -| `--drift` | — | Enable drift scan mode | -| `--duration` | 3600 | Drift scan duration in seconds | -| `--interval` | 60 | Seconds between drift scans | -| `--motor-step` | 0 | Motor steps between scans (declination scanning) | - -## Sensitivity Notes - - - -The `--control` flag sweeps an adjacent band (1430-1434 MHz) where no hydrogen emission -is expected. Comparing the two bands confirms that any detected bump is real signal, -not system noise variation. - -## CSV Output Format - -| Column | Description | -|---|---| -| `timestamp` | ISO 8601 UTC timestamp | -| `scan_num` | Scan number (drift mode only) | -| `freq_mhz` | Frequency in MHz | -| `power_db` | Raw power in dB (relative) | -| `excess_db` | Power above baseline | -| `velocity_km_s` | Doppler velocity in km/s | -| `baseline_db` | Estimated noise floor | +--- +title: Hydrogen 21 cm Radiometer +description: Detect neutral hydrogen emission at 1420.405 MHz using the SkyWalker-1 as an L-band radiometer. +--- + +import { Aside, Card, CardGrid, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; + +The `h21cm.py` tool turns the SkyWalker-1 into a hydrogen line radiometer. Neutral hydrogen +atoms emit radiation at **1420.405 MHz** when the electron's spin flips relative to the proton — +the most fundamental spectral line in radio astronomy, and it falls directly in the IF range. + +## Antenna Setup + +The SkyWalker-1 normally receives satellite TV through an LNB (Low Noise Block downconverter) mounted +at the focal point of a dish. For hydrogen line work, the LNB must be **removed or bypassed entirely** — +it would block the signal. An LNB's waveguide feed is dimensioned for Ku-band wavelengths (~2.5 cm) +and its internal filters reject everything outside the 10.7-12.75 GHz range. At 1420 MHz (wavelength +~21 cm), nothing gets through. + +Instead, connect an L-band antenna directly to the SkyWalker-1's F-connector with coaxial cable. +The tool disables LNB power automatically, so there's no voltage on the cable. + +### Antenna Options + + + + The classic radio astronomy choice. A tin-can "cantenna" or sheet-metal pyramidal horn provides + 10-15 dBi gain with predictable, calculable performance. Easy to build from hardware store materials. + A circular waveguide horn from a ~15 cm diameter can works well at 1420 MHz. + + + Reuse your satellite dish — replace the LNB with a 1420 MHz feed (dipole + reflector, or a small + horn at the focal point). The dish surface accuracy matters less at 21 cm wavelength than at Ku-band, + so even mesh dishes work fine. This gives the highest gain of any option here. + + + A helical antenna is circularly polarized and offers good gain in a compact package. Hydrogen emission + is unpolarized, so a circularly-polarized antenna captures half the power (~3 dB penalty vs linear), + but helix construction is forgiving and well-documented for L-band. + + + Commercial L-band patch antennas (GPS antennas at 1575 MHz are close) are compact and cheap. Lower + gain than the other options (~5-7 dBi), and narrow bandwidth may not cover the full hydrogen emission + profile. Fine for a first detection attempt. + + + + + +### Cable and Connectors + +Run 75Ω coax (RG-6 is standard satellite TV cable) from the antenna to the SkyWalker-1. At 1420 MHz, +cable loss matters more than impedance mismatch — keep runs under 10 meters if possible. RG-6 loses +roughly **0.2 dB per meter** at 1.4 GHz (about 6 dB per 100 feet), so a 10m run costs ~2 dB. +Shorter is better. + +If your antenna uses 50Ω connectors (SMA, N-type), a simple adapter to F-type is fine. The 0.2 dB +impedance mismatch is far less than a meter of extra cable. + +## Quick Start + +```bash +# Single sweep, 8x averaging for best sensitivity +python tools/h21cm.py --averages 8 + +# One-hour drift scan with CSV output +python tools/h21cm.py --drift --duration 3600 --averages 4 --output h21cm-data.csv +``` + +## How It Works + + +1. **LNB power is disabled** — direct input mode, no frequency conversion +2. **Sweeps 1418-1422 MHz** (configurable) at 0.5 MHz steps +3. **Measures AGC power** at each step — the BCM4500 responds to any RF energy +4. **Estimates baseline** from the band edges (where no hydrogen is expected) +5. **Calculates excess power** above baseline — the hydrogen emission +6. **Computes Doppler velocity** for each frequency bin + + +The velocity axis maps frequency to radial velocity via: + +**v = c × (1420.405 − f_observed) / 1420.405** + +Positive velocity = hydrogen moving away (lower frequency). The ~200 km/s spread +in a typical observation maps the rotation curve of the Milky Way. + +## Options + +| Flag | Default | Description | +|---|---|---| +| `--center` | 1420.405 | Center frequency in MHz | +| `--span` | 4.0 | Frequency span in MHz | +| `--step` | 0.5 | Frequency step in MHz | +| `--dwell` | 50 | Integration time per step in ms | +| `--averages` | 1 | Number of sweeps to average (4-16 recommended) | +| `--output` | — | CSV output file | +| `--control` | — | Include control band comparison | +| `--drift` | — | Enable drift scan mode | +| `--duration` | 3600 | Drift scan duration in seconds | +| `--interval` | 60 | Seconds between drift scans | +| `--motor-step` | 0 | Motor steps between scans (declination scanning) | + +## Sensitivity Notes + + + +The `--control` flag sweeps an adjacent band (1430-1434 MHz) where no hydrogen emission +is expected. Comparing the two bands confirms that any detected bump is real signal, +not system noise variation. + +## CSV Output Format + +| Column | Description | +|---|---| +| `timestamp` | ISO 8601 UTC timestamp | +| `scan_num` | Scan number (drift mode only) | +| `freq_mhz` | Frequency in MHz | +| `power_db` | Raw power in dB (relative) | +| `excess_db` | Power above baseline | +| `velocity_km_s` | Doppler velocity in km/s | +| `baseline_db` | Estimated noise floor | diff --git a/site/src/content/docs/tools/mcp-server.mdx b/site/src/content/docs/tools/mcp-server.mdx index 0bb0da7..b321e3e 100644 --- a/site/src/content/docs/tools/mcp-server.mdx +++ b/site/src/content/docs/tools/mcp-server.mdx @@ -1,112 +1,112 @@ ---- -title: MCP Server -description: Model Context Protocol server that exposes the SkyWalker-1 hardware API to LLMs for autonomous RF exploration. ---- - -import { Aside, Steps } from '@astrojs/starlight/components'; - -The `skywalker-mcp` package wraps the entire SkyWalker-1 Python API as an MCP (Model Context Protocol) -server, making every hardware function accessible to LLMs. This enables natural-language signal analysis, -autonomous RF exploration, and scheduled observation campaigns. - -## Installation - -```bash -cd mcp/skywalker-mcp -uv sync -``` - -## Running - -```bash -# Local development -uv run --directory mcp/skywalker-mcp skywalker-mcp - -# Add to Claude Code -claude mcp add skywalker-mcp -- uv run --directory mcp/skywalker-mcp skywalker-mcp -``` - -## Tools (20) - -### Device Status -| Tool | Description | -|---|---| -| `get_device_status` | Firmware version, config bits, USB speed, serial, last error | -| `get_signal_quality` | SNR, AGC, power, lock status | -| `get_stream_diagnostics` | Poll count, overflows, sync loss | - -### Spectrum & Tuning -| Tool | Description | -|---|---| -| `sweep_spectrum` | Full-band power measurement with peak detection | -| `tune_frequency` | Tune to specific freq/modulation/FEC, read signal | -| `run_blind_scan` | Symbol rate sweep at single frequency | - -### Survey & Catalog -| Tool | Description | -|---|---| -| `run_carrier_survey` | Six-stage pipeline: sweep → peaks → blind → TS → catalog | -| `compare_surveys` | Diff two saved catalogs for changes | -| `list_surveys` | List saved survey files with metadata | - -### Dish Motor -| Tool | Description | -|---|---| -| `move_dish` | Halt, east, west, goto slot, USALS GotoX (continuous drive requires explicit opt-in) | -| `jog_dish` | Small steps (1-30) + signal quality readback | -| `store_position` | Save current position to memory slot | - -### LNB & I2C -| Tool | Description | -|---|---| -| `set_lnb_config` | Voltage (13V/18V), 22 kHz tone, power off | -| `scan_i2c_bus` | Enumerate all I2C devices | -| `read_i2c_register` | Read single byte from I2C address | - -### Transport Stream & Identification -| Tool | Description | -|---|---| -| `capture_transport_stream` | Capture + parse PAT/PMT/SDT for service names | -| `identify_frequency` | Look up frequency against allocation tables | - -## Resources - -| URI | Description | -|---|---| -| `skywalker://status` | Live device state (firmware, config, signal) | -| `skywalker://catalog/latest` | Most recent survey catalog as JSON | -| `skywalker://allocations/lband` | L-band frequency allocation table | -| `skywalker://modulations` | Supported modulations and FEC rates | - -## Prompts - -| Prompt | Description | -|---|---| -| `explore_rf_environment` | Strategy for autonomous RF discovery | -| `hydrogen_line_observation` | Guided 21 cm observation procedure | - -## Architecture - - - -The server uses FastMCP's lifespan pattern: the USB device opens on server startup and -closes on shutdown. All tools receive the device bridge through the lifespan context. - -## Testing - -```bash -# Verify the server starts and can talk to hardware -claude -p "What firmware version is loaded?" \ - --mcp-config .mcp.json \ - --allowedTools "mcp__skywalker-mcp__*" - -# Run a spectrum sweep via natural language -claude -p "Sweep the full IF band and tell me what you find" \ - --mcp-config .mcp.json \ - --allowedTools "mcp__skywalker-mcp__*" -``` +--- +title: MCP Server +description: Model Context Protocol server that exposes the SkyWalker-1 hardware API to LLMs for autonomous RF exploration. +--- + +import { Aside, Steps } from '@astrojs/starlight/components'; + +The `skywalker-mcp` package wraps the entire SkyWalker-1 Python API as an MCP (Model Context Protocol) +server, making every hardware function accessible to LLMs. This enables natural-language signal analysis, +autonomous RF exploration, and scheduled observation campaigns. + +## Installation + +```bash +cd mcp/skywalker-mcp +uv sync +``` + +## Running + +```bash +# Local development +uv run --directory mcp/skywalker-mcp skywalker-mcp + +# Add to Claude Code +claude mcp add skywalker-mcp -- uv run --directory mcp/skywalker-mcp skywalker-mcp +``` + +## Tools (20) + +### Device Status +| Tool | Description | +|---|---| +| `get_device_status` | Firmware version, config bits, USB speed, serial, last error | +| `get_signal_quality` | SNR, AGC, power, lock status | +| `get_stream_diagnostics` | Poll count, overflows, sync loss | + +### Spectrum & Tuning +| Tool | Description | +|---|---| +| `sweep_spectrum` | Full-band power measurement with peak detection | +| `tune_frequency` | Tune to specific freq/modulation/FEC, read signal | +| `run_blind_scan` | Symbol rate sweep at single frequency | + +### Survey & Catalog +| Tool | Description | +|---|---| +| `run_carrier_survey` | Six-stage pipeline: sweep → peaks → blind → TS → catalog | +| `compare_surveys` | Diff two saved catalogs for changes | +| `list_surveys` | List saved survey files with metadata | + +### Dish Motor +| Tool | Description | +|---|---| +| `move_dish` | Halt, east, west, goto slot, USALS GotoX (continuous drive requires explicit opt-in) | +| `jog_dish` | Small steps (1-30) + signal quality readback | +| `store_position` | Save current position to memory slot | + +### LNB & I2C +| Tool | Description | +|---|---| +| `set_lnb_config` | Voltage (13V/18V), 22 kHz tone, power off | +| `scan_i2c_bus` | Enumerate all I2C devices | +| `read_i2c_register` | Read single byte from I2C address | + +### Transport Stream & Identification +| Tool | Description | +|---|---| +| `capture_transport_stream` | Capture + parse PAT/PMT/SDT for service names | +| `identify_frequency` | Look up frequency against allocation tables | + +## Resources + +| URI | Description | +|---|---| +| `skywalker://status` | Live device state (firmware, config, signal) | +| `skywalker://catalog/latest` | Most recent survey catalog as JSON | +| `skywalker://allocations/lband` | L-band frequency allocation table | +| `skywalker://modulations` | Supported modulations and FEC rates | + +## Prompts + +| Prompt | Description | +|---|---| +| `explore_rf_environment` | Strategy for autonomous RF discovery | +| `hydrogen_line_observation` | Guided 21 cm observation procedure | + +## Architecture + + + +The server uses FastMCP's lifespan pattern: the USB device opens on server startup and +closes on shutdown. All tools receive the device bridge through the lifespan context. + +## Testing + +```bash +# Verify the server starts and can talk to hardware +claude -p "What firmware version is loaded?" \ + --mcp-config .mcp.json \ + --allowedTools "mcp__skywalker-mcp__*" + +# Run a spectrum sweep via natural language +claude -p "Sweep the full IF band and tell me what you find" \ + --mcp-config .mcp.json \ + --allowedTools "mcp__skywalker-mcp__*" +``` diff --git a/site/src/content/docs/tools/motor.mdx b/site/src/content/docs/tools/motor.mdx index ccd428d..4214baf 100644 --- a/site/src/content/docs/tools/motor.mdx +++ b/site/src/content/docs/tools/motor.mdx @@ -1,107 +1,107 @@ ---- -title: Motor Control -description: DiSEqC 1.2 positioner motor control with USALS GotoX, stored positions, and live signal feedback for dish alignment. ---- - -import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; - -Command-line tool for driving a DiSEqC 1.2 positioner motor through the SkyWalker-1. Supports continuous jog, stored positions, USALS GotoX calculations, and an interactive keyboard-driven mode with live signal feedback for hands-free dish alignment. - - - -## Usage - -```bash title="Interactive motor jog with live signal" -sudo python3 tools/motor.py interactive -``` - -```bash title="Drive to a stored position" -sudo python3 tools/motor.py goto 3 -``` - -```bash title="USALS GotoX (Es'hail-2 from central Texas)" -sudo python3 tools/motor.py gotox --sat 25.9 --lon -97.5 -``` - -## Subcommands - -| Command | Description | -|---------|-------------| -| `halt` | Emergency stop the motor | -| `east [--steps N]` | Drive east (continuous or N steps) | -| `west [--steps N]` | Drive west (continuous or N steps) | -| `goto SLOT` | Recall stored position (0 = reference) | -| `store SLOT` | Store current position in slot (1-255) | -| `gotox --sat LON --lon LON` | USALS GotoX with calculated rotation angle | -| `limit east\|west` | Set east or west movement limit | -| `nolimits` | Disable movement limits | -| `raw HEX` | Send raw DiSEqC bytes (e.g. `E0 31 60`) | -| `interactive` | Keyboard-driven jog with live signal display | - -## Interactive Mode - -The `interactive` subcommand turns the terminal into a real-time motor controller optimized for dish alignment. No menus, no prompts — just directional input with instant signal feedback. - -| Key | Action | -|-----|--------| -| Left / h | Jog west | -| Right / l | Jog east | -| Space | Halt motor | -| 19 | Recall stored position | -| s | Store current position (prompts for slot) | -| g | USALS GotoX (prompts for coordinates) | -| q | Quit (motor auto-halts on exit) | - -Signal readings update at ~2 Hz, showing SNR, AGC power, and lock status. The display uses ANSI color to distinguish locked (green) from unlocked (red) states. - -### Safety Features - -- **30-second auto-halt** — continuous jog stops automatically after 30 seconds to prevent mechanical damage -- **Exit handler** — motor is halted via `atexit` if the process terminates unexpectedly -- **Limit enforcement** — DiSEqC 1.2 hardware limits are respected when set - - - -## USALS GotoX - -The `gotox` subcommand calculates the optimal motor rotation angle using the USALS (Universal Satellites Automatic Location System) algorithm and sends a DiSEqC 1.3 GotoX command. - -```bash title="Calculate and drive to Es'hail-2 (QO-100)" -sudo python3 tools/motor.py gotox --sat 25.9 --lon -97.5 -# Output: Angle: 72.4 deg East — sending GotoX -``` - -The angle calculation accounts for: -- Observer longitude (negative = West) -- Satellite longitude (positive = East) -- Geostationary orbit geometry at 35,786 km altitude - - - -## DiSEqC 1.2 Protocol - -All motor commands use DiSEqC 1.2 (EN 50494) framing transmitted through the SkyWalker-1's Manchester encoder. The firmware generates the 22 kHz modulated waveform with correct timing: - -| Command | DiSEqC Bytes | Description | -|---------|-------------|-------------| -| Halt | `E0 31 60` | Stop motor movement | -| Drive East | `E0 31 68 00` | Continuous east (00 = no step limit) | -| Drive West | `E0 31 69 00` | Continuous west | -| Store Position | `E0 31 6A NN` | Save current position to slot NN | -| Goto Position | `E0 31 6B NN` | Drive to stored position NN | -| Set East Limit | `E0 31 66 00` | Set current position as east limit | -| Set West Limit | `E0 31 66 01` | Set current position as west limit | -| Disable Limits | `E0 31 63` | Remove movement limits | -| GotoX | `E0 31 6E HH LL` | USALS rotation (angle encoded as 2 bytes) | - -The `raw` subcommand accepts any hex string for advanced DiSEqC messaging beyond the built-in commands. - -## See Also - -- [SkyWalker TUI — Motor Screen](/tools/tui/#motor-control) — graphical motor control with signal gauge -- [DiSEqC Protocol](/lnb-diseqc/diseqc-protocol/) — protocol specification and Manchester encoding details -- [QO-100 DATV Reception](/guides/qo100-datv/) — using motor control for QO-100 dish pointing +--- +title: Motor Control +description: DiSEqC 1.2 positioner motor control with USALS GotoX, stored positions, and live signal feedback for dish alignment. +--- + +import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; + +Command-line tool for driving a DiSEqC 1.2 positioner motor through the SkyWalker-1. Supports continuous jog, stored positions, USALS GotoX calculations, and an interactive keyboard-driven mode with live signal feedback for hands-free dish alignment. + + + +## Usage + +```bash title="Interactive motor jog with live signal" +sudo python3 tools/motor.py interactive +``` + +```bash title="Drive to a stored position" +sudo python3 tools/motor.py goto 3 +``` + +```bash title="USALS GotoX (Es'hail-2 from central Texas)" +sudo python3 tools/motor.py gotox --sat 25.9 --lon -97.5 +``` + +## Subcommands + +| Command | Description | +|---------|-------------| +| `halt` | Emergency stop the motor | +| `east [--steps N]` | Drive east (continuous or N steps) | +| `west [--steps N]` | Drive west (continuous or N steps) | +| `goto SLOT` | Recall stored position (0 = reference) | +| `store SLOT` | Store current position in slot (1-255) | +| `gotox --sat LON --lon LON` | USALS GotoX with calculated rotation angle | +| `limit east\|west` | Set east or west movement limit | +| `nolimits` | Disable movement limits | +| `raw HEX` | Send raw DiSEqC bytes (e.g. `E0 31 60`) | +| `interactive` | Keyboard-driven jog with live signal display | + +## Interactive Mode + +The `interactive` subcommand turns the terminal into a real-time motor controller optimized for dish alignment. No menus, no prompts — just directional input with instant signal feedback. + +| Key | Action | +|-----|--------| +| Left / h | Jog west | +| Right / l | Jog east | +| Space | Halt motor | +| 19 | Recall stored position | +| s | Store current position (prompts for slot) | +| g | USALS GotoX (prompts for coordinates) | +| q | Quit (motor auto-halts on exit) | + +Signal readings update at ~2 Hz, showing SNR, AGC power, and lock status. The display uses ANSI color to distinguish locked (green) from unlocked (red) states. + +### Safety Features + +- **30-second auto-halt** — continuous jog stops automatically after 30 seconds to prevent mechanical damage +- **Exit handler** — motor is halted via `atexit` if the process terminates unexpectedly +- **Limit enforcement** — DiSEqC 1.2 hardware limits are respected when set + + + +## USALS GotoX + +The `gotox` subcommand calculates the optimal motor rotation angle using the USALS (Universal Satellites Automatic Location System) algorithm and sends a DiSEqC 1.3 GotoX command. + +```bash title="Calculate and drive to Es'hail-2 (QO-100)" +sudo python3 tools/motor.py gotox --sat 25.9 --lon -97.5 +# Output: Angle: 72.4 deg East — sending GotoX +``` + +The angle calculation accounts for: +- Observer longitude (negative = West) +- Satellite longitude (positive = East) +- Geostationary orbit geometry at 35,786 km altitude + + + +## DiSEqC 1.2 Protocol + +All motor commands use DiSEqC 1.2 (EN 50494) framing transmitted through the SkyWalker-1's Manchester encoder. The firmware generates the 22 kHz modulated waveform with correct timing: + +| Command | DiSEqC Bytes | Description | +|---------|-------------|-------------| +| Halt | `E0 31 60` | Stop motor movement | +| Drive East | `E0 31 68 00` | Continuous east (00 = no step limit) | +| Drive West | `E0 31 69 00` | Continuous west | +| Store Position | `E0 31 6A NN` | Save current position to slot NN | +| Goto Position | `E0 31 6B NN` | Drive to stored position NN | +| Set East Limit | `E0 31 66 00` | Set current position as east limit | +| Set West Limit | `E0 31 66 01` | Set current position as west limit | +| Disable Limits | `E0 31 63` | Remove movement limits | +| GotoX | `E0 31 6E HH LL` | USALS rotation (angle encoded as 2 bytes) | + +The `raw` subcommand accepts any hex string for advanced DiSEqC messaging beyond the built-in commands. + +## See Also + +- [SkyWalker TUI — Motor Screen](/tools/tui/#motor-control) — graphical motor control with signal gauge +- [DiSEqC Protocol](/lnb-diseqc/diseqc-protocol/) — protocol specification and Manchester encoding details +- [QO-100 DATV Reception](/guides/qo100-datv/) — using motor control for QO-100 dish pointing diff --git a/site/src/content/docs/tools/rf-testbench.mdx b/site/src/content/docs/tools/rf-testbench.mdx index cbf4bf3..a894507 100644 --- a/site/src/content/docs/tools/rf-testbench.mdx +++ b/site/src/content/docs/tools/rf-testbench.mdx @@ -1,260 +1,260 @@ ---- -title: RF Test Bench -description: Automated CW injection testing with NanoVNA, HMC472A digital attenuator, and SkyWalker-1 receiver. ---- - -import { Aside, Badge, Card, CardGrid, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; - - - -The `rf_testbench.py` tool turns a NanoVNA, an HMC472A digital attenuator, and the SkyWalker-1 into -an automated RF test bench. It injects CW signals at known frequencies and power levels, then -measures the receiver's response — characterizing AGC linearity, IF band flatness, frequency -accuracy, sensitivity, and BPSK mode 9 behavior. - -## Hardware Setup - - -1. **NanoVNA CH0 output** (SMA) connects to a **DC blocker** (SMA inline, required) -2. **DC blocker output** connects to the **HMC472A RF IN** (SMA) -3. **HMC472A RF OUT** (SMA) connects via **SMA-to-F adapter** to the **SkyWalker-1 F-connector** -4. **HMC472A ESP32-S2 controller** connected to your network (WiFi) — reachable at `http://attenuator.local` -5. **NanoVNA** connected via USB (for mcnanovna automation) or operated via touchscreen (manual mode) - - -``` -NanoVNA CH0 ──→ DC Blocker ──→ HMC472A (0-31.5 dB) ──→ SMA-to-F ──→ SkyWalker-1 - (SMA) (SMA) REST API control adapter (F-type) - http://attenuator.local -``` - -### Components - -| Component | Purpose | Notes | -|-----------|---------|-------| -| NanoVNA-H (9 kHz-1.5 GHz) | CW signal source | Output ~-15 dBm at max power. Overlaps SkyWalker-1 IF band at 950-1500 MHz | -| DC Blocker (SMA inline) | Protect NanoVNA from LNB voltage | Required — even though the tool disables LNB power, this prevents accidental damage | -| HMC472A attenuator module | Precision level control | 0-31.5 dB in 0.5 dB steps, controlled via ESP32-S2 REST API | -| SMA-to-F adapter | Connector transition | 50-to-75 ohm mismatch is ~0.2 dB — negligible | - - - -### HMC472A Attenuator - -The [HMC472A digital attenuator](https://hmc472.l.zmesh.systems/) provides programmable signal level -control via its ESP32-S2 REST API: - -- **Range**: 0 to 31.5 dB in 0.5 dB steps (64 discrete settings) -- **Bandwidth**: DC to 3.8 GHz (covers the full SkyWalker-1 IF range) -- **Insertion loss**: 1.4-1.9 dB typical -- **Control**: HTTP REST — `POST /set {"attenuation_db": 10.5}` -- **Switching speed**: 60 ns (faster than any measurement cycle) - -The tool communicates with the attenuator at `http://attenuator.local` by default. Override with -`--attenuator http://10.0.0.50` if your device has a different address. - -### NanoVNA Frequency Overlap - -The NanoVNA-H (HW3.7) covers 9 kHz to 1.5 GHz. The SkyWalker-1's IF range is 950-2150 MHz. -The **overlapping usable range is 950-1500 MHz** — the lower portion of the IF band. This is -sufficient for characterizing the tuner and AGC, and includes the 1420 MHz hydrogen line region. - -For testing above 1500 MHz, a different signal source (bladeRF, signal generator) would be needed. - -## Calibration - -Before running quantitative tests, characterize the signal path loss: - - -1. Disconnect the SkyWalker-1 end of the cable -2. Connect: **NanoVNA CH0** → DC blocker → HMC472A (set to 0 dB) → cable → **NanoVNA CH1** -3. Run an S21 sweep from 950 to 1500 MHz using mcnanovna or the NanoVNA touchscreen -4. Export as CSV with columns `freq_mhz` and `s21_db` (or `frequency_hz` and `loss_db`) -5. Pass to `rf_testbench.py` with `--cal path_loss.csv` - - -The tool interpolates the measured path loss at each test frequency and subtracts it from AGC -readings. Without a calibration file, raw AGC values are still reported — useful for relative -measurements but not calibrated to absolute power. - - - -## Prerequisites - -- **SkyWalker-1** with [custom firmware v3.02+](/firmware/custom-v302/) (for `tune_monitor` command) -- **HMC472A** attenuator with ESP32-S2 controller on the network -- **NanoVNA-H** (manual mode works with any VNA; auto mode requires [mcnanovna](https://git.supported.systems/rf/mcnanovna)) -- **Python 3.10+** with `pyusb` installed -- **DC blocker** (SMA inline) -- **SMA-to-F adapter** - -## Test Descriptions - -### AGC Power Linearity - -```bash -python tools/rf_testbench.py agc-linearity --freq 1200 -``` - -Injects CW at a fixed frequency while sweeping the HMC472A from 0 to 31.5 dB in 0.5 dB steps. -At each attenuation level, the SkyWalker-1 reports AGC1, AGC2, and derived power. This maps the -**AGC transfer function** — how the receiver's automatic gain control responds to known changes -in input power. - -The output shows whether the AGC is linear, where it saturates, and its effective dynamic range. -With 64 measurement points across 31.5 dB, the resolution is high enough to reveal nonlinearities -in the BCM3440 tuner's gain control loop. - -### IF Band Flatness - -```bash -python tools/rf_testbench.py band-flatness --start 950 --stop 1500 --step 10 -``` - -Sweeps the NanoVNA CW frequency across the IF band while keeping the HMC472A at a fixed -attenuation (10 dB default). At each frequency, the SkyWalker-1 tunes and reads AGC power. - -The result reveals: -- **Tuner gain slope**: the BCM3440 may have more gain at some frequencies than others -- **Passband ripple**: resonances or nulls in the IF filter chain -- **Cable/path frequency response**: if a calibration file is loaded, this is subtracted out - -### Frequency Accuracy - -```bash -python tools/rf_testbench.py freq-accuracy --freqs 1000,1100,1200,1300,1400 -``` - -At each test frequency, the NanoVNA injects CW while the SkyWalker-1 runs a narrow spectrum -sweep (+/- 5 MHz) around the expected frequency. The detected power peak is compared against the -injected frequency. - -This characterizes the **BCM3440 tuner's frequency accuracy** — how much the actual tuned -frequency differs from the commanded frequency. The error may be systematic (constant offset) -or frequency-dependent. - -### Minimum Detectable Signal - -```bash -python tools/rf_testbench.py mds --freq 1200 -``` - -First measures the noise floor with maximum attenuation (31.5 dB). Then injects CW and steps -the HMC472A from 0 dB upward in 1 dB increments until the signal drops below 3-sigma above -the noise floor. - -The attenuation level where the signal disappears, combined with the NanoVNA output power -(~-15 dBm), gives an approximate **minimum detectable signal level** in dBm. - -### BPSK Mode 9 CW Probe - -```bash -python tools/rf_testbench.py bpsk-probe --freq 1200 -``` - -An exploratory test that tunes the SkyWalker-1 in **BPSK mode (index 9)** — the same Viterbi -rate 1/2 K=7 inner FEC used by GOES LRIT. A CW carrier has no modulation, so the demodulator -shouldn't acquire lock, but the AGC and carrier recovery behavior is informative. - -Tests several symbol rates (293,883 sps matching LRIT, plus 500K, 1M, and 5M) and compares -against QPSK mode 0 at the same frequency. This establishes a baseline for what mode 9 reports -with an unmodulated carrier — useful context for future modulated-signal experiments with a -bladeRF. - -## Options - -| Flag | Default | Description | -|------|---------|-------------| -| `--attenuator` | `http://attenuator.local` | HMC472A REST API base URL | -| `--nanovna` | `auto` | NanoVNA control: `auto` (mcnanovna) or `manual` (prompted) | -| `--cal` | — | Path loss calibration CSV file | -| `--settle` | 200 | Settle time in ms after changing attenuation | -| `--output` / `-o` | — | CSV output file | -| `--verbose` / `-v` | — | Show raw USB traffic | - -### Per-Test Options - -| Test | Flag | Default | Description | -|------|------|---------|-------------| -| `agc-linearity` | `--freq` | 1200 | Test frequency in MHz | -| `band-flatness` | `--start` | 950 | Start frequency in MHz | -| `band-flatness` | `--stop` | 1500 | Stop frequency in MHz | -| `band-flatness` | `--step` | 10 | Frequency step in MHz | -| `freq-accuracy` | `--freqs` | 1000,1100,1200,1300,1400 | Comma-separated test frequencies | -| `mds` | `--freq` | 1200 | Test frequency in MHz | -| `bpsk-probe` | `--freq` | 1200 | Test frequency in MHz | - -## CSV Output Format - -All tests write the same CSV format when `--output` is specified: - -| Column | Description | -|--------|-------------| -| `timestamp` | ISO 8601 UTC timestamp | -| `test_name` | Test identifier (agc_linearity, band_flatness, freq_accuracy, mds, bpsk_probe) | -| `freq_mhz` | Frequency in MHz | -| `atten_db` | HMC472A attenuation setting in dB | -| `agc1` | BCM3440 AGC1 register value | -| `agc2` | BCM3440 AGC2 register value | -| `power_db` | Derived power estimate in dB (relative) | -| `snr_raw` | Raw SNR register value | -| `snr_db` | SNR in dB | -| `locked` | Demodulator lock status | -| `lock_raw` | Raw lock status byte | -| `status` | Status byte | -| `notes` | Test-specific metadata | - -## Interpreting Results - -### AGC Linearity Curves - -A well-behaved AGC should show a roughly linear relationship between attenuation (dB) and AGC -register value. Look for: - -- **Linear region**: Where AGC tracks input power changes 1:1 in dB — this is the useful - measurement range -- **Saturation**: Where adding more signal doesn't change AGC — the tuner's front end is - compressing -- **Noise floor**: Where reducing signal doesn't change AGC — the receiver's internal noise - dominates - -### Band Flatness - -Ideal response is flat across the band. In practice: -- **1-3 dB variation** across 950-1500 MHz is typical for a consumer-grade tuner -- **Sharp dips** may indicate cable resonances or connector issues -- **Systematic slope** (gain increasing or decreasing with frequency) is common and can be - corrected in post-processing - -### Frequency Error - -Consumer satellite tuners typically have **50-200 kHz frequency accuracy**. A consistent offset -suggests LO error in the BCM3440. Frequency-dependent error suggests tuning nonlinearity. - -## Mock Mode - -Run with `SKYWALKER_MOCK=1` for testing without hardware: - -```bash -SKYWALKER_MOCK=1 python tools/rf_testbench.py agc-linearity --freq 1200 --nanovna manual -``` - -Mock mode uses built-in simulated responses for the SkyWalker-1 and HMC472A. The NanoVNA prompts -are skipped. Useful for verifying command structure and CSV output format. - -## See Also - -- [Spectrum Analysis](/tools/spectrum-analysis/) — frequency sweep techniques -- [Hydrogen 21 cm](/tools/h21cm/) — direct L-band input mode (same RF path concept) -- [Signal Monitoring](/bcm4500/signal-monitoring/) — AGC and SNR register details -- [HMC472A Documentation](https://hmc472.l.zmesh.systems/) — attenuator module reference -- [Applications & Use Cases](/guides/applications/) — RF test and measurement context +--- +title: RF Test Bench +description: Automated CW injection testing with NanoVNA, HMC472A digital attenuator, and SkyWalker-1 receiver. +--- + +import { Aside, Badge, Card, CardGrid, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; + + + +The `rf_testbench.py` tool turns a NanoVNA, an HMC472A digital attenuator, and the SkyWalker-1 into +an automated RF test bench. It injects CW signals at known frequencies and power levels, then +measures the receiver's response — characterizing AGC linearity, IF band flatness, frequency +accuracy, sensitivity, and BPSK mode 9 behavior. + +## Hardware Setup + + +1. **NanoVNA CH0 output** (SMA) connects to a **DC blocker** (SMA inline, required) +2. **DC blocker output** connects to the **HMC472A RF IN** (SMA) +3. **HMC472A RF OUT** (SMA) connects via **SMA-to-F adapter** to the **SkyWalker-1 F-connector** +4. **HMC472A ESP32-S2 controller** connected to your network (WiFi) — reachable at `http://attenuator.local` +5. **NanoVNA** connected via USB (for mcnanovna automation) or operated via touchscreen (manual mode) + + +``` +NanoVNA CH0 ──→ DC Blocker ──→ HMC472A (0-31.5 dB) ──→ SMA-to-F ──→ SkyWalker-1 + (SMA) (SMA) REST API control adapter (F-type) + http://attenuator.local +``` + +### Components + +| Component | Purpose | Notes | +|-----------|---------|-------| +| NanoVNA-H (9 kHz-1.5 GHz) | CW signal source | Output ~-15 dBm at max power. Overlaps SkyWalker-1 IF band at 950-1500 MHz | +| DC Blocker (SMA inline) | Protect NanoVNA from LNB voltage | Required — even though the tool disables LNB power, this prevents accidental damage | +| HMC472A attenuator module | Precision level control | 0-31.5 dB in 0.5 dB steps, controlled via ESP32-S2 REST API | +| SMA-to-F adapter | Connector transition | 50-to-75 ohm mismatch is ~0.2 dB — negligible | + + + +### HMC472A Attenuator + +The [HMC472A digital attenuator](https://hmc472.l.zmesh.systems/) provides programmable signal level +control via its ESP32-S2 REST API: + +- **Range**: 0 to 31.5 dB in 0.5 dB steps (64 discrete settings) +- **Bandwidth**: DC to 3.8 GHz (covers the full SkyWalker-1 IF range) +- **Insertion loss**: 1.4-1.9 dB typical +- **Control**: HTTP REST — `POST /set {"attenuation_db": 10.5}` +- **Switching speed**: 60 ns (faster than any measurement cycle) + +The tool communicates with the attenuator at `http://attenuator.local` by default. Override with +`--attenuator http://10.0.0.50` if your device has a different address. + +### NanoVNA Frequency Overlap + +The NanoVNA-H (HW3.7) covers 9 kHz to 1.5 GHz. The SkyWalker-1's IF range is 950-2150 MHz. +The **overlapping usable range is 950-1500 MHz** — the lower portion of the IF band. This is +sufficient for characterizing the tuner and AGC, and includes the 1420 MHz hydrogen line region. + +For testing above 1500 MHz, a different signal source (bladeRF, signal generator) would be needed. + +## Calibration + +Before running quantitative tests, characterize the signal path loss: + + +1. Disconnect the SkyWalker-1 end of the cable +2. Connect: **NanoVNA CH0** → DC blocker → HMC472A (set to 0 dB) → cable → **NanoVNA CH1** +3. Run an S21 sweep from 950 to 1500 MHz using mcnanovna or the NanoVNA touchscreen +4. Export as CSV with columns `freq_mhz` and `s21_db` (or `frequency_hz` and `loss_db`) +5. Pass to `rf_testbench.py` with `--cal path_loss.csv` + + +The tool interpolates the measured path loss at each test frequency and subtracts it from AGC +readings. Without a calibration file, raw AGC values are still reported — useful for relative +measurements but not calibrated to absolute power. + + + +## Prerequisites + +- **SkyWalker-1** with [custom firmware v3.02+](/firmware/custom-v302/) (for `tune_monitor` command) +- **HMC472A** attenuator with ESP32-S2 controller on the network +- **NanoVNA-H** (manual mode works with any VNA; auto mode requires [mcnanovna](https://git.supported.systems/rf/mcnanovna)) +- **Python 3.10+** with `pyusb` installed +- **DC blocker** (SMA inline) +- **SMA-to-F adapter** + +## Test Descriptions + +### AGC Power Linearity + +```bash +python tools/rf_testbench.py agc-linearity --freq 1200 +``` + +Injects CW at a fixed frequency while sweeping the HMC472A from 0 to 31.5 dB in 0.5 dB steps. +At each attenuation level, the SkyWalker-1 reports AGC1, AGC2, and derived power. This maps the +**AGC transfer function** — how the receiver's automatic gain control responds to known changes +in input power. + +The output shows whether the AGC is linear, where it saturates, and its effective dynamic range. +With 64 measurement points across 31.5 dB, the resolution is high enough to reveal nonlinearities +in the BCM3440 tuner's gain control loop. + +### IF Band Flatness + +```bash +python tools/rf_testbench.py band-flatness --start 950 --stop 1500 --step 10 +``` + +Sweeps the NanoVNA CW frequency across the IF band while keeping the HMC472A at a fixed +attenuation (10 dB default). At each frequency, the SkyWalker-1 tunes and reads AGC power. + +The result reveals: +- **Tuner gain slope**: the BCM3440 may have more gain at some frequencies than others +- **Passband ripple**: resonances or nulls in the IF filter chain +- **Cable/path frequency response**: if a calibration file is loaded, this is subtracted out + +### Frequency Accuracy + +```bash +python tools/rf_testbench.py freq-accuracy --freqs 1000,1100,1200,1300,1400 +``` + +At each test frequency, the NanoVNA injects CW while the SkyWalker-1 runs a narrow spectrum +sweep (+/- 5 MHz) around the expected frequency. The detected power peak is compared against the +injected frequency. + +This characterizes the **BCM3440 tuner's frequency accuracy** — how much the actual tuned +frequency differs from the commanded frequency. The error may be systematic (constant offset) +or frequency-dependent. + +### Minimum Detectable Signal + +```bash +python tools/rf_testbench.py mds --freq 1200 +``` + +First measures the noise floor with maximum attenuation (31.5 dB). Then injects CW and steps +the HMC472A from 0 dB upward in 1 dB increments until the signal drops below 3-sigma above +the noise floor. + +The attenuation level where the signal disappears, combined with the NanoVNA output power +(~-15 dBm), gives an approximate **minimum detectable signal level** in dBm. + +### BPSK Mode 9 CW Probe + +```bash +python tools/rf_testbench.py bpsk-probe --freq 1200 +``` + +An exploratory test that tunes the SkyWalker-1 in **BPSK mode (index 9)** — the same Viterbi +rate 1/2 K=7 inner FEC used by GOES LRIT. A CW carrier has no modulation, so the demodulator +shouldn't acquire lock, but the AGC and carrier recovery behavior is informative. + +Tests several symbol rates (293,883 sps matching LRIT, plus 500K, 1M, and 5M) and compares +against QPSK mode 0 at the same frequency. This establishes a baseline for what mode 9 reports +with an unmodulated carrier — useful context for future modulated-signal experiments with a +bladeRF. + +## Options + +| Flag | Default | Description | +|------|---------|-------------| +| `--attenuator` | `http://attenuator.local` | HMC472A REST API base URL | +| `--nanovna` | `auto` | NanoVNA control: `auto` (mcnanovna) or `manual` (prompted) | +| `--cal` | — | Path loss calibration CSV file | +| `--settle` | 200 | Settle time in ms after changing attenuation | +| `--output` / `-o` | — | CSV output file | +| `--verbose` / `-v` | — | Show raw USB traffic | + +### Per-Test Options + +| Test | Flag | Default | Description | +|------|------|---------|-------------| +| `agc-linearity` | `--freq` | 1200 | Test frequency in MHz | +| `band-flatness` | `--start` | 950 | Start frequency in MHz | +| `band-flatness` | `--stop` | 1500 | Stop frequency in MHz | +| `band-flatness` | `--step` | 10 | Frequency step in MHz | +| `freq-accuracy` | `--freqs` | 1000,1100,1200,1300,1400 | Comma-separated test frequencies | +| `mds` | `--freq` | 1200 | Test frequency in MHz | +| `bpsk-probe` | `--freq` | 1200 | Test frequency in MHz | + +## CSV Output Format + +All tests write the same CSV format when `--output` is specified: + +| Column | Description | +|--------|-------------| +| `timestamp` | ISO 8601 UTC timestamp | +| `test_name` | Test identifier (agc_linearity, band_flatness, freq_accuracy, mds, bpsk_probe) | +| `freq_mhz` | Frequency in MHz | +| `atten_db` | HMC472A attenuation setting in dB | +| `agc1` | BCM3440 AGC1 register value | +| `agc2` | BCM3440 AGC2 register value | +| `power_db` | Derived power estimate in dB (relative) | +| `snr_raw` | Raw SNR register value | +| `snr_db` | SNR in dB | +| `locked` | Demodulator lock status | +| `lock_raw` | Raw lock status byte | +| `status` | Status byte | +| `notes` | Test-specific metadata | + +## Interpreting Results + +### AGC Linearity Curves + +A well-behaved AGC should show a roughly linear relationship between attenuation (dB) and AGC +register value. Look for: + +- **Linear region**: Where AGC tracks input power changes 1:1 in dB — this is the useful + measurement range +- **Saturation**: Where adding more signal doesn't change AGC — the tuner's front end is + compressing +- **Noise floor**: Where reducing signal doesn't change AGC — the receiver's internal noise + dominates + +### Band Flatness + +Ideal response is flat across the band. In practice: +- **1-3 dB variation** across 950-1500 MHz is typical for a consumer-grade tuner +- **Sharp dips** may indicate cable resonances or connector issues +- **Systematic slope** (gain increasing or decreasing with frequency) is common and can be + corrected in post-processing + +### Frequency Error + +Consumer satellite tuners typically have **50-200 kHz frequency accuracy**. A consistent offset +suggests LO error in the BCM3440. Frequency-dependent error suggests tuning nonlinearity. + +## Mock Mode + +Run with `SKYWALKER_MOCK=1` for testing without hardware: + +```bash +SKYWALKER_MOCK=1 python tools/rf_testbench.py agc-linearity --freq 1200 --nanovna manual +``` + +Mock mode uses built-in simulated responses for the SkyWalker-1 and HMC472A. The NanoVNA prompts +are skipped. Useful for verifying command structure and CSV output format. + +## See Also + +- [Spectrum Analysis](/tools/spectrum-analysis/) — frequency sweep techniques +- [Hydrogen 21 cm](/tools/h21cm/) — direct L-band input mode (same RF path concept) +- [Signal Monitoring](/bcm4500/signal-monitoring/) — AGC and SNR register details +- [HMC472A Documentation](https://hmc472.l.zmesh.systems/) — attenuator module reference +- [Applications & Use Cases](/guides/applications/) — RF test and measurement context diff --git a/site/src/content/docs/tools/safety-testing.mdx b/site/src/content/docs/tools/safety-testing.mdx index cedb6d2..9b7d346 100644 --- a/site/src/content/docs/tools/safety-testing.mdx +++ b/site/src/content/docs/tools/safety-testing.mdx @@ -1,202 +1,202 @@ ---- -title: Safety Testing -description: Hamilton adversarial test suite for firmware safety validation — operator error, invalid inputs, state machine violations, and rapid-fire stress. ---- - -import { Badge, Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; - -The Hamilton adversarial test suite (`test_hamilton.py`) validates that the firmware survives every kind of wrong input an operator or buggy host software could send. Named after Margaret Hamilton's methodology at MIT Instrumentation Laboratory — she tested "what if the astronaut pushes the wrong button?" and found bugs that saved Apollo missions. - -```bash -sudo python3 tools/test_hamilton.py -``` - - - -## Test Philosophy - -Traditional hardware testing validates the happy path — correct inputs producing correct outputs. Adversarial testing validates the *unhappy* path: what happens when the host sends garbage? The firmware must never: - -1. **Hang** — every code path must terminate in bounded time -2. **Corrupt state** — invalid inputs must not leave the device in an inconsistent state -3. **Fire the watchdog** — the main loop must stay responsive -4. **Become unresponsive** — the device must accept new commands after any error - -Each test follows the same pattern: - - -1. Read `last_error` before the operation -2. Send the invalid/abusive command -3. Verify the device is still alive (`GET_FW_VERS` returns `3.05.0`) -4. Read `last_error` after — check it matches expectations - - -## Test Categories - - - - -### Category 1: DiSEqC Message Abuse - -Tests the DiSEqC messaging path with invalid parameters. DiSEqC uses Timer2-based Manchester encoding on the 22 kHz carrier — a stuck timer would hang the firmware without the v3.05.0 `diseqc_wait_ticks()` timeout fix. - -| Test | Input | Expected | -|------|-------|----------| -| 1a. Tone burst B | `wValue=1` | `ERR_NOT_SUPPORTED` (0x0A) | -| 1b. Tone burst 0xFF | `wValue=0xFF` | `ERR_NOT_SUPPORTED` (0x0A) | -| 1c. Too short | 2-byte payload | No hang | -| 1d. Too long | 8-byte payload | No hang | -| 1e. Empty | 0-byte payload | No hang | -| 1f. Recovery | Valid 4-byte message | No hang, correct execution | -| 1g. Motor halt | DiSEqC 1.2 `E0 31 60` (no motor connected) | No hang | -| 1h. Drive 255 steps | DiSEqC 1.2 `E0 31 68 FF` | No hang | -| 1i. Bogus USALS | DiSEqC 1.2 `E0 31 6E FF FF` | No hang | - - - - -### Category 2: Tune Parameter Abuse - -Sends tuning commands with out-of-range parameters. The BCM4500 demodulator receives these values directly over I2C — the firmware must not hang regardless of what the host sends. - -| Test | Input | Expected | -|------|-------|----------| -| 2a. Zero symbol rate | `SR=0` | No hang | -| 2b. Max symbol rate | `SR=0xFFFFFFFF` | No hang | -| 2c. Zero frequency | `freq=0` | No hang | -| 2d. Max frequency | `freq=0xFFFFFFFF` | No hang | -| 2e. Invalid modulation | `mod=0xFF` | No hang | -| 2f. Invalid FEC | `fec=0xFF` | No hang | -| 2g. Truncated (4 bytes) | 4 of 10 bytes sent | `ERR_EP0_TIMEOUT` (0x07) | -| 2h. Single byte | 1 of 10 bytes sent | `ERR_EP0_TIMEOUT` (0x07) | -| 2i. All zeros | 10 zero bytes | No hang | -| 2j. All 0xFF | 10 × `0xFF` | No hang | - -Tests 2g and 2h specifically validate the `ep0_wait_data()` + `EP0BCL` payload length check. The firmware waits for the EP0 data phase, but when the host sends fewer bytes than expected, the timeout fires and the command is safely aborted. - - - - -### Category 3: I2C Address Space Abuse - -Sends I2C reads and writes to non-existent or invalid addresses. Before v3.05.0, the bus scan command (`0xB4`) used bare `while (!(I2CS & bmDONE))` loops that would hang if any address didn't ACK. - -| Test | Input | Expected | -|------|-------|----------| -| 3a. Addr 0x7F | Non-existent I2C device | `ERR_I2C_NAK` (0x02) | -| 3b. Addr 0x00 | General call address | No hang | -| 3c. Page 0xFF | BCM4500 indirect read, invalid page | No hang | -| 3d. Count=0 | Multi-reg read with zero count | No hang | -| 3e. Count=255 | Multi-reg read exceeding 64 max | No hang (clamped) | -| 3f. Write to 0x7F | Raw I2C write to non-existent device | No hang | -| 3g. Reserved reg | BCM4500 direct read of reg 0xFF | No hang | - - - - -### Category 4: State Machine Violations - -Tests operations in the wrong order — the classic "what if the astronaut pushes the wrong button?" scenario. - -| Test | Scenario | Expected | -|------|----------|----------| -| 4a. Double boot | `BOOT_8PSK(1)` when already booted | No hang | -| 4b. Tune with BCM off | Power off BCM4500, then tune | `ERR_BCM_NOT_READY` (0x04) | -| 4c. Signal read, BCM off | Signal monitor with demod powered down | No hang | -| 4d. Bus scan, BCM off | I2C bus scan with demod powered down | No hang | -| 4e. Hotplug, BCM off | Force hotplug rescan with demod off | No hang | -| 4f. Recovery | Re-boot BCM4500 after power cycle | `STARTED \| FW_LOADED` | -| 4g. Arm + disarm | Arm streaming then immediately disarm | No hang | -| 4h. Disarm, not armed | Disarm when already disarmed | No hang | -| 4i. Rapid boot toggle | Off → on → off → on in quick succession | No hang | - -Test 4b is the most important: it proves the `do_tune()` early guard works. Without the v3.05.0 fix, tuning with a powered-off BCM4500 would attempt I2C writes to a device that isn't there, potentially hanging on the bus. - - - - -### Category 5: Boundary and Buffer Abuse - -Tests USB transfer size mismatches — requesting more or fewer bytes than the firmware produces. - -| Test | Scenario | Expected | -|------|----------|----------| -| 5a. 0 bytes from GET_CONFIG | Request 0 bytes (firmware sends 1) | No hang | -| 5b. 64 bytes from GET_CONFIG | Request 64 bytes (firmware sends 1) | No hang | -| 5c. 64 bytes from GET_LAST_ERROR | Request 64 bytes (firmware sends 1) | No hang | -| 5d. 1 byte from GET_FW_VERS | Request 1 byte (firmware sends 6) | No hang | -| 5e. 1 byte from GET_STREAM_DIAG | Request 1 byte (firmware sends 12) | No hang | -| 5f. STREAM_DIAG wval=0xFFFF | Non-standard wValue | No hang | -| 5g. HOTPLUG wval=0xFFFF | Non-standard wValue | No hang | - -The USB layer handles size mismatches — the firmware writes its full response to EP0BUF regardless of how many bytes the host requested. The host-side libusb may return an `Overflow` error, but the device remains stable. - - - - -### Category 6: Rapid-Fire Stress - -Sends many commands in quick succession to test for timing-dependent failures. - -| Test | Operations | Typical Time | -|------|-----------|--------------| -| 6a. Config reads | 200 × `GET_CONFIG` | ~9 ms | -| 6b. Error reads | 50 × `GET_LAST_ERROR` | ~2 ms | -| 6c. Signal monitors | 30 × `SIGNAL_MONITOR` | ~276 ms | -| 6d. Voltage toggles | 40 × `SET_LNB_VOLTAGE` | ~2 ms | -| 6e. DiSEqC messages | 10 × 4-byte DiSEqC switch | ~1,473 ms | - -Single-byte vendor commands (config, error) complete in under 0.05 ms. Signal monitors are slower (~9 ms each) because they perform multiple I2C reads. DiSEqC messages are the slowest due to Timer2-based bit-bang encoding. - - - - -### Category 7: Invalid Vendor Commands - -Sends vendor command codes that don't exist in the firmware. - -| Test | Command | Expected | -|------|---------|----------| -| 7a-f. Unknown codes | `0xFF`, `0x01`, `0x50`, `0xFE`, `0x00`, `0x79` | USB STALL | -| 7g. Wrong direction | `GET_CONFIG` as OUT | No hang | -| 7h. Oversized payload | 64-byte payload to `GET_CONFIG` | No hang | - -Unknown commands correctly return a USB STALL (the EP0 error response per USB spec). The firmware's `handle_vendorcommand()` returns FALSE for unrecognized request codes, which causes fx2lib to send the STALL handshake. - - - - -## Results - -The full test suite runs in under 30 seconds on hardware: - -``` -================================================================ - HAMILTON ADVERSARIAL TEST — FINAL RESULTS - ----------------------------------------- - Tests passed: 55 - Tests failed: 0 - Device alive: True - Final error: 0x08 [GPIF_TIMEOUT] - Watchdog fired: No - Verdict: PASS -================================================================ -``` - - - -## Running the Tests - -The test can be run after any firmware change to verify safety properties are preserved: - -```bash -# Load firmware and run tests -sudo python3 tools/fw_load.py load firmware/build/skywalker1.ihx --wait 5 -sudo python3 tools/test_hamilton.py -``` - -The test automatically boots the BCM4500, runs all 55 tests, and powers everything down. If any test fails, the device state and error code are reported. If the device becomes unresponsive, the test halts immediately and reports which operation killed it. +--- +title: Safety Testing +description: Hamilton adversarial test suite for firmware safety validation — operator error, invalid inputs, state machine violations, and rapid-fire stress. +--- + +import { Badge, Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; + +The Hamilton adversarial test suite (`test_hamilton.py`) validates that the firmware survives every kind of wrong input an operator or buggy host software could send. Named after Margaret Hamilton's methodology at MIT Instrumentation Laboratory — she tested "what if the astronaut pushes the wrong button?" and found bugs that saved Apollo missions. + +```bash +sudo python3 tools/test_hamilton.py +``` + + + +## Test Philosophy + +Traditional hardware testing validates the happy path — correct inputs producing correct outputs. Adversarial testing validates the *unhappy* path: what happens when the host sends garbage? The firmware must never: + +1. **Hang** — every code path must terminate in bounded time +2. **Corrupt state** — invalid inputs must not leave the device in an inconsistent state +3. **Fire the watchdog** — the main loop must stay responsive +4. **Become unresponsive** — the device must accept new commands after any error + +Each test follows the same pattern: + + +1. Read `last_error` before the operation +2. Send the invalid/abusive command +3. Verify the device is still alive (`GET_FW_VERS` returns `3.05.0`) +4. Read `last_error` after — check it matches expectations + + +## Test Categories + + + + +### Category 1: DiSEqC Message Abuse + +Tests the DiSEqC messaging path with invalid parameters. DiSEqC uses Timer2-based Manchester encoding on the 22 kHz carrier — a stuck timer would hang the firmware without the v3.05.0 `diseqc_wait_ticks()` timeout fix. + +| Test | Input | Expected | +|------|-------|----------| +| 1a. Tone burst B | `wValue=1` | `ERR_NOT_SUPPORTED` (0x0A) | +| 1b. Tone burst 0xFF | `wValue=0xFF` | `ERR_NOT_SUPPORTED` (0x0A) | +| 1c. Too short | 2-byte payload | No hang | +| 1d. Too long | 8-byte payload | No hang | +| 1e. Empty | 0-byte payload | No hang | +| 1f. Recovery | Valid 4-byte message | No hang, correct execution | +| 1g. Motor halt | DiSEqC 1.2 `E0 31 60` (no motor connected) | No hang | +| 1h. Drive 255 steps | DiSEqC 1.2 `E0 31 68 FF` | No hang | +| 1i. Bogus USALS | DiSEqC 1.2 `E0 31 6E FF FF` | No hang | + + + + +### Category 2: Tune Parameter Abuse + +Sends tuning commands with out-of-range parameters. The BCM4500 demodulator receives these values directly over I2C — the firmware must not hang regardless of what the host sends. + +| Test | Input | Expected | +|------|-------|----------| +| 2a. Zero symbol rate | `SR=0` | No hang | +| 2b. Max symbol rate | `SR=0xFFFFFFFF` | No hang | +| 2c. Zero frequency | `freq=0` | No hang | +| 2d. Max frequency | `freq=0xFFFFFFFF` | No hang | +| 2e. Invalid modulation | `mod=0xFF` | No hang | +| 2f. Invalid FEC | `fec=0xFF` | No hang | +| 2g. Truncated (4 bytes) | 4 of 10 bytes sent | `ERR_EP0_TIMEOUT` (0x07) | +| 2h. Single byte | 1 of 10 bytes sent | `ERR_EP0_TIMEOUT` (0x07) | +| 2i. All zeros | 10 zero bytes | No hang | +| 2j. All 0xFF | 10 × `0xFF` | No hang | + +Tests 2g and 2h specifically validate the `ep0_wait_data()` + `EP0BCL` payload length check. The firmware waits for the EP0 data phase, but when the host sends fewer bytes than expected, the timeout fires and the command is safely aborted. + + + + +### Category 3: I2C Address Space Abuse + +Sends I2C reads and writes to non-existent or invalid addresses. Before v3.05.0, the bus scan command (`0xB4`) used bare `while (!(I2CS & bmDONE))` loops that would hang if any address didn't ACK. + +| Test | Input | Expected | +|------|-------|----------| +| 3a. Addr 0x7F | Non-existent I2C device | `ERR_I2C_NAK` (0x02) | +| 3b. Addr 0x00 | General call address | No hang | +| 3c. Page 0xFF | BCM4500 indirect read, invalid page | No hang | +| 3d. Count=0 | Multi-reg read with zero count | No hang | +| 3e. Count=255 | Multi-reg read exceeding 64 max | No hang (clamped) | +| 3f. Write to 0x7F | Raw I2C write to non-existent device | No hang | +| 3g. Reserved reg | BCM4500 direct read of reg 0xFF | No hang | + + + + +### Category 4: State Machine Violations + +Tests operations in the wrong order — the classic "what if the astronaut pushes the wrong button?" scenario. + +| Test | Scenario | Expected | +|------|----------|----------| +| 4a. Double boot | `BOOT_8PSK(1)` when already booted | No hang | +| 4b. Tune with BCM off | Power off BCM4500, then tune | `ERR_BCM_NOT_READY` (0x04) | +| 4c. Signal read, BCM off | Signal monitor with demod powered down | No hang | +| 4d. Bus scan, BCM off | I2C bus scan with demod powered down | No hang | +| 4e. Hotplug, BCM off | Force hotplug rescan with demod off | No hang | +| 4f. Recovery | Re-boot BCM4500 after power cycle | `STARTED \| FW_LOADED` | +| 4g. Arm + disarm | Arm streaming then immediately disarm | No hang | +| 4h. Disarm, not armed | Disarm when already disarmed | No hang | +| 4i. Rapid boot toggle | Off → on → off → on in quick succession | No hang | + +Test 4b is the most important: it proves the `do_tune()` early guard works. Without the v3.05.0 fix, tuning with a powered-off BCM4500 would attempt I2C writes to a device that isn't there, potentially hanging on the bus. + + + + +### Category 5: Boundary and Buffer Abuse + +Tests USB transfer size mismatches — requesting more or fewer bytes than the firmware produces. + +| Test | Scenario | Expected | +|------|----------|----------| +| 5a. 0 bytes from GET_CONFIG | Request 0 bytes (firmware sends 1) | No hang | +| 5b. 64 bytes from GET_CONFIG | Request 64 bytes (firmware sends 1) | No hang | +| 5c. 64 bytes from GET_LAST_ERROR | Request 64 bytes (firmware sends 1) | No hang | +| 5d. 1 byte from GET_FW_VERS | Request 1 byte (firmware sends 6) | No hang | +| 5e. 1 byte from GET_STREAM_DIAG | Request 1 byte (firmware sends 12) | No hang | +| 5f. STREAM_DIAG wval=0xFFFF | Non-standard wValue | No hang | +| 5g. HOTPLUG wval=0xFFFF | Non-standard wValue | No hang | + +The USB layer handles size mismatches — the firmware writes its full response to EP0BUF regardless of how many bytes the host requested. The host-side libusb may return an `Overflow` error, but the device remains stable. + + + + +### Category 6: Rapid-Fire Stress + +Sends many commands in quick succession to test for timing-dependent failures. + +| Test | Operations | Typical Time | +|------|-----------|--------------| +| 6a. Config reads | 200 × `GET_CONFIG` | ~9 ms | +| 6b. Error reads | 50 × `GET_LAST_ERROR` | ~2 ms | +| 6c. Signal monitors | 30 × `SIGNAL_MONITOR` | ~276 ms | +| 6d. Voltage toggles | 40 × `SET_LNB_VOLTAGE` | ~2 ms | +| 6e. DiSEqC messages | 10 × 4-byte DiSEqC switch | ~1,473 ms | + +Single-byte vendor commands (config, error) complete in under 0.05 ms. Signal monitors are slower (~9 ms each) because they perform multiple I2C reads. DiSEqC messages are the slowest due to Timer2-based bit-bang encoding. + + + + +### Category 7: Invalid Vendor Commands + +Sends vendor command codes that don't exist in the firmware. + +| Test | Command | Expected | +|------|---------|----------| +| 7a-f. Unknown codes | `0xFF`, `0x01`, `0x50`, `0xFE`, `0x00`, `0x79` | USB STALL | +| 7g. Wrong direction | `GET_CONFIG` as OUT | No hang | +| 7h. Oversized payload | 64-byte payload to `GET_CONFIG` | No hang | + +Unknown commands correctly return a USB STALL (the EP0 error response per USB spec). The firmware's `handle_vendorcommand()` returns FALSE for unrecognized request codes, which causes fx2lib to send the STALL handshake. + + + + +## Results + +The full test suite runs in under 30 seconds on hardware: + +``` +================================================================ + HAMILTON ADVERSARIAL TEST — FINAL RESULTS + ----------------------------------------- + Tests passed: 55 + Tests failed: 0 + Device alive: True + Final error: 0x08 [GPIF_TIMEOUT] + Watchdog fired: No + Verdict: PASS +================================================================ +``` + + + +## Running the Tests + +The test can be run after any firmware change to verify safety properties are preserved: + +```bash +# Load firmware and run tests +sudo python3 tools/fw_load.py load firmware/build/skywalker1.ihx --wait 5 +sudo python3 tools/test_hamilton.py +``` + +The test automatically boots the BCM4500, runs all 55 tests, and powers everything down. If any test fails, the device state and error code are reported. If the device becomes unresponsive, the test halts immediately and reports which operation killed it. diff --git a/site/src/content/docs/tools/skywalker.mdx b/site/src/content/docs/tools/skywalker.mdx index b2f7963..3718e7c 100644 --- a/site/src/content/docs/tools/skywalker.mdx +++ b/site/src/content/docs/tools/skywalker.mdx @@ -1,329 +1,329 @@ ---- -title: SkyWalker Multi-Mode RF Tool -description: Spectrum analysis, transponder scanning, signal monitoring, L-band analysis, and carrier tracking for the SkyWalker-1. ---- - -import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; - -`skywalker.py` provides five alternative operating modes beyond satellite feed tuning. These modes repurpose the BCM4500's AGC registers as a crude power detector across the 950--2150 MHz IF range, allowing spectrum sweeps, blind transponder scanning, real-time dish alignment, L-band direct input analysis, and carrier tracking -- all without requiring demodulator lock. - - - -Commands `0xB7` (SIGNAL_MONITOR), `0xB8` (TUNE_MONITOR), and `0xB9` (BLIND_SCAN) must be present in the FX2 firmware. See [Custom v3.01](/firmware/custom-v301/) for the base firmware these commands build on. - -## Subcommands - -| Subcommand | Purpose | -|------------|---------| -| `spectrum` | Sweep spectrum analyzer (950--2150 MHz IF range) | -| `scan` | Automated transponder scanner (sweep + blind scan) | -| `monitor` | Real-time signal strength / dish alignment | -| `lband` | L-band direct input analyzer (no LNB) | -| `track` | Carrier/beacon tracker with logging | - -## Global Options - -| Flag | Description | -|------|-------------| -| `-v, --verbose` | Show raw USB traffic | - ---- - -## spectrum -- Sweep Spectrum Analyzer - -Sweeps the IF range using TUNE_MONITOR (`0xB8`) at each step and renders an ASCII bar chart to the terminal. Optionally produces waterfall displays, matplotlib plots, and CSV exports. - -### Options - -| Flag | Description | -|------|-------------| -| `--start FLOAT` | Start IF frequency in MHz (default: `950`) | -| `--stop FLOAT` | Stop IF frequency in MHz (default: `2150`) | -| `--step FLOAT` | Step size in MHz (default: `5`) | -| `--dwell INT` | Dwell time per step in ms (default: `10`) | -| `--lnb-lo FLOAT` | LNB LO frequency in MHz; 0 = direct input (default: `0`) | -| `--sr INT` | Symbol rate for measurement in ksps (default: `20000`) | -| `--waterfall` | Waterfall display (time x frequency x power) | -| `--sweeps INT` | Number of sweeps (default: `1`) | -| `--threshold FLOAT` | Peak detection threshold in dB above noise floor (default: `3`) | -| `--plot` | Show matplotlib plot after sweep completes | -| `--csv FILE` | Save sweep data to CSV | - -### Examples - -```bash title="Full IF range sweep" -sudo python3 tools/skywalker.py spectrum -``` - -```bash title="Narrow sweep with LNB LO, save CSV" -sudo python3 tools/skywalker.py spectrum --start 1200 --stop 1400 --step 2 --lnb-lo 9750 --csv sweep.csv -``` - -```bash title="Waterfall display, 10 sweeps" -sudo python3 tools/skywalker.py spectrum --waterfall --sweeps 10 --step 10 -``` - -```bash title="Plot with peak markers" -sudo python3 tools/skywalker.py spectrum --plot --threshold 5 -``` - -### Sample Output - -``` -SkyWalker-1 Spectrum Sweep -================================================== - Range: 950.0 - 2150.0 MHz IF (step 5.0 MHz) - Dwell: 10 ms/step SR: 20000 ksps - Steps: 240 Est. time: ~2.9s - - 950.0 | -62.3 dBm - 955.0 | -61.8 dBm - 960.0 | -62.1 dBm - ... -1120.0 | ████████████████████ -48.2 dBm -1125.0 | ██████████████████████████████████ -42.7 dBm << PEAK -1130.0 | ████████████████████ -48.5 dBm - ... -1920.0 | █████████████████████████████ -44.1 dBm << PEAK -1925.0 | ████████████████████████████████████ -41.3 dBm << PEAK -1930.0 | ████████████████████████ -46.0 dBm - ... -2145.0 | -63.1 dBm -2150.0 | -62.9 dBm - -Peaks detected (>3.0 dB above floor): - 1125.0 MHz IF -42.7 dBm (RF: 1125.0 MHz) - 1925.0 MHz IF -41.3 dBm (RF: 1925.0 MHz) - -Sweep complete: 240 steps in 2.88s -``` - - - - - ---- - -## scan -- Automated Transponder Scanner - -Performs a three-phase automated transponder search: coarse spectrum sweep, fine sweep around detected peaks, and blind scan at each refined peak across a range of symbol rates. - -### Phases - - -1. **Coarse sweep** at 10 MHz steps across the full IF range to identify candidate carriers. -2. **Fine sweep** at 2 MHz steps around each peak detected in the coarse pass. -3. **Blind scan** (`0xB3`) at each refined peak, trying symbol rates from `--sr-min` to `--sr-max` in `--sr-step` increments. - - -### Options - -| Flag | Description | -|------|-------------| -| `--start FLOAT` | Start IF frequency in MHz (default: `950`) | -| `--stop FLOAT` | Stop IF frequency in MHz (default: `2150`) | -| `--threshold FLOAT` | Peak detection threshold in dB (default: `3`) | -| `--sr-min INT` | Minimum symbol rate in ksps (default: `1000`) | -| `--sr-max INT` | Maximum symbol rate in ksps (default: `30000`) | -| `--sr-step INT` | Symbol rate step in ksps (default: `500`) | -| `--lnb-lo FLOAT` | LNB LO frequency in MHz (default: `9750`) | -| `--pol H/V/L/R` | Polarization | -| `--band low/high` | LNB band | -| `--json` | JSON output | -| `--csv FILE` | CSV output | - -### Examples - -```bash title="Full Ku-band low scan, horizontal" -sudo python3 tools/skywalker.py scan --pol H --band low -``` - -```bash title="Narrow scan, fast SR range, JSON output" -sudo python3 tools/skywalker.py scan --start 1100 --stop 1200 --sr-min 10000 --sr-max 30000 --sr-step 1000 --json -``` - -```bash title="C-band scan with custom LO" -sudo python3 tools/skywalker.py scan --lnb-lo 5150 --pol V --csv cband_scan.csv -``` - - - ---- - -## monitor -- Real-Time Signal Strength - -Tunes to a specified frequency and symbol rate, then polls SIGNAL_MONITOR (`0xB7`) at a configurable rate to display a continuously updating signal strength indicator. Designed for hands-free dish alignment. - -### Required Arguments - -| Argument | Description | -|----------|-------------| -| `FREQ_MHZ` | Transponder frequency in MHz | -| `SR_KSPS` | Symbol rate in ksps | - -### Options - -| Flag | Description | -|------|-------------| -| `--pol H/V/L/R` | Polarization | -| `--band low/high` | LNB band | -| `--lnb-lo FLOAT` | LNB LO frequency in MHz (default: `9750`) | -| `--rate FLOAT` | Poll rate in Hz (default: `10`, max ~50) | -| `--audio` | Pitch-proportional beep for hands-free alignment | -| `--peak-hold` | Track and display maximum signal level | -| `--history INT` | Sparkline history length in samples (default: `60`) | -| `--plot` | Show matplotlib plot after stopping | - -### Examples - -```bash title="Basic monitor, horizontal low-band" -sudo python3 tools/skywalker.py monitor 12520 27500 --pol H --band high -``` - -```bash title="Audio-assisted dish alignment" -sudo python3 tools/skywalker.py monitor 11836 27500 --pol V --band low --audio --peak-hold -``` - -```bash title="Fast polling with long history" -sudo python3 tools/skywalker.py monitor 12520 27500 --pol H --band high --rate 30 --history 120 -``` - -### Display Format - -``` -[LOCK] SNR 8.2dB AGC 0x3A [████████████████░░░░░░░░░░░░░░░░░░░░░░░░] 42% ▁▃▅▇█▇▅▃▅▇█▇▅▃▁ -[----] SNR 0.0dB AGC 0xFF [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0% ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -``` - -The display updates in-place using terminal control codes. The sparkline at the right edge shows `--history` samples of recent signal strength. Press Ctrl-C to stop. - - - - - ---- - -## lband -- L-Band Direct Input Analyzer - -Identical to `spectrum` mode but with `lnb_lo=0` and LNB power disabled. Intended for direct RF input in the 950--2150 MHz range (L-band). Annotates known L-band frequency allocations in the output. - -### Options - -| Flag | Description | -|------|-------------| -| `--start FLOAT` | Start frequency in MHz (default: `950`) | -| `--stop FLOAT` | Stop frequency in MHz (default: `2150`) | -| `--step FLOAT` | Step size in MHz (default: `2`) | -| `--dwell INT` | Dwell time per step in ms (default: `20`) | -| `--23cm` | Narrow to 1240--1300 MHz with 500 kHz steps | -| `--band-info` | Print L-band allocation table and exit | -| `--waterfall` | Waterfall display | -| `--plot` | Show matplotlib plot | -| `--csv FILE` | Save data to CSV | - -### L-Band Frequency Allocations - -| Range (MHz) | Service | -|-------------|---------| -| 1240--1300 | Amateur 23cm | -| 1525--1559 | Inmarsat downlink | -| 1559--1610 | GNSS (GPS L1, Galileo E1) | -| 1610--1626 | Iridium downlink | -| 1670--1710 | MetSat (GOES LRIT, NOAA HRPT) | -| 1710--1785 | LTE/AWS uplink | -| 1920--2025 | UMTS uplink | - -### Examples - -```bash title="Full L-band sweep" -sudo python3 tools/skywalker.py lband -``` - -```bash title="23cm amateur band, fine resolution" -sudo python3 tools/skywalker.py lband --23cm -``` - -```bash title="Waterfall with CSV export" -sudo python3 tools/skywalker.py lband --waterfall --csv lband_sweep.csv -``` - -```bash title="Print allocation table only" -sudo python3 tools/skywalker.py lband --band-info -``` - - - - - ---- - -## track -- Carrier/Beacon Tracker - -Tunes to a single frequency and symbol rate, then polls SIGNAL_MONITOR continuously with timestamped logging. Detects lock/unlock transitions and optionally tracks frequency drift by periodically sweeping a narrow window around the target. - -### Required Arguments - -| Argument | Description | -|----------|-------------| -| `FREQ_MHZ` | Carrier frequency in MHz | -| `SR_KSPS` | Symbol rate in ksps | - -### Options - -| Flag | Description | -|------|-------------| -| `--pol H/V/L/R` | Polarization | -| `--band low/high` | LNB band | -| `--lnb-lo FLOAT` | LNB LO frequency in MHz (default: `9750`) | -| `--rate FLOAT` | Poll rate in Hz (default: `1`) | -| `--duration FLOAT` | Duration in seconds (default: until Ctrl-C) | -| `--log FILE` | CSV log file | -| `--drift-track` | Sweep +/-5 MHz every 5s to measure frequency drift | -| `--plot` | Show matplotlib plot after stopping | -| `--json-lines FILE` | JSON-lines log file | - -### Examples - -```bash title="Track a beacon for 1 hour, log to CSV" -sudo python3 tools/skywalker.py track 11700 27500 --pol V --band low --duration 3600 --log beacon.csv -``` - -```bash title="Drift tracking with JSON-lines output" -sudo python3 tools/skywalker.py track 12520 27500 --pol H --band high --drift-track --json-lines drift.jsonl -``` - -```bash title="Quick track with matplotlib plot on exit" -sudo python3 tools/skywalker.py track 11836 27500 --pol V --band low --rate 5 --plot -``` - - - - - ---- - -## See Also - -- [Signal Monitoring](/bcm4500/signal-monitoring/) -- SIGNAL_MONITOR register protocol and data format -- [Vendor Commands](/usb/vendor-commands/) -- complete USB vendor command reference including 0xB7--0xB9 -- [Tuning Tool](/tools/tuning/) -- primary tuning, LNB control, and transport stream capture -- [RF Coverage](/hardware/rf-coverage/) -- frequency coverage and antenna considerations -- [Spectrum Analysis How-to](/tools/spectrum-analysis/) -- practical guides for each operating mode +--- +title: SkyWalker Multi-Mode RF Tool +description: Spectrum analysis, transponder scanning, signal monitoring, L-band analysis, and carrier tracking for the SkyWalker-1. +--- + +import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; + +`skywalker.py` provides five alternative operating modes beyond satellite feed tuning. These modes repurpose the BCM4500's AGC registers as a crude power detector across the 950--2150 MHz IF range, allowing spectrum sweeps, blind transponder scanning, real-time dish alignment, L-band direct input analysis, and carrier tracking -- all without requiring demodulator lock. + + + +Commands `0xB7` (SIGNAL_MONITOR), `0xB8` (TUNE_MONITOR), and `0xB9` (BLIND_SCAN) must be present in the FX2 firmware. See [Custom v3.01](/firmware/custom-v301/) for the base firmware these commands build on. + +## Subcommands + +| Subcommand | Purpose | +|------------|---------| +| `spectrum` | Sweep spectrum analyzer (950--2150 MHz IF range) | +| `scan` | Automated transponder scanner (sweep + blind scan) | +| `monitor` | Real-time signal strength / dish alignment | +| `lband` | L-band direct input analyzer (no LNB) | +| `track` | Carrier/beacon tracker with logging | + +## Global Options + +| Flag | Description | +|------|-------------| +| `-v, --verbose` | Show raw USB traffic | + +--- + +## spectrum -- Sweep Spectrum Analyzer + +Sweeps the IF range using TUNE_MONITOR (`0xB8`) at each step and renders an ASCII bar chart to the terminal. Optionally produces waterfall displays, matplotlib plots, and CSV exports. + +### Options + +| Flag | Description | +|------|-------------| +| `--start FLOAT` | Start IF frequency in MHz (default: `950`) | +| `--stop FLOAT` | Stop IF frequency in MHz (default: `2150`) | +| `--step FLOAT` | Step size in MHz (default: `5`) | +| `--dwell INT` | Dwell time per step in ms (default: `10`) | +| `--lnb-lo FLOAT` | LNB LO frequency in MHz; 0 = direct input (default: `0`) | +| `--sr INT` | Symbol rate for measurement in ksps (default: `20000`) | +| `--waterfall` | Waterfall display (time x frequency x power) | +| `--sweeps INT` | Number of sweeps (default: `1`) | +| `--threshold FLOAT` | Peak detection threshold in dB above noise floor (default: `3`) | +| `--plot` | Show matplotlib plot after sweep completes | +| `--csv FILE` | Save sweep data to CSV | + +### Examples + +```bash title="Full IF range sweep" +sudo python3 tools/skywalker.py spectrum +``` + +```bash title="Narrow sweep with LNB LO, save CSV" +sudo python3 tools/skywalker.py spectrum --start 1200 --stop 1400 --step 2 --lnb-lo 9750 --csv sweep.csv +``` + +```bash title="Waterfall display, 10 sweeps" +sudo python3 tools/skywalker.py spectrum --waterfall --sweeps 10 --step 10 +``` + +```bash title="Plot with peak markers" +sudo python3 tools/skywalker.py spectrum --plot --threshold 5 +``` + +### Sample Output + +``` +SkyWalker-1 Spectrum Sweep +================================================== + Range: 950.0 - 2150.0 MHz IF (step 5.0 MHz) + Dwell: 10 ms/step SR: 20000 ksps + Steps: 240 Est. time: ~2.9s + + 950.0 | -62.3 dBm + 955.0 | -61.8 dBm + 960.0 | -62.1 dBm + ... +1120.0 | ████████████████████ -48.2 dBm +1125.0 | ██████████████████████████████████ -42.7 dBm << PEAK +1130.0 | ████████████████████ -48.5 dBm + ... +1920.0 | █████████████████████████████ -44.1 dBm << PEAK +1925.0 | ████████████████████████████████████ -41.3 dBm << PEAK +1930.0 | ████████████████████████ -46.0 dBm + ... +2145.0 | -63.1 dBm +2150.0 | -62.9 dBm + +Peaks detected (>3.0 dB above floor): + 1125.0 MHz IF -42.7 dBm (RF: 1125.0 MHz) + 1925.0 MHz IF -41.3 dBm (RF: 1925.0 MHz) + +Sweep complete: 240 steps in 2.88s +``` + + + + + +--- + +## scan -- Automated Transponder Scanner + +Performs a three-phase automated transponder search: coarse spectrum sweep, fine sweep around detected peaks, and blind scan at each refined peak across a range of symbol rates. + +### Phases + + +1. **Coarse sweep** at 10 MHz steps across the full IF range to identify candidate carriers. +2. **Fine sweep** at 2 MHz steps around each peak detected in the coarse pass. +3. **Blind scan** (`0xB3`) at each refined peak, trying symbol rates from `--sr-min` to `--sr-max` in `--sr-step` increments. + + +### Options + +| Flag | Description | +|------|-------------| +| `--start FLOAT` | Start IF frequency in MHz (default: `950`) | +| `--stop FLOAT` | Stop IF frequency in MHz (default: `2150`) | +| `--threshold FLOAT` | Peak detection threshold in dB (default: `3`) | +| `--sr-min INT` | Minimum symbol rate in ksps (default: `1000`) | +| `--sr-max INT` | Maximum symbol rate in ksps (default: `30000`) | +| `--sr-step INT` | Symbol rate step in ksps (default: `500`) | +| `--lnb-lo FLOAT` | LNB LO frequency in MHz (default: `9750`) | +| `--pol H/V/L/R` | Polarization | +| `--band low/high` | LNB band | +| `--json` | JSON output | +| `--csv FILE` | CSV output | + +### Examples + +```bash title="Full Ku-band low scan, horizontal" +sudo python3 tools/skywalker.py scan --pol H --band low +``` + +```bash title="Narrow scan, fast SR range, JSON output" +sudo python3 tools/skywalker.py scan --start 1100 --stop 1200 --sr-min 10000 --sr-max 30000 --sr-step 1000 --json +``` + +```bash title="C-band scan with custom LO" +sudo python3 tools/skywalker.py scan --lnb-lo 5150 --pol V --csv cband_scan.csv +``` + + + +--- + +## monitor -- Real-Time Signal Strength + +Tunes to a specified frequency and symbol rate, then polls SIGNAL_MONITOR (`0xB7`) at a configurable rate to display a continuously updating signal strength indicator. Designed for hands-free dish alignment. + +### Required Arguments + +| Argument | Description | +|----------|-------------| +| `FREQ_MHZ` | Transponder frequency in MHz | +| `SR_KSPS` | Symbol rate in ksps | + +### Options + +| Flag | Description | +|------|-------------| +| `--pol H/V/L/R` | Polarization | +| `--band low/high` | LNB band | +| `--lnb-lo FLOAT` | LNB LO frequency in MHz (default: `9750`) | +| `--rate FLOAT` | Poll rate in Hz (default: `10`, max ~50) | +| `--audio` | Pitch-proportional beep for hands-free alignment | +| `--peak-hold` | Track and display maximum signal level | +| `--history INT` | Sparkline history length in samples (default: `60`) | +| `--plot` | Show matplotlib plot after stopping | + +### Examples + +```bash title="Basic monitor, horizontal low-band" +sudo python3 tools/skywalker.py monitor 12520 27500 --pol H --band high +``` + +```bash title="Audio-assisted dish alignment" +sudo python3 tools/skywalker.py monitor 11836 27500 --pol V --band low --audio --peak-hold +``` + +```bash title="Fast polling with long history" +sudo python3 tools/skywalker.py monitor 12520 27500 --pol H --band high --rate 30 --history 120 +``` + +### Display Format + +``` +[LOCK] SNR 8.2dB AGC 0x3A [████████████████░░░░░░░░░░░░░░░░░░░░░░░░] 42% ▁▃▅▇█▇▅▃▅▇█▇▅▃▁ +[----] SNR 0.0dB AGC 0xFF [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0% ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +``` + +The display updates in-place using terminal control codes. The sparkline at the right edge shows `--history` samples of recent signal strength. Press Ctrl-C to stop. + + + + + +--- + +## lband -- L-Band Direct Input Analyzer + +Identical to `spectrum` mode but with `lnb_lo=0` and LNB power disabled. Intended for direct RF input in the 950--2150 MHz range (L-band). Annotates known L-band frequency allocations in the output. + +### Options + +| Flag | Description | +|------|-------------| +| `--start FLOAT` | Start frequency in MHz (default: `950`) | +| `--stop FLOAT` | Stop frequency in MHz (default: `2150`) | +| `--step FLOAT` | Step size in MHz (default: `2`) | +| `--dwell INT` | Dwell time per step in ms (default: `20`) | +| `--23cm` | Narrow to 1240--1300 MHz with 500 kHz steps | +| `--band-info` | Print L-band allocation table and exit | +| `--waterfall` | Waterfall display | +| `--plot` | Show matplotlib plot | +| `--csv FILE` | Save data to CSV | + +### L-Band Frequency Allocations + +| Range (MHz) | Service | +|-------------|---------| +| 1240--1300 | Amateur 23cm | +| 1525--1559 | Inmarsat downlink | +| 1559--1610 | GNSS (GPS L1, Galileo E1) | +| 1610--1626 | Iridium downlink | +| 1670--1710 | MetSat (GOES LRIT, NOAA HRPT) | +| 1710--1785 | LTE/AWS uplink | +| 1920--2025 | UMTS uplink | + +### Examples + +```bash title="Full L-band sweep" +sudo python3 tools/skywalker.py lband +``` + +```bash title="23cm amateur band, fine resolution" +sudo python3 tools/skywalker.py lband --23cm +``` + +```bash title="Waterfall with CSV export" +sudo python3 tools/skywalker.py lband --waterfall --csv lband_sweep.csv +``` + +```bash title="Print allocation table only" +sudo python3 tools/skywalker.py lband --band-info +``` + + + + + +--- + +## track -- Carrier/Beacon Tracker + +Tunes to a single frequency and symbol rate, then polls SIGNAL_MONITOR continuously with timestamped logging. Detects lock/unlock transitions and optionally tracks frequency drift by periodically sweeping a narrow window around the target. + +### Required Arguments + +| Argument | Description | +|----------|-------------| +| `FREQ_MHZ` | Carrier frequency in MHz | +| `SR_KSPS` | Symbol rate in ksps | + +### Options + +| Flag | Description | +|------|-------------| +| `--pol H/V/L/R` | Polarization | +| `--band low/high` | LNB band | +| `--lnb-lo FLOAT` | LNB LO frequency in MHz (default: `9750`) | +| `--rate FLOAT` | Poll rate in Hz (default: `1`) | +| `--duration FLOAT` | Duration in seconds (default: until Ctrl-C) | +| `--log FILE` | CSV log file | +| `--drift-track` | Sweep +/-5 MHz every 5s to measure frequency drift | +| `--plot` | Show matplotlib plot after stopping | +| `--json-lines FILE` | JSON-lines log file | + +### Examples + +```bash title="Track a beacon for 1 hour, log to CSV" +sudo python3 tools/skywalker.py track 11700 27500 --pol V --band low --duration 3600 --log beacon.csv +``` + +```bash title="Drift tracking with JSON-lines output" +sudo python3 tools/skywalker.py track 12520 27500 --pol H --band high --drift-track --json-lines drift.jsonl +``` + +```bash title="Quick track with matplotlib plot on exit" +sudo python3 tools/skywalker.py track 11836 27500 --pol V --band low --rate 5 --plot +``` + + + + + +--- + +## See Also + +- [Signal Monitoring](/bcm4500/signal-monitoring/) -- SIGNAL_MONITOR register protocol and data format +- [Vendor Commands](/usb/vendor-commands/) -- complete USB vendor command reference including 0xB7--0xB9 +- [Tuning Tool](/tools/tuning/) -- primary tuning, LNB control, and transport stream capture +- [RF Coverage](/hardware/rf-coverage/) -- frequency coverage and antenna considerations +- [Spectrum Analysis How-to](/tools/spectrum-analysis/) -- practical guides for each operating mode diff --git a/site/src/content/docs/tools/spectrum-analysis.mdx b/site/src/content/docs/tools/spectrum-analysis.mdx index 6c33463..1d2135a 100644 --- a/site/src/content/docs/tools/spectrum-analysis.mdx +++ b/site/src/content/docs/tools/spectrum-analysis.mdx @@ -1,212 +1,212 @@ ---- -title: Spectrum Analysis Guides -description: How-to guides for spectrum sweeps, transponder scanning, dish alignment, L-band monitoring, and carrier tracking. ---- - -import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; - -The SkyWalker-1's BCM4500 demodulator and BCM3440 tuner accept any signal in the 950--2150 MHz IF range. While the demod pipeline only locks to DVB-S, Turbo, DCII, and DSS carriers, the AGC registers respond to RF energy at any frequency in range. Custom firmware v3.02.0 exposes this through `SIGNAL_MONITOR` (0xB7) and `TUNE_MONITOR` (0xB8) commands, turning the hardware into a crude but useful power detector. - -`skywalker.py` wraps these commands into practical modes: spectrum sweeps, transponder scanning, dish alignment monitoring, L-band direct input, and long-duration carrier tracking. - -```bash -pip install pyusb matplotlib -``` - ---- - -## How to Sweep the Ku-Band Spectrum - -Survey what transponders are active on a satellite by stepping across the IF range and recording AGC power at each frequency. - - -1. Confirm the dish is pointed at the target satellite and the LNB coax is connected to the SkyWalker-1 F-connector. - -2. Run a spectrum sweep. The tool steps from `--start` to `--stop` in MHz (IF frequency), reading AGC power at each step. Specify your LNB local oscillator frequency so the output labels show true downlink frequencies. - - ```bash title="Sweep Ku-band low (H-pol)" - sudo python3 tools/skywalker.py spectrum --lnb-lo 9750 --start 950 --stop 2150 --step 5 - ``` - - ```bash title="Sweep Ku-band high (V-pol)" - sudo python3 tools/skywalker.py spectrum --lnb-lo 10600 --start 950 --stop 2150 --step 5 - ``` - -3. Look for peaks in the output. Each peak corresponds to a carrier -- the wider the peak, the higher the symbol rate. The AGC value is relative, not calibrated, so compare peaks to each other rather than treating the numbers as absolute power. - -4. Note the downlink frequencies of interesting peaks. Use these as inputs to the [Tuning Tool](/tools/tuning/) to attempt demodulator lock, or feed them into the transponder scanner below for automated identification. - - - - ---- - -## How to Find Transponders Automatically - -The scan mode automates the full process of discovering and identifying transponders on a satellite. It runs three phases internally: coarse AGC sweep, peak refinement, and blind demod lock attempts. - - -1. Start a scan with your LNB parameters and a detection threshold. The threshold controls how many dB above the noise floor a peak must be to count as a candidate. - - ```bash title="Full satellite scan, H-pol high band" - sudo python3 tools/skywalker.py scan --lnb-lo 10600 --pol H --band high --threshold 3 - ``` - -2. The scanner runs three phases automatically: - - - **Phase 1 -- Coarse sweep**: Steps across the full IF range reading AGC power. Builds a noise floor baseline and identifies candidate peaks above the threshold. - - **Phase 2 -- Peak refinement**: Re-sweeps each candidate with finer step size to pinpoint center frequencies and estimate bandwidths. - - **Phase 3 -- Blind scan**: Attempts demodulator lock at each refined peak, cycling through modulation types (QPSK, Turbo QPSK, Turbo 8PSK, DCII) and common symbol rates. Reports lock status, SNR, and modulation parameters for each successful lock. - -3. Review the results. Successfully locked transponders are reported with their downlink frequency, symbol rate, modulation type, FEC rate, and SNR. Peaks that produced AGC response but no demod lock may be DVB-S2, analog, or non-satellite signals. - - - - ---- - -## How to Align a Dish - -The monitor mode provides real-time signal quality feedback at high poll rates, suitable for peaking a dish on a known transponder. - - -1. Pick a known strong transponder on the target satellite. You need the downlink frequency and symbol rate. Transponder lists for most satellites are available from LyngSat or SatBeams. - -2. Start the signal monitor at a high poll rate. The tool tunes to the specified transponder and continuously reads SNR and lock status. - - ```bash title="Monitor for dish alignment (50 Hz polling)" - sudo python3 tools/skywalker.py monitor 12520 27500 --lnb-lo 10600 --pol H --band high --rate 50 --audio --peak-hold - ``` - -3. Slowly move the dish in azimuth and elevation while watching the SNR reading. The `--peak-hold` flag keeps the highest observed value on screen so you can tell when you have passed the peak. - -4. The `--audio` flag produces a pitch-proportional tone through the system audio -- higher pitch means stronger signal. This frees you from watching the screen while adjusting the dish. - -5. Fine-tune for maximum SNR. Once you find the general peak, make small adjustments in azimuth, then elevation, then azimuth again. Tighten the mount hardware and verify the SNR holds. - - - - ---- - -## How to Monitor L-Band Direct - -The SkyWalker-1 accepts 950--2150 MHz directly at the F-connector. With no LNB in the path, you can survey L-band signals from a direct-connect antenna or feed. - - -1. Disconnect the LNB coax cable. Connect your L-band antenna, preamp, or feed directly to the SkyWalker-1 F-connector. - -2. Run the `lband` mode. This automatically disables LNB power output to protect direct-connect equipment, then sweeps the IF range with zero LO offset. - - ```bash title="Full L-band survey with allocation annotations" - sudo python3 tools/skywalker.py lband --band-info - ``` - -3. The `--band-info` flag annotates the sweep output with ITU frequency allocation data for the 950--2150 MHz range, identifying which services (GPS, Iridium, Inmarsat, amateur, weather satellite, ATC) occupy each segment. - -4. Narrow to a specific band of interest for higher resolution sweeps. - - ```bash title="Focus on 23cm amateur band" - sudo python3 tools/skywalker.py lband --23cm --plot - ``` - - ```bash title="Focus on weather satellite frequencies" - sudo python3 tools/skywalker.py lband --start 1670 --stop 1710 --step 0.5 --dwell 50 - ``` - - - - - - ---- - -## How to Receive QO-100 DATV - -The Es'hail 2 / QO-100 geostationary amateur satellite carries DVB-S QPSK signals on its narrowband transponder at 10489--10499 MHz downlink. This is the one scenario where amateur satellite signals are both in the SkyWalker-1's IF range AND use a compatible modulation scheme. - -The catch: a standard 9.75 GHz universal LNB places QO-100 at ~741 MHz IF, which is below the 950 MHz minimum. You need a modified or purpose-built LNB with a lower LO frequency. - -| LNB LO | QO-100 IF Range | In Range? | -|--------|----------------|-----------| -| 9750 MHz (standard) | 739 -- 749 MHz | No | -| 9000 MHz (custom) | 1489 -- 1499 MHz | Yes | -| 9250 MHz (custom) | 1239 -- 1249 MHz | Yes | - -With a suitable LNB, sweep the QO-100 DATV range: - -```bash title="Sweep QO-100 DATV range with custom 9.0 GHz LO LNB" -sudo python3 tools/skywalker.py spectrum --lnb-lo 9000 --start 1480 --stop 1510 --step 1 --dwell 30 -``` - -To tune to a specific carrier once you have identified it: - -```bash title="Tune to a specific QO-100 DATV carrier" -sudo python3 tools/tune.py tune 10494 1000 --lnb-lo 9000 --mod qpsk --fec auto -``` - - - ---- - -## How to Track a Carrier Over Time - -Track mode continuously monitors a transponder's signal quality and logs timestamped samples. Useful for rain fade analysis, antenna drift detection, and long-term signal characterization. - - -1. Tune to the target transponder frequency and symbol rate, specifying LNB parameters as usual. - -2. Start tracking with logging enabled. Specify a duration in seconds, or omit `--duration` to run until Ctrl-C. - - ```bash title="Track a transponder for 1 hour with CSV logging" - sudo python3 tools/skywalker.py track 12520 27500 --lnb-lo 10600 --pol H --band high --log signal_log.csv --duration 3600 - ``` - -3. The tracker logs SNR, lock status, and AGC values at each sample. Lock/unlock transitions are flagged in the log for easy filtering. - -4. For frequency drift analysis, add `--drift-track` to monitor the demodulator's carrier frequency offset over time. - - ```bash title="Track with drift detection" - sudo python3 tools/skywalker.py track 12520 27500 --lnb-lo 10600 --pol H --band high --drift-track --log drift.csv - ``` - - - - ---- - -## How to Export Data - -All spectrum analysis modes support data export. Choose the format that fits your workflow. - -| Flag | Applicable Modes | Output Format | -|------|-----------------|---------------| -| `--csv FILE` | `spectrum`, `scan`, `lband` | Comma-separated: frequency, power, annotations | -| `--log FILE` | `track` | Timestamped CSV: time, frequency, SNR, lock, AGC | -| `--json-lines FILE` | `track` | One JSON object per sample, newline-delimited | -| `--json` | `scan` | Full scan results as a single JSON document | -| `--plot` | `spectrum`, `scan`, `lband` | Interactive matplotlib window | - -Combine `--csv` or `--json` with `--plot` to both save data and visualize it in a single run. - ---- - -## See Also - -- [Tuning Tool](/tools/tuning/) -- transponder tuning, LNB control, transport stream capture -- [RF Specifications](/hardware/rf-specifications/) -- IF range, symbol rate limits, LNB current limits -- [Signal Monitoring](/bcm4500/signal-monitoring/) -- SNR, lock status, and AGC register details +--- +title: Spectrum Analysis Guides +description: How-to guides for spectrum sweeps, transponder scanning, dish alignment, L-band monitoring, and carrier tracking. +--- + +import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; + +The SkyWalker-1's BCM4500 demodulator and BCM3440 tuner accept any signal in the 950--2150 MHz IF range. While the demod pipeline only locks to DVB-S, Turbo, DCII, and DSS carriers, the AGC registers respond to RF energy at any frequency in range. Custom firmware v3.02.0 exposes this through `SIGNAL_MONITOR` (0xB7) and `TUNE_MONITOR` (0xB8) commands, turning the hardware into a crude but useful power detector. + +`skywalker.py` wraps these commands into practical modes: spectrum sweeps, transponder scanning, dish alignment monitoring, L-band direct input, and long-duration carrier tracking. + +```bash +pip install pyusb matplotlib +``` + +--- + +## How to Sweep the Ku-Band Spectrum + +Survey what transponders are active on a satellite by stepping across the IF range and recording AGC power at each frequency. + + +1. Confirm the dish is pointed at the target satellite and the LNB coax is connected to the SkyWalker-1 F-connector. + +2. Run a spectrum sweep. The tool steps from `--start` to `--stop` in MHz (IF frequency), reading AGC power at each step. Specify your LNB local oscillator frequency so the output labels show true downlink frequencies. + + ```bash title="Sweep Ku-band low (H-pol)" + sudo python3 tools/skywalker.py spectrum --lnb-lo 9750 --start 950 --stop 2150 --step 5 + ``` + + ```bash title="Sweep Ku-band high (V-pol)" + sudo python3 tools/skywalker.py spectrum --lnb-lo 10600 --start 950 --stop 2150 --step 5 + ``` + +3. Look for peaks in the output. Each peak corresponds to a carrier -- the wider the peak, the higher the symbol rate. The AGC value is relative, not calibrated, so compare peaks to each other rather than treating the numbers as absolute power. + +4. Note the downlink frequencies of interesting peaks. Use these as inputs to the [Tuning Tool](/tools/tuning/) to attempt demodulator lock, or feed them into the transponder scanner below for automated identification. + + + + +--- + +## How to Find Transponders Automatically + +The scan mode automates the full process of discovering and identifying transponders on a satellite. It runs three phases internally: coarse AGC sweep, peak refinement, and blind demod lock attempts. + + +1. Start a scan with your LNB parameters and a detection threshold. The threshold controls how many dB above the noise floor a peak must be to count as a candidate. + + ```bash title="Full satellite scan, H-pol high band" + sudo python3 tools/skywalker.py scan --lnb-lo 10600 --pol H --band high --threshold 3 + ``` + +2. The scanner runs three phases automatically: + + - **Phase 1 -- Coarse sweep**: Steps across the full IF range reading AGC power. Builds a noise floor baseline and identifies candidate peaks above the threshold. + - **Phase 2 -- Peak refinement**: Re-sweeps each candidate with finer step size to pinpoint center frequencies and estimate bandwidths. + - **Phase 3 -- Blind scan**: Attempts demodulator lock at each refined peak, cycling through modulation types (QPSK, Turbo QPSK, Turbo 8PSK, DCII) and common symbol rates. Reports lock status, SNR, and modulation parameters for each successful lock. + +3. Review the results. Successfully locked transponders are reported with their downlink frequency, symbol rate, modulation type, FEC rate, and SNR. Peaks that produced AGC response but no demod lock may be DVB-S2, analog, or non-satellite signals. + + + + +--- + +## How to Align a Dish + +The monitor mode provides real-time signal quality feedback at high poll rates, suitable for peaking a dish on a known transponder. + + +1. Pick a known strong transponder on the target satellite. You need the downlink frequency and symbol rate. Transponder lists for most satellites are available from LyngSat or SatBeams. + +2. Start the signal monitor at a high poll rate. The tool tunes to the specified transponder and continuously reads SNR and lock status. + + ```bash title="Monitor for dish alignment (50 Hz polling)" + sudo python3 tools/skywalker.py monitor 12520 27500 --lnb-lo 10600 --pol H --band high --rate 50 --audio --peak-hold + ``` + +3. Slowly move the dish in azimuth and elevation while watching the SNR reading. The `--peak-hold` flag keeps the highest observed value on screen so you can tell when you have passed the peak. + +4. The `--audio` flag produces a pitch-proportional tone through the system audio -- higher pitch means stronger signal. This frees you from watching the screen while adjusting the dish. + +5. Fine-tune for maximum SNR. Once you find the general peak, make small adjustments in azimuth, then elevation, then azimuth again. Tighten the mount hardware and verify the SNR holds. + + + + +--- + +## How to Monitor L-Band Direct + +The SkyWalker-1 accepts 950--2150 MHz directly at the F-connector. With no LNB in the path, you can survey L-band signals from a direct-connect antenna or feed. + + +1. Disconnect the LNB coax cable. Connect your L-band antenna, preamp, or feed directly to the SkyWalker-1 F-connector. + +2. Run the `lband` mode. This automatically disables LNB power output to protect direct-connect equipment, then sweeps the IF range with zero LO offset. + + ```bash title="Full L-band survey with allocation annotations" + sudo python3 tools/skywalker.py lband --band-info + ``` + +3. The `--band-info` flag annotates the sweep output with ITU frequency allocation data for the 950--2150 MHz range, identifying which services (GPS, Iridium, Inmarsat, amateur, weather satellite, ATC) occupy each segment. + +4. Narrow to a specific band of interest for higher resolution sweeps. + + ```bash title="Focus on 23cm amateur band" + sudo python3 tools/skywalker.py lband --23cm --plot + ``` + + ```bash title="Focus on weather satellite frequencies" + sudo python3 tools/skywalker.py lband --start 1670 --stop 1710 --step 0.5 --dwell 50 + ``` + + + + + + +--- + +## How to Receive QO-100 DATV + +The Es'hail 2 / QO-100 geostationary amateur satellite carries DVB-S QPSK signals on its narrowband transponder at 10489--10499 MHz downlink. This is the one scenario where amateur satellite signals are both in the SkyWalker-1's IF range AND use a compatible modulation scheme. + +The catch: a standard 9.75 GHz universal LNB places QO-100 at ~741 MHz IF, which is below the 950 MHz minimum. You need a modified or purpose-built LNB with a lower LO frequency. + +| LNB LO | QO-100 IF Range | In Range? | +|--------|----------------|-----------| +| 9750 MHz (standard) | 739 -- 749 MHz | No | +| 9000 MHz (custom) | 1489 -- 1499 MHz | Yes | +| 9250 MHz (custom) | 1239 -- 1249 MHz | Yes | + +With a suitable LNB, sweep the QO-100 DATV range: + +```bash title="Sweep QO-100 DATV range with custom 9.0 GHz LO LNB" +sudo python3 tools/skywalker.py spectrum --lnb-lo 9000 --start 1480 --stop 1510 --step 1 --dwell 30 +``` + +To tune to a specific carrier once you have identified it: + +```bash title="Tune to a specific QO-100 DATV carrier" +sudo python3 tools/tune.py tune 10494 1000 --lnb-lo 9000 --mod qpsk --fec auto +``` + + + +--- + +## How to Track a Carrier Over Time + +Track mode continuously monitors a transponder's signal quality and logs timestamped samples. Useful for rain fade analysis, antenna drift detection, and long-term signal characterization. + + +1. Tune to the target transponder frequency and symbol rate, specifying LNB parameters as usual. + +2. Start tracking with logging enabled. Specify a duration in seconds, or omit `--duration` to run until Ctrl-C. + + ```bash title="Track a transponder for 1 hour with CSV logging" + sudo python3 tools/skywalker.py track 12520 27500 --lnb-lo 10600 --pol H --band high --log signal_log.csv --duration 3600 + ``` + +3. The tracker logs SNR, lock status, and AGC values at each sample. Lock/unlock transitions are flagged in the log for easy filtering. + +4. For frequency drift analysis, add `--drift-track` to monitor the demodulator's carrier frequency offset over time. + + ```bash title="Track with drift detection" + sudo python3 tools/skywalker.py track 12520 27500 --lnb-lo 10600 --pol H --band high --drift-track --log drift.csv + ``` + + + + +--- + +## How to Export Data + +All spectrum analysis modes support data export. Choose the format that fits your workflow. + +| Flag | Applicable Modes | Output Format | +|------|-----------------|---------------| +| `--csv FILE` | `spectrum`, `scan`, `lband` | Comma-separated: frequency, power, annotations | +| `--log FILE` | `track` | Timestamped CSV: time, frequency, SNR, lock, AGC | +| `--json-lines FILE` | `track` | One JSON object per sample, newline-delimited | +| `--json` | `scan` | Full scan results as a single JSON document | +| `--plot` | `spectrum`, `scan`, `lband` | Interactive matplotlib window | + +Combine `--csv` or `--json` with `--plot` to both save data and visualize it in a single run. + +--- + +## See Also + +- [Tuning Tool](/tools/tuning/) -- transponder tuning, LNB control, transport stream capture +- [RF Specifications](/hardware/rf-specifications/) -- IF range, symbol rate limits, LNB current limits +- [Signal Monitoring](/bcm4500/signal-monitoring/) -- SNR, lock status, and AGC register details diff --git a/site/src/content/docs/tools/survey.mdx b/site/src/content/docs/tools/survey.mdx index 086edd0..7ee8e60 100644 --- a/site/src/content/docs/tools/survey.mdx +++ b/site/src/content/docs/tools/survey.mdx @@ -1,160 +1,160 @@ ---- -title: Carrier Survey -description: Automated six-stage carrier detection, cataloging, and differential analysis across the full IF range or QO-100 narrowband transponder. ---- - -import { Tabs, TabItem, Steps, Aside, Badge, CardGrid, Card } from '@astrojs/starlight/components'; - -Automated carrier survey tool that sweeps the IF range, detects signals above the noise floor, identifies locked transponders with blind scan, and catalogs results with service name extraction from the transport stream. Supports full-band surveys, quick scans, QO-100 narrowband sweeps, and differential analysis between survey snapshots. - - - -## Quick Start - -```bash title="Full six-stage survey" -sudo python3 tools/survey.py full-scan -``` - -```bash title="Quick sweep (no blind scan)" -sudo python3 tools/survey.py quick-scan -``` - -```bash title="QO-100 narrowband survey" -sudo python3 tools/survey.py qo100 --lnb-lo 9750 -``` - -```bash title="Compare two surveys" -python3 tools/survey.py diff survey-old.json survey-new.json -``` - -## Subcommands - -| Command | Description | -|---------|-------------| -| `full-scan` | Complete 6-stage survey with carrier identification | -| `quick-scan` | Sweep + peak detection only (stages 1-2) | -| `diff FILE1 FILE2` | Compare two survey catalogs | -| `export FILE` | Export to CSV, JSON, or text | -| `view [FILE]` | Display latest or specified survey | -| `qo100 --lnb-lo LO` | QO-100 transponder survey with optimized parameters | - -## Survey Pipeline - -The full survey runs six stages sequentially. Each stage refines the previous stage's results. - - -1. **Coarse sweep** — full IF range (950-2150 MHz default) at 5 MHz steps. Measures signal power at each frequency using the firmware's spectrum sweep command. - -2. **Peak detection** — adaptive noise floor estimation using median + MAD (Median Absolute Deviation), followed by local maxima finding with -3 dB bandwidth estimation and peak merging. - -3. **Fine sweep** — +/-10 MHz around each detected peak at 1 MHz steps. Refines center frequencies from the coarse sweep. - -4. **Blind scan** — at each refined peak, tries symbol rates from 1-30 Msps in 1 Msps steps using the firmware's adaptive blind scan (AGC pre-check skips empty frequencies). Reports lock status with frequency and symbol rate. - -5. **TS sample** — for locked carriers, captures 3 seconds of transport stream data and parses PAT, PMT, and SDT tables to extract program numbers, stream types, service names, and provider names. - -6. **Catalog assembly** — aggregates all results into a `CarrierCatalog` with full metadata, saves to `~/.skywalker1/surveys/`. - - -### Full Scan Options - -| Flag | Default | Description | -|------|---------|-------------| -| `--start` | 950 | Start frequency (MHz) | -| `--stop` | 2150 | Stop frequency (MHz) | -| `--coarse-step` | 5.0 | Coarse sweep step (MHz) | -| `--fine-step` | 1.0 | Fine sweep step (MHz) | -| `--sr-min` | 1,000,000 | Min symbol rate (sps) | -| `--sr-max` | 30,000,000 | Max symbol rate (sps) | -| `--sr-step` | 1,000,000 | Symbol rate step (sps) | -| `--pol` | — | Polarization label (H/V/L/R) | -| `--band` | — | Band label (low/high) | -| `--output` | auto | Output filename | - -## Carrier Catalog - -Survey results are stored as JSON in `~/.skywalker1/surveys/` with auto-generated filenames: - -``` -~/.skywalker1/surveys/ - survey-2026-02-15-low-V.json - survey-2026-02-16-high-H.json - survey-2026-02-16-qo100-9750.json -``` - -Each carrier entry records: -- Frequency, symbol rate, modulation, FEC -- Signal power, SNR, lock status -- Detected services (names, types, PIDs) -- Estimated bandwidth and carrier classification -- First/last seen timestamps, scan count - -### Differential Analysis - -Compare two survey snapshots to detect changes over time: - -```bash -python3 tools/survey.py diff old-survey.json new-survey.json -``` - -Output categorizes carriers as: - -| Category | Description | -|----------|-------------| -| **NEW** | Carrier present in new survey but not old | -| **MISSING** | Carrier present in old survey but not new | -| **CHANGED** | Carrier exists in both but with differences (lock state, power >2 dB, modulation, services) | -| **STABLE** | Carrier unchanged between surveys | - - - -## QO-100 Mode - -The `qo100` subcommand uses parameters optimized for the Es'hail-2 wideband transponder (10491-10499 MHz): - -- **Step sizes**: 0.5 MHz coarse, 0.1 MHz fine (vs 5/1 MHz for broadcast) -- **Symbol rate range**: 256 ksps - 2 Msps (vs 1-30 Msps for broadcast) -- **Detection threshold**: 3.0 dB above noise floor (more sensitive) -- **IF range**: auto-calculated from `--lnb-lo` parameter - -```bash title="QO-100 with universal LNB low band" -sudo python3 tools/survey.py qo100 --lnb-lo 9750 -# Scans IF: 741-749 MHz -``` - -```bash title="QO-100 with modified LNB (9361 MHz LO)" -sudo python3 tools/survey.py qo100 --lnb-lo 9361 -# Scans IF: 1130-1138 MHz -``` - - - -## Signal Analysis - -The survey engine uses enhanced signal analysis beyond the basic peak detection in `skywalker_lib`: - -- **Adaptive noise floor** — median + MAD robust estimator, resistant to strong carriers biasing the baseline -- **Bandwidth estimation** — walks from peak until -3 dB crossing on each side, with inter-bin interpolation -- **Carrier classification** — heuristic mapping from estimated bandwidth to likely symbol rate range and modulation type -- **Peak merging** — overlapping detections within one bandwidth are consolidated into a single carrier - -## Export Formats - -```bash title="Export to CSV" -python3 tools/survey.py export survey.json --format csv --output carriers.csv -``` - -```bash title="Export to formatted text" -python3 tools/survey.py export survey.json --format text -``` - -## See Also - -- [SkyWalker TUI — Survey Screen](/tools/tui/#survey--qo-100) — graphical survey with spectrum plot and carrier table -- [QO-100 DATV Reception](/guides/qo100-datv/) — complete guide to receiving amateur television via Es'hail-2 -- [Spectrum Analysis](/tools/spectrum-analysis/) — manual spectrum sweep techniques -- [TS Analyzer](/tools/ts-analyzer/) — standalone transport stream analysis +--- +title: Carrier Survey +description: Automated six-stage carrier detection, cataloging, and differential analysis across the full IF range or QO-100 narrowband transponder. +--- + +import { Tabs, TabItem, Steps, Aside, Badge, CardGrid, Card } from '@astrojs/starlight/components'; + +Automated carrier survey tool that sweeps the IF range, detects signals above the noise floor, identifies locked transponders with blind scan, and catalogs results with service name extraction from the transport stream. Supports full-band surveys, quick scans, QO-100 narrowband sweeps, and differential analysis between survey snapshots. + + + +## Quick Start + +```bash title="Full six-stage survey" +sudo python3 tools/survey.py full-scan +``` + +```bash title="Quick sweep (no blind scan)" +sudo python3 tools/survey.py quick-scan +``` + +```bash title="QO-100 narrowband survey" +sudo python3 tools/survey.py qo100 --lnb-lo 9750 +``` + +```bash title="Compare two surveys" +python3 tools/survey.py diff survey-old.json survey-new.json +``` + +## Subcommands + +| Command | Description | +|---------|-------------| +| `full-scan` | Complete 6-stage survey with carrier identification | +| `quick-scan` | Sweep + peak detection only (stages 1-2) | +| `diff FILE1 FILE2` | Compare two survey catalogs | +| `export FILE` | Export to CSV, JSON, or text | +| `view [FILE]` | Display latest or specified survey | +| `qo100 --lnb-lo LO` | QO-100 transponder survey with optimized parameters | + +## Survey Pipeline + +The full survey runs six stages sequentially. Each stage refines the previous stage's results. + + +1. **Coarse sweep** — full IF range (950-2150 MHz default) at 5 MHz steps. Measures signal power at each frequency using the firmware's spectrum sweep command. + +2. **Peak detection** — adaptive noise floor estimation using median + MAD (Median Absolute Deviation), followed by local maxima finding with -3 dB bandwidth estimation and peak merging. + +3. **Fine sweep** — +/-10 MHz around each detected peak at 1 MHz steps. Refines center frequencies from the coarse sweep. + +4. **Blind scan** — at each refined peak, tries symbol rates from 1-30 Msps in 1 Msps steps using the firmware's adaptive blind scan (AGC pre-check skips empty frequencies). Reports lock status with frequency and symbol rate. + +5. **TS sample** — for locked carriers, captures 3 seconds of transport stream data and parses PAT, PMT, and SDT tables to extract program numbers, stream types, service names, and provider names. + +6. **Catalog assembly** — aggregates all results into a `CarrierCatalog` with full metadata, saves to `~/.skywalker1/surveys/`. + + +### Full Scan Options + +| Flag | Default | Description | +|------|---------|-------------| +| `--start` | 950 | Start frequency (MHz) | +| `--stop` | 2150 | Stop frequency (MHz) | +| `--coarse-step` | 5.0 | Coarse sweep step (MHz) | +| `--fine-step` | 1.0 | Fine sweep step (MHz) | +| `--sr-min` | 1,000,000 | Min symbol rate (sps) | +| `--sr-max` | 30,000,000 | Max symbol rate (sps) | +| `--sr-step` | 1,000,000 | Symbol rate step (sps) | +| `--pol` | — | Polarization label (H/V/L/R) | +| `--band` | — | Band label (low/high) | +| `--output` | auto | Output filename | + +## Carrier Catalog + +Survey results are stored as JSON in `~/.skywalker1/surveys/` with auto-generated filenames: + +``` +~/.skywalker1/surveys/ + survey-2026-02-15-low-V.json + survey-2026-02-16-high-H.json + survey-2026-02-16-qo100-9750.json +``` + +Each carrier entry records: +- Frequency, symbol rate, modulation, FEC +- Signal power, SNR, lock status +- Detected services (names, types, PIDs) +- Estimated bandwidth and carrier classification +- First/last seen timestamps, scan count + +### Differential Analysis + +Compare two survey snapshots to detect changes over time: + +```bash +python3 tools/survey.py diff old-survey.json new-survey.json +``` + +Output categorizes carriers as: + +| Category | Description | +|----------|-------------| +| **NEW** | Carrier present in new survey but not old | +| **MISSING** | Carrier present in old survey but not new | +| **CHANGED** | Carrier exists in both but with differences (lock state, power >2 dB, modulation, services) | +| **STABLE** | Carrier unchanged between surveys | + + + +## QO-100 Mode + +The `qo100` subcommand uses parameters optimized for the Es'hail-2 wideband transponder (10491-10499 MHz): + +- **Step sizes**: 0.5 MHz coarse, 0.1 MHz fine (vs 5/1 MHz for broadcast) +- **Symbol rate range**: 256 ksps - 2 Msps (vs 1-30 Msps for broadcast) +- **Detection threshold**: 3.0 dB above noise floor (more sensitive) +- **IF range**: auto-calculated from `--lnb-lo` parameter + +```bash title="QO-100 with universal LNB low band" +sudo python3 tools/survey.py qo100 --lnb-lo 9750 +# Scans IF: 741-749 MHz +``` + +```bash title="QO-100 with modified LNB (9361 MHz LO)" +sudo python3 tools/survey.py qo100 --lnb-lo 9361 +# Scans IF: 1130-1138 MHz +``` + + + +## Signal Analysis + +The survey engine uses enhanced signal analysis beyond the basic peak detection in `skywalker_lib`: + +- **Adaptive noise floor** — median + MAD robust estimator, resistant to strong carriers biasing the baseline +- **Bandwidth estimation** — walks from peak until -3 dB crossing on each side, with inter-bin interpolation +- **Carrier classification** — heuristic mapping from estimated bandwidth to likely symbol rate range and modulation type +- **Peak merging** — overlapping detections within one bandwidth are consolidated into a single carrier + +## Export Formats + +```bash title="Export to CSV" +python3 tools/survey.py export survey.json --format csv --output carriers.csv +``` + +```bash title="Export to formatted text" +python3 tools/survey.py export survey.json --format text +``` + +## See Also + +- [SkyWalker TUI — Survey Screen](/tools/tui/#survey--qo-100) — graphical survey with spectrum plot and carrier table +- [QO-100 DATV Reception](/guides/qo100-datv/) — complete guide to receiving amateur television via Es'hail-2 +- [Spectrum Analysis](/tools/spectrum-analysis/) — manual spectrum sweep techniques +- [TS Analyzer](/tools/ts-analyzer/) — standalone transport stream analysis diff --git a/site/src/content/docs/tools/ts-analyzer.mdx b/site/src/content/docs/tools/ts-analyzer.mdx index c7df1e3..be0da26 100644 --- a/site/src/content/docs/tools/ts-analyzer.mdx +++ b/site/src/content/docs/tools/ts-analyzer.mdx @@ -1,262 +1,262 @@ ---- -title: TS Analyzer -description: MPEG-2 Transport Stream analyzer for captured satellite data from the SkyWalker-1. ---- - -import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; - -`ts_analyze.py` parses and analyzes 188-byte MPEG-2 transport stream packets from `.ts` files captured by `tune.py`, piped from stdin, or any standard TS source. It covers PID distribution, PAT/PMT parsing, continuity counter verification, scrambling detection, bitrate estimation, and live stream monitoring. - -Reference: ISO/IEC 13818-1 (MPEG-2 Systems). - -## Subcommands - -| Subcommand | Purpose | -|------------|---------| -| `analyze` | Full stream analysis with PID table, CC errors, bitrate (default) | -| `pids` | Quick PID summary table | -| `pat` | Parse and display the Program Association Table | -| `pmt` | Parse and display Program Map Tables | -| `dump` | Hex dump of individual TS packets | -| `monitor` | Live stream monitoring with real-time statistics | - -Passing a filename without a subcommand defaults to `analyze`. - -## Global Options - -| Flag | Description | -|------|-------------| -| `-v, --verbose` | Show sync search details and debug info | - ---- - -## analyze -- Full Stream Analysis - -Comprehensive analysis of a TS file or stream: total packets, unique PIDs, TEI errors, scrambling, continuity counter errors, PCR-based bitrate, and a per-PID distribution table. - -```bash -python3 tools/ts_analyze.py capture.ts -``` - -```bash title="Analyze first 10000 packets only" -python3 tools/ts_analyze.py analyze capture.ts --max-packets 10000 -``` - -| Flag | Description | -|------|-------------| -| `INPUT` | TS file path or `-` for stdin | -| `--max-packets N` | Limit analysis to N packets (0 = all) | - -**Example output:** - -``` -MPEG-2 Transport Stream Analysis -============================================================ -File: capture.ts (52,428,800 bytes) - -Total packets: 278,876 -Total bytes: 52,428,688 -Unique PIDs: 12 -TEI errors: 0 -Scrambled: 0 -Duration: 5.23s (from PCR) -Bitrate: 80.21 Mbps (PCR-based) - -============================================================ -PID Distribution -============================================================ - PID Count % CC Err Name - --- ----- -- ------ ---- - 0x0000 1,394 0.50% - PAT - 0x0001 278 0.10% - CAT - 0x0010 556 0.20% - NIT/ST - 0x0011 556 0.20% - SDT/BAT/ST - 0x0100 139,438 50.00% - - 0x0101 83,663 29.99% - - 0x0102 5,576 2.00% - - 0x1FFF 47,415 17.01% - Null -``` - -### What It Detects - -| Metric | Description | -|--------|-------------| -| **PID count** | Packet count per Program ID | -| **TEI** | Transport Error Indicator flag in the TS header | -| **Scrambling** | Transport scrambling control bits (even/odd key) | -| **CC errors** | Continuity counter discontinuities per PID | -| **Bitrate** | Calculated from PCR timestamps when available | - ---- - -## pids -- Quick PID Table - -Faster variant of `analyze` that only counts packets per PID without CC checking or PCR extraction. - -```bash -python3 tools/ts_analyze.py pids capture.ts -``` - ---- - -## pat -- Program Association Table - -Parses PID 0x0000 to extract the PAT, showing the transport stream ID and which PMT PID corresponds to each program number. - -```bash -python3 tools/ts_analyze.py pat capture.ts -``` - -``` -Program Association Table (PAT) -================================================== - Transport Stream ID: 0x0001 (1) - Version: 3 - Programs: 2 - - Program PMT PID Note - ------- ------- ---- - 0 0x0010 NIT - 1 0x0100 -``` - ---- - -## pmt -- Program Map Tables - -Parses the PAT first to discover PMT PIDs, then parses each PMT to show the elementary streams (video, audio, data) in each program. - -```bash -python3 tools/ts_analyze.py pmt capture.ts -``` - -``` -Program Map Tables -============================================================ -Transport Stream ID: 0x0001 - - Program 1 (PMT PID 0x0100, version 2) - PCR PID: 0x0101 - Streams: - Type PID Description - ---- --- ----------- - 0x02 0x0101 MPEG-2 Video (13818-2) - 0x04 0x0102 MPEG-2 Audio (13818-3) - 0x06 0x0103 PES Private Data -``` - -### Known Stream Types - -The tool recognizes these standard stream type identifiers: - -| Code | Description | -|------|-------------| -| 0x01 | MPEG-1 Video | -| 0x02 | MPEG-2 Video | -| 0x03 | MPEG-1 Audio | -| 0x04 | MPEG-2 Audio | -| 0x06 | PES Private Data | -| 0x0F | MPEG-2 AAC Audio | -| 0x1B | H.264/AVC Video | -| 0x24 | H.265/HEVC Video | -| 0x81 | AC-3 Audio (ATSC) | - ---- - -## dump -- Hex Dump Packets - -Print detailed per-packet information including header fields, adaptation field flags, PCR values, and raw hex dump. - -```bash title="Dump first 5 packets" -python3 tools/ts_analyze.py dump capture.ts --count 5 -``` - -```bash title="Dump packets for a specific PID" -python3 tools/ts_analyze.py dump capture.ts --pid 0x0100 --count 3 -``` - -| Flag | Description | -|------|-------------| -| `--pid PID` | Filter by PID (hex `0x100` or decimal `256`) | -| `--count N` | Maximum packets to dump (default: `10`) | - -**Example output:** - -``` -Packet #1 - PID: 0x0000 (PAT) - TEI: 0 PUSI: 1 Priority: 0 - Scrambling: none Adaptation: payload only CC: 5 - Hex: - 0000: 47 40 00 15 00 00 B0 0D 00 01 C1 00 00 00 00 E0 G@.............. - 0010: 10 00 01 E1 00 78 A0 B4 FF FF FF FF FF FF FF FF .....x.......... - ... -``` - ---- - -## monitor -- Live Stream Monitoring - -Real-time monitoring of a TS stream, reporting new PIDs as they appear, continuity errors, TEI errors, and bitrate. - -```bash title="Monitor from a live stream pipe" -sudo python3 tools/tune.py stream --stdout | python3 tools/ts_analyze.py monitor - -``` - -```bash title="Monitor a growing file" -python3 tools/ts_analyze.py monitor capture.ts -``` - -The monitor prints events as they occur (new PIDs, errors) and updates a status line on stderr every second with running statistics. - -``` -MPEG-2 TS Live Monitor -============================================================ -Ctrl-C to stop - - [ 0.0s] New PID: 0x0000 (PAT) - [ 0.0s] New PID: 0x1FFF (Null) - [ 0.1s] New PID: 0x0100 - [ 0.1s] New PID: 0x0101 - [ 0.2s] New PID: 0x0102 - 278,876 pkts 52,428,688 bytes 80.21 Mbps PIDs: 12 CCerr:0 TEI:0 (5s) - -Monitor Summary -======================================== - Duration: 5.2s - Packets: 278,876 - Bytes: 52,428,688 - Unique PIDs: 12 - CC errors: 0 - TEI errors: 0 - Avg bitrate: 80.21 Mbps -``` - ---- - -## Capturing a Transport Stream - - - -## Sync Detection - -The analyzer requires at least 3 consecutive sync bytes (`0x47`) at 188-byte intervals to confirm alignment. If the file starts with non-TS data (e.g., leading garbage from a partial USB transfer), the tool will skip ahead until sync is found. - -When using `-v` (verbose), the sync search offset is reported. A non-zero sync offset may indicate that the capture started mid-packet or contains a non-TS preamble. - -## See Also - -- [Tuning Tool](/tools/tuning/) -- capture TS data from a transponder -- [GPIF Streaming](/bcm4500/gpif-streaming/) -- how TS data flows from BCM4500 through USB -- [Vendor Commands](/usb/vendor-commands/) -- ARM_TRANSFER (0x85) starts/stops the stream +--- +title: TS Analyzer +description: MPEG-2 Transport Stream analyzer for captured satellite data from the SkyWalker-1. +--- + +import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; + +`ts_analyze.py` parses and analyzes 188-byte MPEG-2 transport stream packets from `.ts` files captured by `tune.py`, piped from stdin, or any standard TS source. It covers PID distribution, PAT/PMT parsing, continuity counter verification, scrambling detection, bitrate estimation, and live stream monitoring. + +Reference: ISO/IEC 13818-1 (MPEG-2 Systems). + +## Subcommands + +| Subcommand | Purpose | +|------------|---------| +| `analyze` | Full stream analysis with PID table, CC errors, bitrate (default) | +| `pids` | Quick PID summary table | +| `pat` | Parse and display the Program Association Table | +| `pmt` | Parse and display Program Map Tables | +| `dump` | Hex dump of individual TS packets | +| `monitor` | Live stream monitoring with real-time statistics | + +Passing a filename without a subcommand defaults to `analyze`. + +## Global Options + +| Flag | Description | +|------|-------------| +| `-v, --verbose` | Show sync search details and debug info | + +--- + +## analyze -- Full Stream Analysis + +Comprehensive analysis of a TS file or stream: total packets, unique PIDs, TEI errors, scrambling, continuity counter errors, PCR-based bitrate, and a per-PID distribution table. + +```bash +python3 tools/ts_analyze.py capture.ts +``` + +```bash title="Analyze first 10000 packets only" +python3 tools/ts_analyze.py analyze capture.ts --max-packets 10000 +``` + +| Flag | Description | +|------|-------------| +| `INPUT` | TS file path or `-` for stdin | +| `--max-packets N` | Limit analysis to N packets (0 = all) | + +**Example output:** + +``` +MPEG-2 Transport Stream Analysis +============================================================ +File: capture.ts (52,428,800 bytes) + +Total packets: 278,876 +Total bytes: 52,428,688 +Unique PIDs: 12 +TEI errors: 0 +Scrambled: 0 +Duration: 5.23s (from PCR) +Bitrate: 80.21 Mbps (PCR-based) + +============================================================ +PID Distribution +============================================================ + PID Count % CC Err Name + --- ----- -- ------ ---- + 0x0000 1,394 0.50% - PAT + 0x0001 278 0.10% - CAT + 0x0010 556 0.20% - NIT/ST + 0x0011 556 0.20% - SDT/BAT/ST + 0x0100 139,438 50.00% - + 0x0101 83,663 29.99% - + 0x0102 5,576 2.00% - + 0x1FFF 47,415 17.01% - Null +``` + +### What It Detects + +| Metric | Description | +|--------|-------------| +| **PID count** | Packet count per Program ID | +| **TEI** | Transport Error Indicator flag in the TS header | +| **Scrambling** | Transport scrambling control bits (even/odd key) | +| **CC errors** | Continuity counter discontinuities per PID | +| **Bitrate** | Calculated from PCR timestamps when available | + +--- + +## pids -- Quick PID Table + +Faster variant of `analyze` that only counts packets per PID without CC checking or PCR extraction. + +```bash +python3 tools/ts_analyze.py pids capture.ts +``` + +--- + +## pat -- Program Association Table + +Parses PID 0x0000 to extract the PAT, showing the transport stream ID and which PMT PID corresponds to each program number. + +```bash +python3 tools/ts_analyze.py pat capture.ts +``` + +``` +Program Association Table (PAT) +================================================== + Transport Stream ID: 0x0001 (1) + Version: 3 + Programs: 2 + + Program PMT PID Note + ------- ------- ---- + 0 0x0010 NIT + 1 0x0100 +``` + +--- + +## pmt -- Program Map Tables + +Parses the PAT first to discover PMT PIDs, then parses each PMT to show the elementary streams (video, audio, data) in each program. + +```bash +python3 tools/ts_analyze.py pmt capture.ts +``` + +``` +Program Map Tables +============================================================ +Transport Stream ID: 0x0001 + + Program 1 (PMT PID 0x0100, version 2) + PCR PID: 0x0101 + Streams: + Type PID Description + ---- --- ----------- + 0x02 0x0101 MPEG-2 Video (13818-2) + 0x04 0x0102 MPEG-2 Audio (13818-3) + 0x06 0x0103 PES Private Data +``` + +### Known Stream Types + +The tool recognizes these standard stream type identifiers: + +| Code | Description | +|------|-------------| +| 0x01 | MPEG-1 Video | +| 0x02 | MPEG-2 Video | +| 0x03 | MPEG-1 Audio | +| 0x04 | MPEG-2 Audio | +| 0x06 | PES Private Data | +| 0x0F | MPEG-2 AAC Audio | +| 0x1B | H.264/AVC Video | +| 0x24 | H.265/HEVC Video | +| 0x81 | AC-3 Audio (ATSC) | + +--- + +## dump -- Hex Dump Packets + +Print detailed per-packet information including header fields, adaptation field flags, PCR values, and raw hex dump. + +```bash title="Dump first 5 packets" +python3 tools/ts_analyze.py dump capture.ts --count 5 +``` + +```bash title="Dump packets for a specific PID" +python3 tools/ts_analyze.py dump capture.ts --pid 0x0100 --count 3 +``` + +| Flag | Description | +|------|-------------| +| `--pid PID` | Filter by PID (hex `0x100` or decimal `256`) | +| `--count N` | Maximum packets to dump (default: `10`) | + +**Example output:** + +``` +Packet #1 + PID: 0x0000 (PAT) + TEI: 0 PUSI: 1 Priority: 0 + Scrambling: none Adaptation: payload only CC: 5 + Hex: + 0000: 47 40 00 15 00 00 B0 0D 00 01 C1 00 00 00 00 E0 G@.............. + 0010: 10 00 01 E1 00 78 A0 B4 FF FF FF FF FF FF FF FF .....x.......... + ... +``` + +--- + +## monitor -- Live Stream Monitoring + +Real-time monitoring of a TS stream, reporting new PIDs as they appear, continuity errors, TEI errors, and bitrate. + +```bash title="Monitor from a live stream pipe" +sudo python3 tools/tune.py stream --stdout | python3 tools/ts_analyze.py monitor - +``` + +```bash title="Monitor a growing file" +python3 tools/ts_analyze.py monitor capture.ts +``` + +The monitor prints events as they occur (new PIDs, errors) and updates a status line on stderr every second with running statistics. + +``` +MPEG-2 TS Live Monitor +============================================================ +Ctrl-C to stop + + [ 0.0s] New PID: 0x0000 (PAT) + [ 0.0s] New PID: 0x1FFF (Null) + [ 0.1s] New PID: 0x0100 + [ 0.1s] New PID: 0x0101 + [ 0.2s] New PID: 0x0102 + 278,876 pkts 52,428,688 bytes 80.21 Mbps PIDs: 12 CCerr:0 TEI:0 (5s) + +Monitor Summary +======================================== + Duration: 5.2s + Packets: 278,876 + Bytes: 52,428,688 + Unique PIDs: 12 + CC errors: 0 + TEI errors: 0 + Avg bitrate: 80.21 Mbps +``` + +--- + +## Capturing a Transport Stream + + + +## Sync Detection + +The analyzer requires at least 3 consecutive sync bytes (`0x47`) at 188-byte intervals to confirm alignment. If the file starts with non-TS data (e.g., leading garbage from a partial USB transfer), the tool will skip ahead until sync is found. + +When using `-v` (verbose), the sync search offset is reported. A non-zero sync offset may indicate that the capture started mid-packet or contains a non-TS preamble. + +## See Also + +- [Tuning Tool](/tools/tuning/) -- capture TS data from a transponder +- [GPIF Streaming](/bcm4500/gpif-streaming/) -- how TS data flows from BCM4500 through USB +- [Vendor Commands](/usb/vendor-commands/) -- ARM_TRANSFER (0x85) starts/stops the stream diff --git a/site/src/content/docs/tools/tui.mdx b/site/src/content/docs/tools/tui.mdx index 4451ee0..9699f4f 100644 --- a/site/src/content/docs/tools/tui.mdx +++ b/site/src/content/docs/tools/tui.mdx @@ -1,314 +1,314 @@ ---- -title: SkyWalker TUI -description: Interactive terminal dashboard for spectrum analysis, transponder scanning, signal monitoring, L-band analysis, carrier tracking, device management, transport stream analysis, and hardware configuration. ---- - -import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; - -The SkyWalker TUI is a full-screen terminal dashboard built on [Textual](https://textual.textualize.io/) that wraps all ten operating modes into a single interactive interface. Five RF signal modes (Spectrum, Scan, Monitor, L-Band, Track), Device management, Transport Stream analysis, hardware Config, DiSEqC Motor control, and an automated Carrier Survey with QO-100 DATV support — all with sidebar navigation, F-key shortcuts, real-time widget updates, and a dark/light theme toggle. - - - -![SkyWalker TUI — Spectrum mode](../../../assets/tui/spectrum.svg) - -## Installation - -The TUI is distributed as the `skywalker-tui` package from the `tui/` directory in the repository. - -```bash title="Install with uv" -cd tui && uv sync -``` - -```bash title="Run with hardware" -sudo uv run skywalker-tui -``` - -```bash title="Run in demo mode (no hardware)" -uv run skywalker-tui --demo -``` - -### CLI Flags - -| Flag | Description | -|------|-------------| -| `--demo` | Synthetic signal data — no SkyWalker-1 USB device needed | -| `--no-splash` | Skip the startup splash screen | -| `--verbose, -v` | Verbose USB logging (hardware mode only) | -| `MODE` | Initial mode: `spectrum` (default), `scan`, `monitor`, `lband`, `track`, `device`, `stream`, `config`, `motor`, `survey` | - - - ---- - -## Keyboard Shortcuts - -| Key | Action | -|-----|--------| -| F1 | Spectrum analyzer | -| F2 | Transponder scanner | -| F3 | Signal monitor | -| F4 | L-Band analyzer | -| F5 | Carrier tracker | -| F6 | Device management | -| F7 | Transport stream | -| F8 | Hardware config | -| F9 | Motor control | -| F10 | Carrier survey | -| d | Toggle dark/light theme | -| q | Quit | -| Ctrl+W | Easter egg | -| Esc | Dismiss overlay / skip splash | - -The sidebar buttons mirror the F-key shortcuts — click or press to switch modes. The active mode is highlighted in the sidebar. - ---- - -## Screens - -Each mode is a self-contained screen with its own widgets, workers, and layout. Switching modes pauses the previous screen's polling and starts the new one. - -### Spectrum - -Sweep spectrum analyzer across the 950–2150 MHz IF range. Renders a bar chart of signal power at each frequency step with a scrolling waterfall display below. - -![Spectrum mode](../../../assets/tui/spectrum.svg) - -- Bar chart with color-coded signal levels (blue → green → yellow → red) -- Waterfall display showing power over time -- Peak detection markers above the noise floor -- Configurable sweep range, step size, and dwell time - -### Scan - -Three-phase automated transponder search: coarse sweep, fine sweep around peaks, and blind scan at each refined candidate across a range of symbol rates. - -![Scan mode](../../../assets/tui/scan.svg) - - -1. **Coarse sweep** at 10 MHz steps to identify candidate carriers -2. **Fine sweep** at 2 MHz steps around each detected peak -3. **Blind scan** at each refined peak, trying symbol rates from min to max - - -- Progress indicator for each phase -- Results table with frequency, symbol rate, and lock status -- Supports Ku-band, C-band, and custom LO configurations - -### Monitor - -Real-time signal strength display for hands-free dish alignment. Tunes to a single frequency and polls continuously with visual feedback. - -![Monitor mode](../../../assets/tui/monitor.svg) - -- Signal gauge with lock/unlock status indicator -- Sparkline history of recent signal strength samples -- SNR and AGC readouts -- Designed for dish alignment — visual feedback without needing to read numbers - -### L-Band - -Direct RF input analysis in the 950–2150 MHz range with LNB power disabled. Annotates known L-band frequency allocations. - -![L-Band mode](../../../assets/tui/lband.svg) - -- Same sweep engine as Spectrum mode, but with `lnb_lo=0` and LNB voltage off -- Frequency allocation overlays for Amateur 23cm, GNSS, Inmarsat, Iridium, MetSat -- Detects carrier presence regardless of modulation compatibility - - - -### Track - -Carrier/beacon tracker with timestamped logging. Tracks a single frequency and detects lock/unlock transitions over time. - -![Track mode](../../../assets/tui/track.svg) - -- Radar scope display showing signal history -- Sparkline for signal power trend -- Event log with timestamped lock/unlock transitions -- Frequency drift detection - -### Device - -Firmware management, EEPROM operations, and hardware diagnostics. An identity panel at the top always shows firmware version, serial number, USB speed, and config register flags. - -![Device management](../../../assets/tui/device.svg) - - - - - FX2 RAM read at arbitrary addresses rendered as scrollable hex dump - - CPU Halt / Start controls for the Cypress FX2 microcontroller - - Non-destructive — RAM contents revert on power cycle - - - - Read full 16 KB EEPROM with progress bar - - One-click backup to timestamped `.bin` file - - Flash with full safety state machine: - - - 1. **C2 validation** — magic byte, VID/PID match, END marker, record parsing - 2. **Auto-backup** — saves current EEPROM to timestamped file before any write - 3. **3-second countdown** — ABORT button auto-focused (accidental keypress = cancel) - 4. **Page write** — 16-byte pages with progress bar and circuit breaker (3 consecutive errors = abort) - 5. **Byte-verify** — full readback with diff highlighting in hex view - - - - - Boot test with mode selector (0x80–0x85) showing stage progression - - I2C bus scan identifying BCM4500, EEPROM, tuner, and LNB controller - - BCM4500 register dump rendered as hex view - - - - - -### Stream - -Live MPEG-2 Transport Stream capture and analysis. Reads raw 188-byte TS packets from the SkyWalker-1 bulk endpoint, parses them in real time, and displays PID distribution statistics alongside a hierarchical PSI program structure tree. - -![Stream analysis](../../../assets/tui/stream.svg) - -- PID distribution table with count, percentage, CC errors, and known PID names -- PAT/PMT tree display showing transport stream ID, program numbers, PMT PIDs, and elementary stream types (video, audio, data) -- Aggregate stats: total packets, bytes, unique PIDs, CC errors, duration -- Capture-to-file mode for saving raw `.ts` files to disk -- Auto-starts monitoring in demo mode - - - -### Config - -LNB power control, DiSEqC switching, and modulation/FEC configuration — all the hardware settings in one place. - -![Hardware configuration](../../../assets/tui/config.svg) - - - - - Power On / Off toggle - - Voltage selection: 13V (vertical) / 18V (horizontal), with optional +1V boost - - 22 kHz tone toggle for high-band LNB switching - - Current draw warning (450 mA continuous max) - - - - Port selector (1–4) sending committed commands - - Tone burst A/B for legacy switches - - Raw hex message input (3–6 byte DiSEqC 1.0/1.2 commands) with validation - - - - Modulation type dropdown: QPSK, Turbo QPSK, Turbo 8PSK, DCII Combo, DCII Split I/Q, DCII Offset QPSK, DSS - - FEC rate dropdown filtered per modulation group - - Symbol rate (256 Ksps – 30 Msps) and frequency inputs - - Tune button applies settings to the BCM4500 demodulator - - - -### Motor Control - -DiSEqC 1.2 positioner motor control with three-column layout: jog/halt controls, stored positions, and USALS GotoX calculator. A live signal bar at the bottom provides real-time SNR, power, lock status, and motor position feedback for hands-free dish alignment. - -![Motor control](../../../assets/tui/motor.svg) - -- Continuous jog (east/west) with keyboard arrows or button controls -- 3x3 stored position grid for quick satellite recall -- USALS GotoX calculator with observer longitude input and satellite presets (QO-100, Galaxy 19, AMC-1) -- 30-second auto-halt safety timer on continuous jog -- Motor automatically halts when switching away from the screen -- Live signal gauge updates at 2 Hz for alignment feedback - -| Key | Action | -|-----|--------| -| Left | Jog west | -| Right | Jog east | -| Space | Halt motor | - - - -### Survey + QO-100 - -Automated carrier survey with two tabs: Full Band sweep across the entire IF range, and a QO-100 DATV tab with parameters optimized for the Es'hail-2 wideband transponder. - -![Carrier survey](../../../assets/tui/survey.svg) - - - - Six-stage survey pipeline: coarse sweep, peak detection, fine sweep, blind scan, TS sample, and catalog assembly. Results display as a spectrum plot and frequency table showing detected carriers with their symbol rate, modulation, lock status, and service names. - - - Configurable start/stop frequency and step sizes - - Full scan and quick scan modes - - Carrier table with real-time status - - Results auto-saved to `~/.skywalker1/surveys/` - - - Narrowband sweep tuned for Es'hail-2's wideband transponder (10491-10499 MHz). Uses tighter frequency steps (0.5/0.1 MHz vs 5/1 MHz), lower symbol rate range (256 ksps - 2 Msps), and a more sensitive detection threshold (3.0 dB). - - - LNB local oscillator input for IF calculation - - Known QO-100 station frequencies displayed - - Info panel explaining BCM4500 symbol rate limitations - - Carriers classified as "detected" vs "locked" - - - ---- - -## Easter Eggs - -### Dark Side Toggle - -Press d to toggle between dark and light themes. Switching to dark mode triggers a notification: - -> *"Welcome to the Dark Side."* -> — The Force is strong with this one - -![Dark Side notification](../../../assets/tui/dark-mode.svg) - -Switching back to light mode: - -> *"The Force awakens."* -> — A New Hope - -### Star Wars - -Press Ctrl+W to open the Star Wars overlay. It attempts to connect to `towel.blinkenlights.nl:23` for the classic ASCII Star Wars telnet animation. If the connection fails (port 23 is blocked on many networks), a built-in opening crawl plays instead — complete with a SkyWalker-1 themed storyline and Star Destroyer ASCII art. - -![Star Wars easter egg](../../../assets/tui/starwars.svg) - -Press Esc to close the overlay and return to the main dashboard. - -### Kitty Cat - -When running inside the [Kitty terminal](https://sw.kovidgoyal.net/kitty/), the splash screen adds a subtle 🐱 to the title bar. A small nod to the terminal that made it all possible. - ---- - -## Splash Screen - -On startup, the TUI displays a randomly selected piece of ASCII art from the [16colo.rs / Mistigris](https://16colo.rs/) archives — pre-baked as ANSI half-block art for instant rendering. The splash auto-dismisses after 5 seconds or on any keypress. - -![Splash screen](../../../assets/tui/splash.svg) - -Use `--no-splash` to skip it. - ---- - -## See Also - -- [SkyWalker RF Tool](/tools/skywalker/) — CLI version of the RF modes (text-only output, scriptable) -- [Spectrum Analysis](/tools/spectrum-analysis/) — practical how-to guides for each operating mode -- [Signal Monitoring](/bcm4500/signal-monitoring/) — SIGNAL_MONITOR register protocol and data format -- [Tuning Tool](/tools/tuning/) — primary tuning, LNB control, and transport stream capture -- [EEPROM Utilities](/tools/eeprom-utilities/) — standalone CLI for firmware updates (same safety protocol as F6 Device screen) -- [TS Stream Analyzer](/tools/ts-analyzer/) — standalone TS packet analyzer (same engine as F7 Stream screen) -- [Motor Control](/tools/motor/) — standalone CLI for DiSEqC 1.2 motor control (same engine as F9 Motor screen) -- [Carrier Survey](/tools/survey/) — standalone CLI for automated carrier survey (same engine as F10 Survey screen) -- [QO-100 DATV Reception](/guides/qo100-datv/) — complete guide to Es'hail-2 amateur television reception -- [RF Coverage](/hardware/rf-coverage/) — frequency coverage and antenna considerations +--- +title: SkyWalker TUI +description: Interactive terminal dashboard for spectrum analysis, transponder scanning, signal monitoring, L-band analysis, carrier tracking, device management, transport stream analysis, and hardware configuration. +--- + +import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; + +The SkyWalker TUI is a full-screen terminal dashboard built on [Textual](https://textual.textualize.io/) that wraps all ten operating modes into a single interactive interface. Five RF signal modes (Spectrum, Scan, Monitor, L-Band, Track), Device management, Transport Stream analysis, hardware Config, DiSEqC Motor control, and an automated Carrier Survey with QO-100 DATV support — all with sidebar navigation, F-key shortcuts, real-time widget updates, and a dark/light theme toggle. + + + +![SkyWalker TUI — Spectrum mode](../../../assets/tui/spectrum.svg) + +## Installation + +The TUI is distributed as the `skywalker-tui` package from the `tui/` directory in the repository. + +```bash title="Install with uv" +cd tui && uv sync +``` + +```bash title="Run with hardware" +sudo uv run skywalker-tui +``` + +```bash title="Run in demo mode (no hardware)" +uv run skywalker-tui --demo +``` + +### CLI Flags + +| Flag | Description | +|------|-------------| +| `--demo` | Synthetic signal data — no SkyWalker-1 USB device needed | +| `--no-splash` | Skip the startup splash screen | +| `--verbose, -v` | Verbose USB logging (hardware mode only) | +| `MODE` | Initial mode: `spectrum` (default), `scan`, `monitor`, `lband`, `track`, `device`, `stream`, `config`, `motor`, `survey` | + + + +--- + +## Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| F1 | Spectrum analyzer | +| F2 | Transponder scanner | +| F3 | Signal monitor | +| F4 | L-Band analyzer | +| F5 | Carrier tracker | +| F6 | Device management | +| F7 | Transport stream | +| F8 | Hardware config | +| F9 | Motor control | +| F10 | Carrier survey | +| d | Toggle dark/light theme | +| q | Quit | +| Ctrl+W | Easter egg | +| Esc | Dismiss overlay / skip splash | + +The sidebar buttons mirror the F-key shortcuts — click or press to switch modes. The active mode is highlighted in the sidebar. + +--- + +## Screens + +Each mode is a self-contained screen with its own widgets, workers, and layout. Switching modes pauses the previous screen's polling and starts the new one. + +### Spectrum + +Sweep spectrum analyzer across the 950–2150 MHz IF range. Renders a bar chart of signal power at each frequency step with a scrolling waterfall display below. + +![Spectrum mode](../../../assets/tui/spectrum.svg) + +- Bar chart with color-coded signal levels (blue → green → yellow → red) +- Waterfall display showing power over time +- Peak detection markers above the noise floor +- Configurable sweep range, step size, and dwell time + +### Scan + +Three-phase automated transponder search: coarse sweep, fine sweep around peaks, and blind scan at each refined candidate across a range of symbol rates. + +![Scan mode](../../../assets/tui/scan.svg) + + +1. **Coarse sweep** at 10 MHz steps to identify candidate carriers +2. **Fine sweep** at 2 MHz steps around each detected peak +3. **Blind scan** at each refined peak, trying symbol rates from min to max + + +- Progress indicator for each phase +- Results table with frequency, symbol rate, and lock status +- Supports Ku-band, C-band, and custom LO configurations + +### Monitor + +Real-time signal strength display for hands-free dish alignment. Tunes to a single frequency and polls continuously with visual feedback. + +![Monitor mode](../../../assets/tui/monitor.svg) + +- Signal gauge with lock/unlock status indicator +- Sparkline history of recent signal strength samples +- SNR and AGC readouts +- Designed for dish alignment — visual feedback without needing to read numbers + +### L-Band + +Direct RF input analysis in the 950–2150 MHz range with LNB power disabled. Annotates known L-band frequency allocations. + +![L-Band mode](../../../assets/tui/lband.svg) + +- Same sweep engine as Spectrum mode, but with `lnb_lo=0` and LNB voltage off +- Frequency allocation overlays for Amateur 23cm, GNSS, Inmarsat, Iridium, MetSat +- Detects carrier presence regardless of modulation compatibility + + + +### Track + +Carrier/beacon tracker with timestamped logging. Tracks a single frequency and detects lock/unlock transitions over time. + +![Track mode](../../../assets/tui/track.svg) + +- Radar scope display showing signal history +- Sparkline for signal power trend +- Event log with timestamped lock/unlock transitions +- Frequency drift detection + +### Device + +Firmware management, EEPROM operations, and hardware diagnostics. An identity panel at the top always shows firmware version, serial number, USB speed, and config register flags. + +![Device management](../../../assets/tui/device.svg) + + + + - FX2 RAM read at arbitrary addresses rendered as scrollable hex dump + - CPU Halt / Start controls for the Cypress FX2 microcontroller + - Non-destructive — RAM contents revert on power cycle + + + - Read full 16 KB EEPROM with progress bar + - One-click backup to timestamped `.bin` file + - Flash with full safety state machine: + + + 1. **C2 validation** — magic byte, VID/PID match, END marker, record parsing + 2. **Auto-backup** — saves current EEPROM to timestamped file before any write + 3. **3-second countdown** — ABORT button auto-focused (accidental keypress = cancel) + 4. **Page write** — 16-byte pages with progress bar and circuit breaker (3 consecutive errors = abort) + 5. **Byte-verify** — full readback with diff highlighting in hex view + + + + - Boot test with mode selector (0x80–0x85) showing stage progression + - I2C bus scan identifying BCM4500, EEPROM, tuner, and LNB controller + - BCM4500 register dump rendered as hex view + + + + + +### Stream + +Live MPEG-2 Transport Stream capture and analysis. Reads raw 188-byte TS packets from the SkyWalker-1 bulk endpoint, parses them in real time, and displays PID distribution statistics alongside a hierarchical PSI program structure tree. + +![Stream analysis](../../../assets/tui/stream.svg) + +- PID distribution table with count, percentage, CC errors, and known PID names +- PAT/PMT tree display showing transport stream ID, program numbers, PMT PIDs, and elementary stream types (video, audio, data) +- Aggregate stats: total packets, bytes, unique PIDs, CC errors, duration +- Capture-to-file mode for saving raw `.ts` files to disk +- Auto-starts monitoring in demo mode + + + +### Config + +LNB power control, DiSEqC switching, and modulation/FEC configuration — all the hardware settings in one place. + +![Hardware configuration](../../../assets/tui/config.svg) + + + + - Power On / Off toggle + - Voltage selection: 13V (vertical) / 18V (horizontal), with optional +1V boost + - 22 kHz tone toggle for high-band LNB switching + - Current draw warning (450 mA continuous max) + + + - Port selector (1–4) sending committed commands + - Tone burst A/B for legacy switches + - Raw hex message input (3–6 byte DiSEqC 1.0/1.2 commands) with validation + + + - Modulation type dropdown: QPSK, Turbo QPSK, Turbo 8PSK, DCII Combo, DCII Split I/Q, DCII Offset QPSK, DSS + - FEC rate dropdown filtered per modulation group + - Symbol rate (256 Ksps – 30 Msps) and frequency inputs + - Tune button applies settings to the BCM4500 demodulator + + + +### Motor Control + +DiSEqC 1.2 positioner motor control with three-column layout: jog/halt controls, stored positions, and USALS GotoX calculator. A live signal bar at the bottom provides real-time SNR, power, lock status, and motor position feedback for hands-free dish alignment. + +![Motor control](../../../assets/tui/motor.svg) + +- Continuous jog (east/west) with keyboard arrows or button controls +- 3x3 stored position grid for quick satellite recall +- USALS GotoX calculator with observer longitude input and satellite presets (QO-100, Galaxy 19, AMC-1) +- 30-second auto-halt safety timer on continuous jog +- Motor automatically halts when switching away from the screen +- Live signal gauge updates at 2 Hz for alignment feedback + +| Key | Action | +|-----|--------| +| Left | Jog west | +| Right | Jog east | +| Space | Halt motor | + + + +### Survey + QO-100 + +Automated carrier survey with two tabs: Full Band sweep across the entire IF range, and a QO-100 DATV tab with parameters optimized for the Es'hail-2 wideband transponder. + +![Carrier survey](../../../assets/tui/survey.svg) + + + + Six-stage survey pipeline: coarse sweep, peak detection, fine sweep, blind scan, TS sample, and catalog assembly. Results display as a spectrum plot and frequency table showing detected carriers with their symbol rate, modulation, lock status, and service names. + + - Configurable start/stop frequency and step sizes + - Full scan and quick scan modes + - Carrier table with real-time status + - Results auto-saved to `~/.skywalker1/surveys/` + + + Narrowband sweep tuned for Es'hail-2's wideband transponder (10491-10499 MHz). Uses tighter frequency steps (0.5/0.1 MHz vs 5/1 MHz), lower symbol rate range (256 ksps - 2 Msps), and a more sensitive detection threshold (3.0 dB). + + - LNB local oscillator input for IF calculation + - Known QO-100 station frequencies displayed + - Info panel explaining BCM4500 symbol rate limitations + - Carriers classified as "detected" vs "locked" + + + +--- + +## Easter Eggs + +### Dark Side Toggle + +Press d to toggle between dark and light themes. Switching to dark mode triggers a notification: + +> *"Welcome to the Dark Side."* +> — The Force is strong with this one + +![Dark Side notification](../../../assets/tui/dark-mode.svg) + +Switching back to light mode: + +> *"The Force awakens."* +> — A New Hope + +### Star Wars + +Press Ctrl+W to open the Star Wars overlay. It attempts to connect to `towel.blinkenlights.nl:23` for the classic ASCII Star Wars telnet animation. If the connection fails (port 23 is blocked on many networks), a built-in opening crawl plays instead — complete with a SkyWalker-1 themed storyline and Star Destroyer ASCII art. + +![Star Wars easter egg](../../../assets/tui/starwars.svg) + +Press Esc to close the overlay and return to the main dashboard. + +### Kitty Cat + +When running inside the [Kitty terminal](https://sw.kovidgoyal.net/kitty/), the splash screen adds a subtle 🐱 to the title bar. A small nod to the terminal that made it all possible. + +--- + +## Splash Screen + +On startup, the TUI displays a randomly selected piece of ASCII art from the [16colo.rs / Mistigris](https://16colo.rs/) archives — pre-baked as ANSI half-block art for instant rendering. The splash auto-dismisses after 5 seconds or on any keypress. + +![Splash screen](../../../assets/tui/splash.svg) + +Use `--no-splash` to skip it. + +--- + +## See Also + +- [SkyWalker RF Tool](/tools/skywalker/) — CLI version of the RF modes (text-only output, scriptable) +- [Spectrum Analysis](/tools/spectrum-analysis/) — practical how-to guides for each operating mode +- [Signal Monitoring](/bcm4500/signal-monitoring/) — SIGNAL_MONITOR register protocol and data format +- [Tuning Tool](/tools/tuning/) — primary tuning, LNB control, and transport stream capture +- [EEPROM Utilities](/tools/eeprom-utilities/) — standalone CLI for firmware updates (same safety protocol as F6 Device screen) +- [TS Stream Analyzer](/tools/ts-analyzer/) — standalone TS packet analyzer (same engine as F7 Stream screen) +- [Motor Control](/tools/motor/) — standalone CLI for DiSEqC 1.2 motor control (same engine as F9 Motor screen) +- [Carrier Survey](/tools/survey/) — standalone CLI for automated carrier survey (same engine as F10 Survey screen) +- [QO-100 DATV Reception](/guides/qo100-datv/) — complete guide to Es'hail-2 amateur television reception +- [RF Coverage](/hardware/rf-coverage/) — frequency coverage and antenna considerations diff --git a/site/src/content/docs/tools/tuning.mdx b/site/src/content/docs/tools/tuning.mdx index 50793b9..0f1a792 100644 --- a/site/src/content/docs/tools/tuning.mdx +++ b/site/src/content/docs/tools/tuning.mdx @@ -1,293 +1,293 @@ ---- -title: Tuning Tool -description: DVB-S tuning, signal monitoring, LNB control, DiSEqC switching, and transport stream capture. ---- - -import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; - -`tune.py` is the primary user-facing tool for operating the SkyWalker-1 as a satellite receiver. It handles the complete signal chain: powering the demodulator, configuring LNB voltage and tone, sending DiSEqC switch commands, tuning to a transponder, monitoring signal quality, and capturing MPEG-2 transport stream data. - -```bash -pip install pyusb -``` - -## Subcommands - -| Subcommand | Purpose | -|------------|---------| -| `status` | Show device config, firmware version, and signal info | -| `tune` | Tune to a transponder frequency | -| `stream` | Capture MPEG-2 transport stream data | -| `diseqc` | Send DiSEqC switch commands | -| `lnb` | Control LNB voltage and 22 kHz tone | - -Running with no subcommand defaults to `status`. - -## Global Options - -| Flag | Description | -|------|-------------| -| `-v, --verbose` | Show raw USB control transfer traffic | -| `--json` | Output machine-readable JSON (where supported) | - ---- - -## status -- Device Information - -Displays the current device state: firmware version, configuration status flags, signal lock, and SNR. - -```bash -sudo python3 tools/tune.py status -``` - -``` -Genpix SkyWalker-1 Status -================================================== - -USB: Bus 1 Addr 12 (VID 0x09C0, PID 0x0203) -FW: 2.06.4 (built 2007-07-13) - -Config: 0x07 - [ ON] 8PSK Started - [ ON] BCM4500 FW Loaded - [ ON] LNB Power On - [off] DVB Mode - [off] 22 kHz Tone - [off] 18V Selected - [off] DC Tuned - [off] Armed (streaming) - -Signal Lock: LOCKED -SNR: 8.2 dB (raw 0x0823) -Quality: [################------------------------] 42.3% -``` - -Use `--json` for programmatic access to all fields. - ---- - -## tune -- Transponder Tuning - -The 8-step tuning sequence: check status, boot demodulator, enable LNB power, set voltage (polarization), set extra voltage, set 22 kHz tone (band), send TUNE_8PSK command, wait for signal lock. - -### Required Arguments - -| Argument | Description | -|----------|-------------| -| `FREQ` | Transponder downlink frequency in MHz (e.g., `12520`) | -| `SR` | Symbol rate in ksps (e.g., `27500`) | - -### Options - -| Flag | Description | -|------|-------------| -| `--pol H/V/L/R` | Polarization: H (horizontal/18V), V (vertical/13V), L (left circular/18V), R (right circular/13V) | -| `--band low/high` | LNB band: low (tone off, 9750 MHz LO) or high (tone on, 10600 MHz LO) | -| `--lnb-lo MHZ` | Override LNB local oscillator frequency (default: 9750 low, 10600 high) | -| `--mod TYPE` | Modulation type (default: `qpsk`) | -| `--fec RATE` | FEC rate (default: `auto`) | -| `--timeout SECONDS` | Signal lock timeout (default: `10`) | -| `--extra-volt` | Enable +1V LNB voltage boost for long cables | -| `--json` | Output JSON result | - -### Modulation Types - -| Name | Description | FEC Group | -|------|-------------|-----------| -| `qpsk` | DVB-S QPSK | dvbs | -| `turbo-qpsk` | Turbo-coded QPSK | turbo | -| `turbo-8psk` | Turbo-coded 8PSK | turbo | -| `turbo-16qam` | Turbo-coded 16QAM | turbo-16qam | -| `dcii-combo` | Digicipher II Combo | dcii | -| `dcii-i` | Digicipher II I-stream | dcii | -| `dcii-q` | Digicipher II Q-stream | dcii | -| `dcii-oqpsk` | Digicipher II Offset QPSK | dcii | -| `dss` | DSS QPSK | dvbs | -| `bpsk` | DVB BPSK | dvbs | - -### FEC Rates by Group - -| Group | Available Rates | -|-------|----------------| -| **dvbs** | `1/2`, `2/3`, `3/4`, `5/6`, `7/8`, `auto`, `none` | -| **turbo** | `1/2`, `2/3`, `3/4`, `5/6`, `auto` | -| **turbo-16qam** | `3/4`, `auto` | -| **dcii** | `1/2`, `2/3`, `6/7`, `3/4`, `5/11`, `1/2+`, `2/3+`, `6/7+`, `3/4+`, `auto` | - - - - -```bash title="Standard DVB-S QPSK, Ku-band high" -sudo python3 tools/tune.py tune 12520 27500 --pol H --band high -``` - -```bash title="DVB-S with explicit FEC" -sudo python3 tools/tune.py tune 11836 27500 --pol V --band low --fec 3/4 -``` - -```bash title="C-band with custom LNB LO" -sudo python3 tools/tune.py tune 3960 26667 --pol V --lnb-lo 5150 -``` - - - - -```bash title="Turbo 8PSK" -sudo python3 tools/tune.py tune 12224 20000 --pol V --band high --mod turbo-8psk --fec 2/3 -``` - -```bash title="Turbo QPSK with auto FEC" -sudo python3 tools/tune.py tune 12090 20000 --pol H --band high --mod turbo-qpsk -``` - - - - -```bash title="Digicipher II Combo" -sudo python3 tools/tune.py tune 12224 20000 --pol V --band high --mod dcii-combo --fec auto -``` - -```bash title="DCII Split I-stream" -sudo python3 tools/tune.py tune 12224 20000 --pol V --band high --mod dcii-i --fec 6/7 -``` - - - - -### Tuning Output - -``` -Tuning SkyWalker-1 -================================================== - Downlink: 12520 MHz - LNB LO: 10600 MHz - IF Frequency: 1920.0 MHz (1920000 kHz) - Symbol Rate: 27500 ksps (27500000 sps) - Modulation: DVB-S QPSK (index 0) - FEC: auto (index 5) - Polarization: Horizontal (18V) - Band: high (22kHz on) - -[1/8] Config status: 0x07 -[2/8] Demodulator already running -[3/8] LNB power already on -[4/8] Setting LNB voltage: 18V -[5/8] Extra voltage: off -[6/8] 22 kHz tone: ON -[7/8] Sending TUNE_8PSK... -[8/8] Waiting for signal lock (timeout 10s)... -... - - LOCKED - SNR: 8.2 dB (raw 0x0823) - Quality: [################------------------------] 42.3% -``` - - - ---- - -## stream -- Transport Stream Capture - -Captures MPEG-2 transport stream data from EP2 bulk endpoint after the device is tuned and locked. Requires a prior successful `tune` command (signal must be locked). - -```bash title="Capture to file for 60 seconds" -sudo python3 tools/tune.py stream -o capture.ts --duration 60 -``` - -```bash title="Pipe directly to VLC" -sudo python3 tools/tune.py stream --stdout | vlc - -``` - -```bash title="Pipe to ffmpeg for recording" -sudo python3 tools/tune.py stream --stdout | ffmpeg -i pipe: -c copy output.mp4 -``` - -| Flag | Description | -|------|-------------| -| `-o, --output FILE` | Write TS data to file | -| `--stdout` | Write TS data to stdout (for piping) | -| `--duration SECONDS` | Capture duration (default: until Ctrl-C) | - -The stream status is written to stderr when using `--stdout`, so it does not contaminate the TS data pipe. - - - ---- - -## diseqc -- Switch Commands - -Send DiSEqC 1.0 port selection, mini-DiSEqC tone bursts, or raw DiSEqC messages. - -```bash title="Select DiSEqC 1.0 port 1" -sudo python3 tools/tune.py diseqc --port 1 -``` - -```bash title="Send tone burst A" -sudo python3 tools/tune.py diseqc --tone-burst A -``` - -```bash title="Send raw DiSEqC message" -sudo python3 tools/tune.py diseqc --raw E0 10 38 F0 -``` - -| Flag | Description | -|------|-------------| -| `--port 1-4` | DiSEqC 1.0 committed switch port | -| `--tone-burst A/B` | Mini-DiSEqC tone burst | -| `--raw HH HH ...` | Raw DiSEqC bytes in hex (3--6 bytes) | - -For details on the DiSEqC signaling implementation, see [DiSEqC Protocol](/lnb-diseqc/diseqc-protocol/). - ---- - -## lnb -- LNB Control - -Direct control of LNB voltage, 22 kHz tone, and power supply. Running with no flags shows the current LNB state. - -```bash title="Show LNB status" -sudo python3 tools/tune.py lnb -``` - -```bash title="Set voltage and tone" -sudo python3 tools/tune.py lnb --voltage 18 --tone on -``` - -```bash title="Power cycle the LNB" -sudo python3 tools/tune.py lnb --power off -sudo python3 tools/tune.py lnb --power on -``` - -| Flag | Description | -|------|-------------| -| `--voltage 13/18` | LNB voltage (13V = vertical, 18V = horizontal) | -| `--tone on/off` | 22 kHz tone (on = high band, off = low band) | -| `--extra-volt` | Enable +1V boost (13V becomes 14V, 18V becomes 19V) | -| `--power on/off` | LNB power supply on/off | - - - ---- - -## Signal Quality Metrics - -| Metric | Source | Description | -|--------|--------|-------------| -| **SNR** | `GET_SIGNAL_STRENGTH` (0x87) | Signal-to-noise ratio in dB, read as 16-bit value (dBu * 256 units) | -| **Lock** | `GET_SIGNAL_LOCK` (0x90) | BCM4500 register 0xA4, bit 5 indicates signal lock | -| **Quality %** | Computed | SNR scaled: `snr_raw * 17` maps to 0--100%, saturating at SNR >= `0x0F00` | - -For details on the signal monitoring protocol, see [Signal Monitoring](/bcm4500/signal-monitoring/). - -## See Also - -- [Tuning Protocol](/bcm4500/tuning-protocol/) -- firmware-level TUNE_8PSK command format -- [LNB Control](/lnb-diseqc/lnb-control/) -- hardware-level voltage and tone control -- [TS Analyzer](/tools/ts-analyzer/) -- analyze captured transport streams -- [Vendor Commands](/usb/vendor-commands/) -- complete USB command reference +--- +title: Tuning Tool +description: DVB-S tuning, signal monitoring, LNB control, DiSEqC switching, and transport stream capture. +--- + +import { Tabs, TabItem, Steps, Aside, Badge } from '@astrojs/starlight/components'; + +`tune.py` is the primary user-facing tool for operating the SkyWalker-1 as a satellite receiver. It handles the complete signal chain: powering the demodulator, configuring LNB voltage and tone, sending DiSEqC switch commands, tuning to a transponder, monitoring signal quality, and capturing MPEG-2 transport stream data. + +```bash +pip install pyusb +``` + +## Subcommands + +| Subcommand | Purpose | +|------------|---------| +| `status` | Show device config, firmware version, and signal info | +| `tune` | Tune to a transponder frequency | +| `stream` | Capture MPEG-2 transport stream data | +| `diseqc` | Send DiSEqC switch commands | +| `lnb` | Control LNB voltage and 22 kHz tone | + +Running with no subcommand defaults to `status`. + +## Global Options + +| Flag | Description | +|------|-------------| +| `-v, --verbose` | Show raw USB control transfer traffic | +| `--json` | Output machine-readable JSON (where supported) | + +--- + +## status -- Device Information + +Displays the current device state: firmware version, configuration status flags, signal lock, and SNR. + +```bash +sudo python3 tools/tune.py status +``` + +``` +Genpix SkyWalker-1 Status +================================================== + +USB: Bus 1 Addr 12 (VID 0x09C0, PID 0x0203) +FW: 2.06.4 (built 2007-07-13) + +Config: 0x07 + [ ON] 8PSK Started + [ ON] BCM4500 FW Loaded + [ ON] LNB Power On + [off] DVB Mode + [off] 22 kHz Tone + [off] 18V Selected + [off] DC Tuned + [off] Armed (streaming) + +Signal Lock: LOCKED +SNR: 8.2 dB (raw 0x0823) +Quality: [################------------------------] 42.3% +``` + +Use `--json` for programmatic access to all fields. + +--- + +## tune -- Transponder Tuning + +The 8-step tuning sequence: check status, boot demodulator, enable LNB power, set voltage (polarization), set extra voltage, set 22 kHz tone (band), send TUNE_8PSK command, wait for signal lock. + +### Required Arguments + +| Argument | Description | +|----------|-------------| +| `FREQ` | Transponder downlink frequency in MHz (e.g., `12520`) | +| `SR` | Symbol rate in ksps (e.g., `27500`) | + +### Options + +| Flag | Description | +|------|-------------| +| `--pol H/V/L/R` | Polarization: H (horizontal/18V), V (vertical/13V), L (left circular/18V), R (right circular/13V) | +| `--band low/high` | LNB band: low (tone off, 9750 MHz LO) or high (tone on, 10600 MHz LO) | +| `--lnb-lo MHZ` | Override LNB local oscillator frequency (default: 9750 low, 10600 high) | +| `--mod TYPE` | Modulation type (default: `qpsk`) | +| `--fec RATE` | FEC rate (default: `auto`) | +| `--timeout SECONDS` | Signal lock timeout (default: `10`) | +| `--extra-volt` | Enable +1V LNB voltage boost for long cables | +| `--json` | Output JSON result | + +### Modulation Types + +| Name | Description | FEC Group | +|------|-------------|-----------| +| `qpsk` | DVB-S QPSK | dvbs | +| `turbo-qpsk` | Turbo-coded QPSK | turbo | +| `turbo-8psk` | Turbo-coded 8PSK | turbo | +| `turbo-16qam` | Turbo-coded 16QAM | turbo-16qam | +| `dcii-combo` | Digicipher II Combo | dcii | +| `dcii-i` | Digicipher II I-stream | dcii | +| `dcii-q` | Digicipher II Q-stream | dcii | +| `dcii-oqpsk` | Digicipher II Offset QPSK | dcii | +| `dss` | DSS QPSK | dvbs | +| `bpsk` | DVB BPSK | dvbs | + +### FEC Rates by Group + +| Group | Available Rates | +|-------|----------------| +| **dvbs** | `1/2`, `2/3`, `3/4`, `5/6`, `7/8`, `auto`, `none` | +| **turbo** | `1/2`, `2/3`, `3/4`, `5/6`, `auto` | +| **turbo-16qam** | `3/4`, `auto` | +| **dcii** | `1/2`, `2/3`, `6/7`, `3/4`, `5/11`, `1/2+`, `2/3+`, `6/7+`, `3/4+`, `auto` | + + + + +```bash title="Standard DVB-S QPSK, Ku-band high" +sudo python3 tools/tune.py tune 12520 27500 --pol H --band high +``` + +```bash title="DVB-S with explicit FEC" +sudo python3 tools/tune.py tune 11836 27500 --pol V --band low --fec 3/4 +``` + +```bash title="C-band with custom LNB LO" +sudo python3 tools/tune.py tune 3960 26667 --pol V --lnb-lo 5150 +``` + + + + +```bash title="Turbo 8PSK" +sudo python3 tools/tune.py tune 12224 20000 --pol V --band high --mod turbo-8psk --fec 2/3 +``` + +```bash title="Turbo QPSK with auto FEC" +sudo python3 tools/tune.py tune 12090 20000 --pol H --band high --mod turbo-qpsk +``` + + + + +```bash title="Digicipher II Combo" +sudo python3 tools/tune.py tune 12224 20000 --pol V --band high --mod dcii-combo --fec auto +``` + +```bash title="DCII Split I-stream" +sudo python3 tools/tune.py tune 12224 20000 --pol V --band high --mod dcii-i --fec 6/7 +``` + + + + +### Tuning Output + +``` +Tuning SkyWalker-1 +================================================== + Downlink: 12520 MHz + LNB LO: 10600 MHz + IF Frequency: 1920.0 MHz (1920000 kHz) + Symbol Rate: 27500 ksps (27500000 sps) + Modulation: DVB-S QPSK (index 0) + FEC: auto (index 5) + Polarization: Horizontal (18V) + Band: high (22kHz on) + +[1/8] Config status: 0x07 +[2/8] Demodulator already running +[3/8] LNB power already on +[4/8] Setting LNB voltage: 18V +[5/8] Extra voltage: off +[6/8] 22 kHz tone: ON +[7/8] Sending TUNE_8PSK... +[8/8] Waiting for signal lock (timeout 10s)... +... + + LOCKED + SNR: 8.2 dB (raw 0x0823) + Quality: [################------------------------] 42.3% +``` + + + +--- + +## stream -- Transport Stream Capture + +Captures MPEG-2 transport stream data from EP2 bulk endpoint after the device is tuned and locked. Requires a prior successful `tune` command (signal must be locked). + +```bash title="Capture to file for 60 seconds" +sudo python3 tools/tune.py stream -o capture.ts --duration 60 +``` + +```bash title="Pipe directly to VLC" +sudo python3 tools/tune.py stream --stdout | vlc - +``` + +```bash title="Pipe to ffmpeg for recording" +sudo python3 tools/tune.py stream --stdout | ffmpeg -i pipe: -c copy output.mp4 +``` + +| Flag | Description | +|------|-------------| +| `-o, --output FILE` | Write TS data to file | +| `--stdout` | Write TS data to stdout (for piping) | +| `--duration SECONDS` | Capture duration (default: until Ctrl-C) | + +The stream status is written to stderr when using `--stdout`, so it does not contaminate the TS data pipe. + + + +--- + +## diseqc -- Switch Commands + +Send DiSEqC 1.0 port selection, mini-DiSEqC tone bursts, or raw DiSEqC messages. + +```bash title="Select DiSEqC 1.0 port 1" +sudo python3 tools/tune.py diseqc --port 1 +``` + +```bash title="Send tone burst A" +sudo python3 tools/tune.py diseqc --tone-burst A +``` + +```bash title="Send raw DiSEqC message" +sudo python3 tools/tune.py diseqc --raw E0 10 38 F0 +``` + +| Flag | Description | +|------|-------------| +| `--port 1-4` | DiSEqC 1.0 committed switch port | +| `--tone-burst A/B` | Mini-DiSEqC tone burst | +| `--raw HH HH ...` | Raw DiSEqC bytes in hex (3--6 bytes) | + +For details on the DiSEqC signaling implementation, see [DiSEqC Protocol](/lnb-diseqc/diseqc-protocol/). + +--- + +## lnb -- LNB Control + +Direct control of LNB voltage, 22 kHz tone, and power supply. Running with no flags shows the current LNB state. + +```bash title="Show LNB status" +sudo python3 tools/tune.py lnb +``` + +```bash title="Set voltage and tone" +sudo python3 tools/tune.py lnb --voltage 18 --tone on +``` + +```bash title="Power cycle the LNB" +sudo python3 tools/tune.py lnb --power off +sudo python3 tools/tune.py lnb --power on +``` + +| Flag | Description | +|------|-------------| +| `--voltage 13/18` | LNB voltage (13V = vertical, 18V = horizontal) | +| `--tone on/off` | 22 kHz tone (on = high band, off = low band) | +| `--extra-volt` | Enable +1V boost (13V becomes 14V, 18V becomes 19V) | +| `--power on/off` | LNB power supply on/off | + + + +--- + +## Signal Quality Metrics + +| Metric | Source | Description | +|--------|--------|-------------| +| **SNR** | `GET_SIGNAL_STRENGTH` (0x87) | Signal-to-noise ratio in dB, read as 16-bit value (dBu * 256 units) | +| **Lock** | `GET_SIGNAL_LOCK` (0x90) | BCM4500 register 0xA4, bit 5 indicates signal lock | +| **Quality %** | Computed | SNR scaled: `snr_raw * 17` maps to 0--100%, saturating at SNR >= `0x0F00` | + +For details on the signal monitoring protocol, see [Signal Monitoring](/bcm4500/signal-monitoring/). + +## See Also + +- [Tuning Protocol](/bcm4500/tuning-protocol/) -- firmware-level TUNE_8PSK command format +- [LNB Control](/lnb-diseqc/lnb-control/) -- hardware-level voltage and tone control +- [TS Analyzer](/tools/ts-analyzer/) -- analyze captured transport streams +- [Vendor Commands](/usb/vendor-commands/) -- complete USB command reference diff --git a/site/src/content/docs/usb/boot-sequence.mdx b/site/src/content/docs/usb/boot-sequence.mdx index e551649..10f6aea 100644 --- a/site/src/content/docs/usb/boot-sequence.mdx +++ b/site/src/content/docs/usb/boot-sequence.mdx @@ -1,135 +1,135 @@ ---- -title: Boot Sequence -description: Complete power-on boot flow from USB enumeration through BCM4500 demodulator initialization. ---- - -import { Steps, Badge, Aside } from '@astrojs/starlight/components'; - -## EEPROM Boot (Hardware) - -The SkyWalker-1 firmware is stored in a 24Cxx-family I2C EEPROM in Cypress C2 format. On power-up, the FX2 boot ROM reads this firmware automatically -- no host interaction is required. - -### C2 EEPROM Format - -| Offset | Size | Field | SkyWalker-1 Value | -|--------|------|-------|-------------------| -| 0 | 1 | Marker | 0xC2 (external memory, large code model) | -| 1 | 2 | VID (LE) | 0x09C0 | -| 3 | 2 | PID (LE) | 0x0203 | -| 5 | 2 | DID (LE) | 0x0000 | -| 7 | 1 | Config | 0x40 (400 kHz I2C) | - -Code segments follow the header: 2-byte length (BE) + 2-byte target address (BE) + data. Maximum segment size is 1023 bytes (FX2 I2C boot ROM buffer limit). All SkyWalker-1 variants use 10 segments. - -The terminator is 0x80xx (high bit set) + 2-byte entry point address (0xE600 = CPUCS register). - -## Kernel Driver Boot Flow - -After USB enumeration, the Linux `dvb_usb_gp8psk` driver executes this initialization sequence: - - -1. **GET_8PSK_CONFIG (0x80)** -- Read the [configuration status byte](/usb/config-status/). Check bit 0 (`bm8pskStarted`). - -2. **BOOT_8PSK (0x89, wValue=1)** -- If not started, power on the BCM4500 demodulator. Then read the firmware version via **GET_FW_VERS (0x92)**. - -3. **LOAD_BCM4500 (0x88)** -- If bit 1 (`bm8pskFW_Loaded`) is clear, load BCM4500 firmware. This only applies to Rev.1 Warm (PID 0x0201). On SkyWalker-1, this bit is always set and 0x88 returns STALL. - -4. **START_INTERSIL (0x8A, wValue=1)** -- If bit 2 (`bmIntersilOn`) is clear, enable the LNB power supply. - -5. **SET_DVB_MODE (0x8E, wValue=1)** -- Attempt to set DVB mode. This STALLs on all SkyWalker-1 firmware versions (the command is a no-op). - -6. **ARM_TRANSFER (0x85, wValue=0)** -- Abort any pending MPEG-2 transfer to ensure a clean state. - -7. **Device ready for tuning** -- The kernel driver reports the frontend as available. - - -## BCM4500 Demodulator Boot (BOOT_8PSK, 0x89) - -The BOOT_8PSK command handler powers on the BCM4500 demodulator and writes three initialization register blocks via I2C. This sequence was reverse-engineered from stock v2.06 firmware (`FUN_CODE_1D4F` + `FUN_CODE_0ddd`) and re-implemented in custom firmware v3.01.0. - - -1. **Assert BCM4500 RESET** -- Drive P0.5 LOW. This holds the BCM4500's digital logic in reset while power is applied. - -2. **Power on** -- Set P0.1 HIGH (power enable), P0.2 LOW (power disable off). The SkyWalker-1 has complementary power control pins. - -3. **Wait for power settle** -- 30 ms delay. The power supply must reach regulation before releasing reset. - -4. **Release RESET** -- Drive P0.5 HIGH. The BCM4500 begins its internal power-on reset (POR) and mask ROM boot sequence. - -5. **Wait for BCM4500 POR** -- 50 ms delay. The chip needs time for its internal oscillator to stabilize and mask ROM to execute. - -6. **I2C probe** -- Read direct register 0xA2 (status) at I2C address 0x08 to verify the chip is alive and responding. If this fails, boot aborts. - -7. **Write init block 0** -- 7 bytes to indirect page 0, starting at register 0x06. Data: `{06 0b 17 38 9f d9 80}`. - -8. **Write init block 1** -- 8 bytes to indirect page 0, starting at register 0x07. Data: `{07 09 39 4f 00 65 b7 10}`. - -9. **Write init block 2** -- 3 bytes to indirect page 0, starting at register 0x0F. Data: `{0f 0c 09}`. - -10. **Set config_status** -- OR in `BM_STARTED | BM_FW_LOADED` (0x03). Subsequent vendor commands check this flag before operating. - - -Each init block is written using the BCM4500 [indirect register protocol](/bcm4500/demodulator/): page select (0xA6 = 0x00), data to 0xA7 (multi-byte with auto-increment), trailing zero to 0xA7, then commit (0xA8 = 0x03). The firmware polls 0xA8 until the command completes before proceeding. - -**Total boot time**: approximately 90 ms (30 ms power settle + 50 ms POR delay + ~10 ms I2C transactions). - -### Boot Results - -| Metric | Value | -|--------|-------| -| Boot time | ~90 ms total | -| config_status after boot | 0x03 (STARTED + FW_LOADED) | -| Direct registers 0xA2-0xA8 | All return 0x02 (powered, not locked -- expected without signal) | -| Signal lock | 0x00 (no lock -- no satellite signal present) | - - - -## Firmware Version Identification - -The kernel reads the firmware version on boot via GET_FW_VERS (0x92) and logs it: - -``` -gp8psk: FW Version = 2.06.4 (0x20604) Build 2007/07/13 -``` - -Kernel revision constants for hardware detection: - -```c title="gp8psk-fe.h" -GP8PSK_FW_REV1 = 0x020604 // v2.06.4 -GP8PSK_FW_REV2 = 0x020704 // v2.07.4 -``` - -If `fw_vers >= GP8PSK_FW_REV2`, the kernel enables Rev.2-specific code paths. The v2.10 and v2.13 firmwares are newer than either constant and trigger Rev.2 behavior. - -## FX2 CPUCS Recovery - -The FX2's CPUCS register at 0xE600 is accessible via the standard vendor request `bRequest=0xA0` (RAM read/write), which is handled by the FX2 boot ROM in silicon -- not by user firmware. This means firmware can be reloaded over a completely hung device without a physical USB unplug: - -```bash title="Recover a hung device" -sudo python3 tools/fw_load.py load firmware/build/skywalker1.ihx --wait 3 -``` - -Writing 0x01 to CPUCS halts the CPU. New code is written to RAM. Writing 0x00 restarts it. The device re-enumerates with the new firmware. - -## Custom Debug Boot Modes (v3.01.0) - -The custom firmware extends BOOT_8PSK (0x89) with incremental debug modes for isolating boot failures: - -| wValue | Action | Purpose | -|--------|--------|---------| -| 0x80 | No-op: return `config_status` and `boot_stage` | Status check without side effects | -| 0x81 | GPIO + power + delays only (no I2C) | Test power sequencing | -| 0x82 | GPIO + power + I2C probe | Test I2C after power-on | -| 0x83 | GPIO + power + probe + init block 0 | Test first register write | -| 0x84 | I2C probe only (chip already powered) | Isolate I2C from power sequencing | -| 0x85 | Same as 0x82 without bmSTOP | Confirmed the STOP corruption bug | -| 0x01 | Full boot (production) | Normal operation | -| 0x00 | Shutdown | Power down demodulator | - -These modes were instrumental in identifying the [I2C STOP corruption bug](/i2c/stop-corruption-bug/) -- mode 0x82 (with bmSTOP) failed while mode 0x85 (without bmSTOP) succeeded, pinpointing the exact cause. +--- +title: Boot Sequence +description: Complete power-on boot flow from USB enumeration through BCM4500 demodulator initialization. +--- + +import { Steps, Badge, Aside } from '@astrojs/starlight/components'; + +## EEPROM Boot (Hardware) + +The SkyWalker-1 firmware is stored in a 24Cxx-family I2C EEPROM in Cypress C2 format. On power-up, the FX2 boot ROM reads this firmware automatically -- no host interaction is required. + +### C2 EEPROM Format + +| Offset | Size | Field | SkyWalker-1 Value | +|--------|------|-------|-------------------| +| 0 | 1 | Marker | 0xC2 (external memory, large code model) | +| 1 | 2 | VID (LE) | 0x09C0 | +| 3 | 2 | PID (LE) | 0x0203 | +| 5 | 2 | DID (LE) | 0x0000 | +| 7 | 1 | Config | 0x40 (400 kHz I2C) | + +Code segments follow the header: 2-byte length (BE) + 2-byte target address (BE) + data. Maximum segment size is 1023 bytes (FX2 I2C boot ROM buffer limit). All SkyWalker-1 variants use 10 segments. + +The terminator is 0x80xx (high bit set) + 2-byte entry point address (0xE600 = CPUCS register). + +## Kernel Driver Boot Flow + +After USB enumeration, the Linux `dvb_usb_gp8psk` driver executes this initialization sequence: + + +1. **GET_8PSK_CONFIG (0x80)** -- Read the [configuration status byte](/usb/config-status/). Check bit 0 (`bm8pskStarted`). + +2. **BOOT_8PSK (0x89, wValue=1)** -- If not started, power on the BCM4500 demodulator. Then read the firmware version via **GET_FW_VERS (0x92)**. + +3. **LOAD_BCM4500 (0x88)** -- If bit 1 (`bm8pskFW_Loaded`) is clear, load BCM4500 firmware. This only applies to Rev.1 Warm (PID 0x0201). On SkyWalker-1, this bit is always set and 0x88 returns STALL. + +4. **START_INTERSIL (0x8A, wValue=1)** -- If bit 2 (`bmIntersilOn`) is clear, enable the LNB power supply. + +5. **SET_DVB_MODE (0x8E, wValue=1)** -- Attempt to set DVB mode. This STALLs on all SkyWalker-1 firmware versions (the command is a no-op). + +6. **ARM_TRANSFER (0x85, wValue=0)** -- Abort any pending MPEG-2 transfer to ensure a clean state. + +7. **Device ready for tuning** -- The kernel driver reports the frontend as available. + + +## BCM4500 Demodulator Boot (BOOT_8PSK, 0x89) + +The BOOT_8PSK command handler powers on the BCM4500 demodulator and writes three initialization register blocks via I2C. This sequence was reverse-engineered from stock v2.06 firmware (`FUN_CODE_1D4F` + `FUN_CODE_0ddd`) and re-implemented in custom firmware v3.01.0. + + +1. **Assert BCM4500 RESET** -- Drive P0.5 LOW. This holds the BCM4500's digital logic in reset while power is applied. + +2. **Power on** -- Set P0.1 HIGH (power enable), P0.2 LOW (power disable off). The SkyWalker-1 has complementary power control pins. + +3. **Wait for power settle** -- 30 ms delay. The power supply must reach regulation before releasing reset. + +4. **Release RESET** -- Drive P0.5 HIGH. The BCM4500 begins its internal power-on reset (POR) and mask ROM boot sequence. + +5. **Wait for BCM4500 POR** -- 50 ms delay. The chip needs time for its internal oscillator to stabilize and mask ROM to execute. + +6. **I2C probe** -- Read direct register 0xA2 (status) at I2C address 0x08 to verify the chip is alive and responding. If this fails, boot aborts. + +7. **Write init block 0** -- 7 bytes to indirect page 0, starting at register 0x06. Data: `{06 0b 17 38 9f d9 80}`. + +8. **Write init block 1** -- 8 bytes to indirect page 0, starting at register 0x07. Data: `{07 09 39 4f 00 65 b7 10}`. + +9. **Write init block 2** -- 3 bytes to indirect page 0, starting at register 0x0F. Data: `{0f 0c 09}`. + +10. **Set config_status** -- OR in `BM_STARTED | BM_FW_LOADED` (0x03). Subsequent vendor commands check this flag before operating. + + +Each init block is written using the BCM4500 [indirect register protocol](/bcm4500/demodulator/): page select (0xA6 = 0x00), data to 0xA7 (multi-byte with auto-increment), trailing zero to 0xA7, then commit (0xA8 = 0x03). The firmware polls 0xA8 until the command completes before proceeding. + +**Total boot time**: approximately 90 ms (30 ms power settle + 50 ms POR delay + ~10 ms I2C transactions). + +### Boot Results + +| Metric | Value | +|--------|-------| +| Boot time | ~90 ms total | +| config_status after boot | 0x03 (STARTED + FW_LOADED) | +| Direct registers 0xA2-0xA8 | All return 0x02 (powered, not locked -- expected without signal) | +| Signal lock | 0x00 (no lock -- no satellite signal present) | + + + +## Firmware Version Identification + +The kernel reads the firmware version on boot via GET_FW_VERS (0x92) and logs it: + +``` +gp8psk: FW Version = 2.06.4 (0x20604) Build 2007/07/13 +``` + +Kernel revision constants for hardware detection: + +```c title="gp8psk-fe.h" +GP8PSK_FW_REV1 = 0x020604 // v2.06.4 +GP8PSK_FW_REV2 = 0x020704 // v2.07.4 +``` + +If `fw_vers >= GP8PSK_FW_REV2`, the kernel enables Rev.2-specific code paths. The v2.10 and v2.13 firmwares are newer than either constant and trigger Rev.2 behavior. + +## FX2 CPUCS Recovery + +The FX2's CPUCS register at 0xE600 is accessible via the standard vendor request `bRequest=0xA0` (RAM read/write), which is handled by the FX2 boot ROM in silicon -- not by user firmware. This means firmware can be reloaded over a completely hung device without a physical USB unplug: + +```bash title="Recover a hung device" +sudo python3 tools/fw_load.py load firmware/build/skywalker1.ihx --wait 3 +``` + +Writing 0x01 to CPUCS halts the CPU. New code is written to RAM. Writing 0x00 restarts it. The device re-enumerates with the new firmware. + +## Custom Debug Boot Modes (v3.01.0) + +The custom firmware extends BOOT_8PSK (0x89) with incremental debug modes for isolating boot failures: + +| wValue | Action | Purpose | +|--------|--------|---------| +| 0x80 | No-op: return `config_status` and `boot_stage` | Status check without side effects | +| 0x81 | GPIO + power + delays only (no I2C) | Test power sequencing | +| 0x82 | GPIO + power + I2C probe | Test I2C after power-on | +| 0x83 | GPIO + power + probe + init block 0 | Test first register write | +| 0x84 | I2C probe only (chip already powered) | Isolate I2C from power sequencing | +| 0x85 | Same as 0x82 without bmSTOP | Confirmed the STOP corruption bug | +| 0x01 | Full boot (production) | Normal operation | +| 0x00 | Shutdown | Power down demodulator | + +These modes were instrumental in identifying the [I2C STOP corruption bug](/i2c/stop-corruption-bug/) -- mode 0x82 (with bmSTOP) failed while mode 0x85 (without bmSTOP) succeeded, pinpointing the exact cause. diff --git a/site/src/content/docs/usb/config-status.mdx b/site/src/content/docs/usb/config-status.mdx index d8bf362..14a7f6a 100644 --- a/site/src/content/docs/usb/config-status.mdx +++ b/site/src/content/docs/usb/config-status.mdx @@ -1,79 +1,79 @@ ---- -title: Configuration Status Byte -description: Bit-field breakdown of the configuration status byte returned by GET_8PSK_CONFIG (0x80). ---- - -import { Badge, Aside } from '@astrojs/starlight/components'; - -The configuration status byte is returned by the GET_8PSK_CONFIG vendor command (0x80) and reflects the current device state. The kernel driver checks these bits during initialization to determine which boot steps have already completed. - -## Bit Field Map - -| Bit | Mask | Name | Meaning | -|-----|------|------|---------| -| 7 | 0x80 | bmArmed | MPEG-2 stream transfer armed / GPIF active | -| 6 | 0x40 | bmDCtuned | DC offset tuning complete (set for DCII modes) | -| 5 | 0x20 | bmSEL18V | 18V LNB voltage selected (else 13V) | -| 4 | 0x10 | bm22kHz | 22 kHz tone active | -| 3 | 0x08 | bmDVBmode | DVB mode enabled | -| 2 | 0x04 | bmIntersilOn | LNB power supply enabled | -| 1 | 0x02 | bm8pskFW_Loaded | BCM4500 firmware loaded | -| 0 | 0x01 | bm8pskStarted | Device booted and running | - -## IRAM Storage Address - -The status byte is stored at a different IRAM address depending on firmware version: - -| Firmware | IRAM Address | -|----------|-------------| -| v2.06 | 0x6D | -| Rev.2 v2.10.4 | 0x4E | -| v2.13 | 0x4F | - -## Bit Details - -### Bit 0: bm8pskStarted - -Set when BOOT_8PSK (0x89, wValue=1) completes successfully. The kernel driver checks this bit first. If clear, it sends BOOT_8PSK to power on the demodulator. - -### Bit 1: bm8pskFW_Loaded - -Indicates that the BCM4500 firmware has been loaded. On the SkyWalker-1, the BCM4500 runs from internal mask ROM, so this bit is **always set** after boot. The kernel driver checks this to decide whether to send LOAD_BCM4500 (0x88), which STALLs on the SkyWalker-1 since it is unnecessary. - -### Bit 2: bmIntersilOn - -Set when START_INTERSIL (0x8A, wValue=1) enables the LNB power supply. The name "Intersil" refers to the LNB voltage regulator IC manufacturer. - -### Bit 3: bmDVBmode - -Set when DVB-S mode is enabled via SET_DVB_MODE (0x8E). On SkyWalker-1, this command STALLs -- the bit is managed internally by the tuning dispatch logic. - -### Bit 4: bm22kHz - -Reflects the current state of the 22 kHz tone (SET_22KHZ_TONE, 0x8C). Set when the tone is active (high band), clear when inactive (low band). - -### Bit 5: bmSEL18V - -Reflects the current LNB voltage selection (SET_LNB_VOLTAGE, 0x8B). Set for 18V (horizontal/circular-left), clear for 13V (vertical/circular-right). - -### Bit 6: bmDCtuned - -Set when tuning to a Digicipher II (DCII) modulation mode. Cleared for all other modulation types (DVB-S, Turbo, DSS, BPSK). The tuning dispatch logic manages this bit during TUNE_8PSK (0x86) processing. - -### Bit 7: bmArmed - -Set when ARM_TRANSFER (0x85, wValue=1) starts the MPEG-2 transport stream. Cleared when ARM_TRANSFER (0x85, wValue=0) stops it. While set, the [GPIF engine](/bcm4500/gpif-streaming/) is continuously reading data from the BCM4500 into the EP2 FIFO. - -## Typical Values - -| State | Value | Bits Set | -|-------|-------|----------| -| After power-on (before boot) | 0x00 | None | -| After BOOT_8PSK | 0x03 | bm8pskStarted + bm8pskFW_Loaded | -| After LNB enable + 18V + tone | 0x37 | Started + FW + Intersil + 18V + 22kHz | -| Streaming DVB-S | 0xB7 | Above + bmArmed | -| Streaming DCII | 0xF7 | Above + bmDCtuned | - - +--- +title: Configuration Status Byte +description: Bit-field breakdown of the configuration status byte returned by GET_8PSK_CONFIG (0x80). +--- + +import { Badge, Aside } from '@astrojs/starlight/components'; + +The configuration status byte is returned by the GET_8PSK_CONFIG vendor command (0x80) and reflects the current device state. The kernel driver checks these bits during initialization to determine which boot steps have already completed. + +## Bit Field Map + +| Bit | Mask | Name | Meaning | +|-----|------|------|---------| +| 7 | 0x80 | bmArmed | MPEG-2 stream transfer armed / GPIF active | +| 6 | 0x40 | bmDCtuned | DC offset tuning complete (set for DCII modes) | +| 5 | 0x20 | bmSEL18V | 18V LNB voltage selected (else 13V) | +| 4 | 0x10 | bm22kHz | 22 kHz tone active | +| 3 | 0x08 | bmDVBmode | DVB mode enabled | +| 2 | 0x04 | bmIntersilOn | LNB power supply enabled | +| 1 | 0x02 | bm8pskFW_Loaded | BCM4500 firmware loaded | +| 0 | 0x01 | bm8pskStarted | Device booted and running | + +## IRAM Storage Address + +The status byte is stored at a different IRAM address depending on firmware version: + +| Firmware | IRAM Address | +|----------|-------------| +| v2.06 | 0x6D | +| Rev.2 v2.10.4 | 0x4E | +| v2.13 | 0x4F | + +## Bit Details + +### Bit 0: bm8pskStarted + +Set when BOOT_8PSK (0x89, wValue=1) completes successfully. The kernel driver checks this bit first. If clear, it sends BOOT_8PSK to power on the demodulator. + +### Bit 1: bm8pskFW_Loaded + +Indicates that the BCM4500 firmware has been loaded. On the SkyWalker-1, the BCM4500 runs from internal mask ROM, so this bit is **always set** after boot. The kernel driver checks this to decide whether to send LOAD_BCM4500 (0x88), which STALLs on the SkyWalker-1 since it is unnecessary. + +### Bit 2: bmIntersilOn + +Set when START_INTERSIL (0x8A, wValue=1) enables the LNB power supply. The name "Intersil" refers to the LNB voltage regulator IC manufacturer. + +### Bit 3: bmDVBmode + +Set when DVB-S mode is enabled via SET_DVB_MODE (0x8E). On SkyWalker-1, this command STALLs -- the bit is managed internally by the tuning dispatch logic. + +### Bit 4: bm22kHz + +Reflects the current state of the 22 kHz tone (SET_22KHZ_TONE, 0x8C). Set when the tone is active (high band), clear when inactive (low band). + +### Bit 5: bmSEL18V + +Reflects the current LNB voltage selection (SET_LNB_VOLTAGE, 0x8B). Set for 18V (horizontal/circular-left), clear for 13V (vertical/circular-right). + +### Bit 6: bmDCtuned + +Set when tuning to a Digicipher II (DCII) modulation mode. Cleared for all other modulation types (DVB-S, Turbo, DSS, BPSK). The tuning dispatch logic manages this bit during TUNE_8PSK (0x86) processing. + +### Bit 7: bmArmed + +Set when ARM_TRANSFER (0x85, wValue=1) starts the MPEG-2 transport stream. Cleared when ARM_TRANSFER (0x85, wValue=0) stops it. While set, the [GPIF engine](/bcm4500/gpif-streaming/) is continuously reading data from the BCM4500 into the EP2 FIFO. + +## Typical Values + +| State | Value | Bits Set | +|-------|-------|----------| +| After power-on (before boot) | 0x00 | None | +| After BOOT_8PSK | 0x03 | bm8pskStarted + bm8pskFW_Loaded | +| After LNB enable + 18V + tone | 0x37 | Started + FW + Intersil + 18V + 22kHz | +| Streaming DVB-S | 0xB7 | Above + bmArmed | +| Streaming DCII | 0xF7 | Above + bmDCtuned | + + diff --git a/site/src/content/docs/usb/interface.mdx b/site/src/content/docs/usb/interface.mdx index e36e7ca..34f5c8c 100644 --- a/site/src/content/docs/usb/interface.mdx +++ b/site/src/content/docs/usb/interface.mdx @@ -1,105 +1,105 @@ ---- -title: USB Interface -description: USB VID/PID table, endpoint map, descriptors, and warm boot behavior for the SkyWalker-1. ---- - -import { Badge, Aside } from '@astrojs/starlight/components'; - -## VID/PID Table - -All Genpix products share USB Vendor ID `0x09C0`: - -| PID | Product | State | Notes | -|-----|---------|-------|-------| -| 0x0200 | 8PSK-to-USB2 Rev.1 | | Requires FW01 upload to RAM | -| 0x0201 | 8PSK-to-USB2 Rev.1 | | Requires FW02 (BCM4500 firmware) | -| 0x0202 | 8PSK-to-USB2 Rev.2 | | Boots from EEPROM | -| 0x0203 | **SkyWalker-1** | | Boots from EEPROM | -| 0x0204 | SkyWalker-1 (alternate) | | Boots from EEPROM | -| 0x0205 | SkyWalker-2 | -- | Not in kernel 6.16.5 | -| 0x0206 | SkyWalker CW3K | | Requires CW3K_INIT (0x9D) | - -PID `0x0203` was added to the Linux kernel `dvb_usb_gp8psk` device table after v6.6.1. - -## Endpoint Map - -| Property | Value | -|----------|-------| -| Control endpoint | EP0 (default pipe, vendor requests) | -| Bulk IN endpoint | EP2 (address 0x82) -- MPEG-2 transport stream | -| Generic bulk CTRL endpoint | 0x01 (BCM4500 FW02 upload, Rev.1 only) | - -## Streaming Properties - -| Property | Value | -|----------|-------| -| URB count | 7 | -| URB buffer size | 8192 bytes each | -| Stream type | USB_BULK | -| FX2 controller type | CYPRESS_FX2 | - -## EP2 Endpoint Configuration - -```c title="EP2CFG Register (0xE610)" -EP2CFG = 0xE2; // valid=1, dir=IN, type=BULK, size=512, buf=DOUBLE -``` - -| Bit | Value | Meaning | -|-----|-------|---------| -| 7 (VALID) | 1 | Endpoint enabled | -| 6 (DIR) | 1 | IN (device to host) | -| 5:4 (TYPE) | 10 | Bulk transfer | -| 3 (SIZE) | 0 | 512-byte packets | -| 1:0 (BUF) | 10 | Double-buffered | - -EP4, EP6, and EP8 are disabled (`&= ~bmVALID`). Only EP2 is used for data streaming. - -## Warm Boot Behavior - -The SkyWalker-1 (PID 0x0203) enumerates directly as a "warm" device. The DVB-USB framework skips firmware download when `cold_ids` is NULL. No host-side firmware files are required. - -| Device | PID | Needs FW01? | Needs FW02? | Boot Source | -|--------|-----|-------------|-------------|-------------| -| Rev.1 Cold | 0x0200 | Yes | -- | RAM (empty) | -| Rev.1 Warm | 0x0201 | No | Yes | RAM (FW01 loaded) | -| Rev.2 | 0x0202 | No | No | EEPROM | -| SkyWalker-1 | 0x0203 | No | No | EEPROM | -| SkyWalker CW3K | 0x0206 | No | No | EEPROM | - - - -## USB Control Transfer Protocol - -All vendor commands use USB control transfers with these common parameters: - -| Parameter | Value | -|-----------|-------| -| bmRequestType | `USB_TYPE_VENDOR` (0x40 for OUT, 0xC0 for IN) | -| Timeout | 2000 ms | -| Retry | Up to 3 attempts for IN operations if partial data received | -| Data buffer maximum | 80 bytes (kernel driver) | - -See [Vendor Commands](/usb/vendor-commands/) for the complete command reference. - -## Kernel Driver Modules - -The Linux kernel uses two modules for the SkyWalker-1: - -| Module | Function | -|--------|----------| -| `dvb_usb_gp8psk` | USB transport layer, device management, firmware loading | -| `gp8psk_fe` | DVB frontend operations (demodulation, tuning, signal status) | - - +--- +title: USB Interface +description: USB VID/PID table, endpoint map, descriptors, and warm boot behavior for the SkyWalker-1. +--- + +import { Badge, Aside } from '@astrojs/starlight/components'; + +## VID/PID Table + +All Genpix products share USB Vendor ID `0x09C0`: + +| PID | Product | State | Notes | +|-----|---------|-------|-------| +| 0x0200 | 8PSK-to-USB2 Rev.1 | | Requires FW01 upload to RAM | +| 0x0201 | 8PSK-to-USB2 Rev.1 | | Requires FW02 (BCM4500 firmware) | +| 0x0202 | 8PSK-to-USB2 Rev.2 | | Boots from EEPROM | +| 0x0203 | **SkyWalker-1** | | Boots from EEPROM | +| 0x0204 | SkyWalker-1 (alternate) | | Boots from EEPROM | +| 0x0205 | SkyWalker-2 | -- | Not in kernel 6.16.5 | +| 0x0206 | SkyWalker CW3K | | Requires CW3K_INIT (0x9D) | + +PID `0x0203` was added to the Linux kernel `dvb_usb_gp8psk` device table after v6.6.1. + +## Endpoint Map + +| Property | Value | +|----------|-------| +| Control endpoint | EP0 (default pipe, vendor requests) | +| Bulk IN endpoint | EP2 (address 0x82) -- MPEG-2 transport stream | +| Generic bulk CTRL endpoint | 0x01 (BCM4500 FW02 upload, Rev.1 only) | + +## Streaming Properties + +| Property | Value | +|----------|-------| +| URB count | 7 | +| URB buffer size | 8192 bytes each | +| Stream type | USB_BULK | +| FX2 controller type | CYPRESS_FX2 | + +## EP2 Endpoint Configuration + +```c title="EP2CFG Register (0xE610)" +EP2CFG = 0xE2; // valid=1, dir=IN, type=BULK, size=512, buf=DOUBLE +``` + +| Bit | Value | Meaning | +|-----|-------|---------| +| 7 (VALID) | 1 | Endpoint enabled | +| 6 (DIR) | 1 | IN (device to host) | +| 5:4 (TYPE) | 10 | Bulk transfer | +| 3 (SIZE) | 0 | 512-byte packets | +| 1:0 (BUF) | 10 | Double-buffered | + +EP4, EP6, and EP8 are disabled (`&= ~bmVALID`). Only EP2 is used for data streaming. + +## Warm Boot Behavior + +The SkyWalker-1 (PID 0x0203) enumerates directly as a "warm" device. The DVB-USB framework skips firmware download when `cold_ids` is NULL. No host-side firmware files are required. + +| Device | PID | Needs FW01? | Needs FW02? | Boot Source | +|--------|-----|-------------|-------------|-------------| +| Rev.1 Cold | 0x0200 | Yes | -- | RAM (empty) | +| Rev.1 Warm | 0x0201 | No | Yes | RAM (FW01 loaded) | +| Rev.2 | 0x0202 | No | No | EEPROM | +| SkyWalker-1 | 0x0203 | No | No | EEPROM | +| SkyWalker CW3K | 0x0206 | No | No | EEPROM | + + + +## USB Control Transfer Protocol + +All vendor commands use USB control transfers with these common parameters: + +| Parameter | Value | +|-----------|-------| +| bmRequestType | `USB_TYPE_VENDOR` (0x40 for OUT, 0xC0 for IN) | +| Timeout | 2000 ms | +| Retry | Up to 3 attempts for IN operations if partial data received | +| Data buffer maximum | 80 bytes (kernel driver) | + +See [Vendor Commands](/usb/vendor-commands/) for the complete command reference. + +## Kernel Driver Modules + +The Linux kernel uses two modules for the SkyWalker-1: + +| Module | Function | +|--------|----------| +| `dvb_usb_gp8psk` | USB transport layer, device management, firmware loading | +| `gp8psk_fe` | DVB frontend operations (demodulation, tuning, signal status) | + + diff --git a/site/src/content/docs/usb/vendor-commands.mdx b/site/src/content/docs/usb/vendor-commands.mdx index 00b743b..3d518b5 100644 --- a/site/src/content/docs/usb/vendor-commands.mdx +++ b/site/src/content/docs/usb/vendor-commands.mdx @@ -1,191 +1,191 @@ ---- -title: Vendor Commands -description: Complete USB vendor command reference with bRequest codes, parameters, and firmware version compatibility. ---- - -import { Tabs, TabItem, Badge, Aside } from '@astrojs/starlight/components'; - -All vendor commands use USB control transfers with `USB_TYPE_VENDOR` (bmRequestType bit 6 set). The vendor command dispatcher at CODE:0056 validates `bRequest` in the range 0x80--0x9D (30 entries for v2.06/v2.13) or 0x80--0x9A (27 entries for Rev.2) and dispatches via an indexed jump table at CODE:0076. - - - - - - -## Stock Command Table (0x80--0x9D) - -| Cmd | Name | Dir | wValue | wIndex | wLength | Purpose | v2.06 | Rev.2 | v2.13 | -|-----|------|-----|--------|--------|---------|---------|-------|-------|-------| -| 0x80 | GET_8PSK_CONFIG | IN | 0 | 0 | 1 | Read [configuration status byte](/usb/config-status/) | | | | -| 0x81 | SET_8PSK_CONFIG | OUT | varies | 0 | 0 | Set config (reserved) | | | | -| 0x82 | (reserved) | -- | -- | -- | -- | Reserved | | | | -| 0x83 | I2C_WRITE | OUT | dev_addr | reg_addr | N | Write to I2C device | | | | -| 0x84 | I2C_READ | IN | dev_addr | reg_addr | N | Read from I2C device | | | | -| 0x85 | ARM_TRANSFER | OUT | 0/1 | 0 | 0 | Start (1) / stop (0) MPEG-2 stream | | | | -| 0x86 | TUNE_8PSK | OUT | 0 | 0 | 10 | Set [tuning parameters](/bcm4500/tuning-protocol/) | | | | -| 0x87 | GET_SIGNAL_STRENGTH | IN | 0 | 0 | 6 | Read [SNR and diagnostics](/bcm4500/signal-monitoring/) | | | | -| 0x88 | LOAD_BCM4500 | OUT | 1 | 0 | 0 | Initiate BCM4500 FW download | | | | -| 0x89 | BOOT_8PSK | IN | 0/1 | 0 | 1 | Power on (1) / off (0) demodulator | | | | -| 0x8A | START_INTERSIL | IN | 0/1 | 0 | 1 | Enable (1) / disable (0) LNB supply | | | | -| 0x8B | SET_LNB_VOLTAGE | OUT | 0/1 | 0 | 0 | 13V (0) or 18V (1) | | | | -| 0x8C | SET_22KHZ_TONE | OUT | 0/1 | 0 | 0 | Tone off (0) or on (1) | | | | -| 0x8D | SEND_DISEQC_COMMAND | OUT | msg[0] | 0 | len | DiSEqC message or tone burst | | | | -| 0x8E | SET_DVB_MODE | OUT | 1 | 0 | 0 | Enable DVB-S mode | | | | -| 0x8F | SET_DN_SWITCH | OUT | cmd7bit | 0 | 0 | Legacy Dish Network switch protocol | | | | -| 0x90 | GET_SIGNAL_LOCK | IN | 0 | 0 | 1 | Read [signal lock status](/bcm4500/signal-monitoring/) | | | | -| 0x92 | GET_FW_VERS | IN | 0 | 0 | 6 | Read firmware version + build date | | | | -| 0x93 | GET_SERIAL_NUMBER | IN | 0 | 0 | 4 | Read 4-byte serial from EEPROM | | | | -| 0x94 | USE_EXTRA_VOLT | OUT | 0/1 | 0 | 0 | Enable +1V LNB boost (14V/19V) | | | | -| 0x95 | GET_FPGA_VERS | IN | 0 | 0 | 1 | Read EEPROM hardware/platform ID | | | | - -### Detailed Parameter Formats - -**0x87 GET_SIGNAL_STRENGTH**: Returns 6 bytes. Bytes 0--1 contain a 16-bit SNR value (little-endian, dBu x 256 units). Bytes 2--5 are reserved/diagnostic BCM4500 registers. Version differences: v2.06 polls 3 registers (0xA2, 0xA8, 0xA4) up to 6 times; v2.13 consolidates to 1 register with a simplified poll. - -**0x8D SEND_DISEQC_COMMAND**: When `wLength > 0`, the payload is a standard DiSEqC message (3--6 bytes) with `wValue` set to `msg[0]` (framing byte, typically 0xE0 or 0xE1). When `wLength == 0` and `wValue == 0`, tone burst A is sent. When `wLength == 0` and `wValue != 0`, tone burst B is sent. - -**0x8F SET_DN_SWITCH**: `wValue` carries a 7-bit Dish Network switch command (LSB-first), bit-banged on GPIO P0.4 with specific timing. The 8th bit (0x80) of the original switch command selects LNB voltage and is sent separately via SET_LNB_VOLTAGE. - -**0x92 GET_FW_VERS**: Returns 6 bytes of hardcoded constants: - -```c title="GET_FW_VERS Response Format" -Byte 0: version minor_minor (e.g., 0x04) -Byte 1: version minor (e.g., 0x06) -Byte 2: version major (e.g., 0x02) -Byte 3: build day (e.g., 0x0D = 13) -Byte 4: build month (e.g., 0x07 = July) -Byte 5: build year - 2000 (e.g., 0x07 = 2007) - -Full version = byte[2] << 16 | byte[1] << 8 | byte[0] -Build date = (2000 + byte[5]) / byte[4] / byte[3] -``` - -**0x93 GET_SERIAL_NUMBER**: Returns 4 bytes read from I2C EEPROM at device address 0x51 (7-bit), extracted at 8-bit intervals using a shift/rotate routine. - -**0x94 USE_EXTRA_VOLT**: `wValue=1` writes 0x6A to XRAM 0xE0B6; `wValue=0` writes 0x62. The difference is bit 3 (0x08), which controls the voltage boost on the LNB power regulator. - -**0x95 GET_FPGA_VERS**: Reads from I2C EEPROM at 0x51. Despite the name, there is no FPGA on the SkyWalker-1 -- this returns a hardware platform ID. v2.06 reads EEPROM offset 0x31 (2 bytes); v2.13/Rev.2 read offset 0x00 (1 byte). - - - - -## Debug Commands (0x91, 0x96--0x98) - -These commands are not used by any driver (Linux or Windows). They appear to be manufacturing/debug interfaces. - -| Cmd | Name | Dir | wValue | wLength | Purpose | v2.06 | Rev.2 | v2.13 | -|-----|------|-----|--------|---------|---------|-------|-------|-------| -| 0x91 | I2C_ADDR_ADJUST | IN | 0=dec, 1=inc | 1 | Inc/dec internal IRAM counter | | | | -| 0x96 | SET_LNB_GPIO_MODE | OUT | 0/1 | 0 | Configure LNB GPIO output enables | | | | -| 0x97 | SET_GPIO_PINS | OUT | bitmap | 0 | Direct write to LNB GPIO pins | | | | -| 0x98 | GET_GPIO_STATUS | IN | 0 | 1 | Read LNB feedback GPIO pin | | | | - -### 0x91 I2C_ADDR_ADJUST - -Increments (`wValue != 0`) or decrements (`wValue == 0`) an internal IRAM counter and returns its current value (1 byte). The counter lives at IRAM 0x66 (v2.06) or IRAM 0x18 (v2.13/Rev.2). Likely used for I2C address or tuner register index adjustment during development. - -### 0x96 SET_LNB_GPIO_MODE - -Configures GPIO output enable registers for the LNB voltage regulator hardware: - -| Mode | v2.06/v2.13 | Rev.2 | -|------|-------------|-------| -| Default (wValue=0) | OEB=0xF0 | OEB=0xE7, OEA=0x9E | -| Active (wValue=1) | IOB=(IOB & 0xF7) OR 0x06; OEB=0xFE | IOB.4 clear; P0.6, P0.0 set; OEA OR= 0x41 | - -### 0x97 SET_GPIO_PINS - -Direct GPIO pin write for LNB control: - -| wValue Bit | v2.06/v2.13 Target | Rev.2 Target | -|-----------|-------------------|-------------| -| bit 1 | IOB.1 (Port B) | P0.6 (Port A) | -| bit 2 | IOB.2 (Port B) | P0.0 (Port A) | -| bit 3 | IOB.3 (Port B) | IOB.4 (Port B) | - -### 0x98 GET_GPIO_STATUS - -Returns 1 byte (0 or 1) from a single GPIO input pin -- likely an LNB overcurrent or power-good feedback signal: - -| Version | Pin Read | -|---------|----------| -| v2.06/v2.13 | IOB.0 (Port B bit 0) | -| Rev.2 | P0.5 (Port A bit 5) | - - - - -## Extended Commands (0x99--0x9D) - -| Cmd | Name | Dir | wValue | wLength | Purpose | v2.06 | Rev.2 | v2.13 | -|-----|------|-----|--------|---------|---------|-------|-------|-------| -| 0x99 | GET_DEMOD_STATUS | IN | 0 | 1 | Read BCM4500 register 0xF9 | | | | -| 0x9A | INIT_DEMOD | OUT | 0 | 0 | Trigger demod re-init (3 attempts) | | | | -| 0x9B | (reserved) | -- | -- | -- | Reserved | | | | -| 0x9C | DELAY_COMMAND | OUT | delay | 0 | Host-controlled tuning delay + poll | | | | -| 0x9D | CW3K_INIT / SET_MODE_FLAG | OUT | 0/1 | 0 | CW3K init or conditional demod reset | | | | - -### Driver Usage Notes - -- The Linux driver only sends LOAD_BCM4500 (0x88) for Rev.1 Warm (PID 0x0201). On SkyWalker-1, `bm8pskFW_Loaded` is already set and 0x88 routes to STALL. -- The Linux driver only sends CW3K_INIT (0x9D) for SkyWalker CW3K (PID 0x0206). -- Rev.2 supports only commands 0x80--0x9A (27 entries). Commands 0x9B--0x9D are out of range and produce undefined behavior. - - - - -## Custom Firmware Commands (0xB0--0xB9) - -Commands added in custom firmware v3.01.0--v3.02.0 for development, diagnostics, and signal monitoring: - -| Cmd | Name | Dir | wValue | wIndex | wLength | Purpose | -|-----|------|-----|--------|--------|---------|---------| -| 0xB0 | SPECTRUM_SWEEP | OUT | 0 | 0 | 10 | Step through freq range, read SNR at each step | -| 0xB1 | RAW_DEMOD_READ | IN | reg | 0 | 1 | Read BCM4500 indirect register | -| 0xB2 | RAW_DEMOD_WRITE | OUT | reg | data | 0 | Write BCM4500 indirect register | -| 0xB3 | BLIND_SCAN | OUT | 0 | 0 | 16 | Try symbol rates at given freq, report lock | -| 0xB4 | I2C_BUS_SCAN | IN | 0 | 0 | 16 | Probe all 7-bit addresses, return 16-byte bitmap | -| 0xB5 | I2C_RAW_READ | IN | addr7 | reg | N | Combined write-read from any I2C device | -| 0xB6 | I2C_DIAG | IN | page | 0 | 8 | Step-by-step indirect register diagnostic | - -### Parameter Formats - -**0xB0 SPECTRUM_SWEEP**: 10-byte EP0 payload: `[start_freq(u32 LE kHz), stop_freq(u32 LE kHz), step_khz(u16 LE)]`. Programs BCM4500 at each frequency step, reads SNR, packs u16 LE results into EP2 bulk FIFO. - -**0xB3 BLIND_SCAN**: 16-byte EP0 payload: `[freq_khz(u32 LE), sr_min(u32 LE sps), sr_max(u32 LE sps), sr_step(u32 LE sps)]`. Returns 8 bytes on lock `[freq_khz(4) + sr_locked(4)]` or 1 byte 0x00 if no lock found. - -**0xB4 I2C_BUS_SCAN**: Returns a 16-byte bitmap (128 bits for addresses 0x00--0x77). Each bit set = ACK received at that 7-bit address. - -### Signal Monitoring Commands (v3.02.0) - -| Cmd | Name | Dir | wValue | wIndex | wLength | Purpose | -|-----|------|-----|--------|--------|---------|---------| -| 0xB7 | SIGNAL_MONITOR | IN | 0 | 0 | 8 | Fast combined signal read: SNR(2) + AGC1(2) + AGC2(2) + lock(1) + status(1) | -| 0xB8 | TUNE_MONITOR | OUT+IN | dwell_ms | 0 | 10 | Tune + dwell + signal read. OUT=tune payload, IN=result | -| 0xB9 | MULTI_REG_READ | IN | start_reg | count | count | Batch read of contiguous BCM4500 indirect registers (1--64) | - -**0xB7 SIGNAL_MONITOR**: Returns 8 bytes in one transfer: `[snr_lo, snr_hi, agc1_lo, agc1_hi, agc2_lo, agc2_hi, lock_0xA4, status_0xA2]`. SNR is from indirect regs 0x00--0x01 (dBu × 256). AGC values from regs 0x02--0x05. Lock/status from direct registers. Enables ~50 Hz polling. - -**0xB8 TUNE_MONITOR**: Two-phase protocol sharing bRequest=0xB8. Phase 1 (OUT, bmRequestType=0x40): 10-byte EP0 payload in TUNE_8PSK format, `wValue=dwell_ms` (1--255). Firmware tunes, waits dwell_ms, reads signal into static buffer. Phase 2 (IN, bmRequestType=0xC0): returns 10-byte result `[snr(6), lock(1), status(1), dwell_echo(2)]`. The OUT phase blocks for the full dwell time. - -**0xB9 MULTI_REG_READ**: `wValue` = start register, `wIndex` = count (1--64). Returns count bytes, one per register, in a single USB transfer. Each register still requires an individual I2C indirect read internally, but saves 63 USB round-trips for register exploration. - - - - -## Vendor Command Dispatch Mechanism - -The dispatch logic at CODE:0056 (identical address across all stock versions): - -``` -1. Check bmRequestType bit 6 -> vendor request? -2. Read bRequest from SETUPDAT[1] -3. Subtract 0x80 (command base offset) -4. Compare against maximum: < 0x1E (v2.06/v2.13) or < 0x1B (Rev.2) -5. If in range: double the index (2 bytes per AJMP) -> JMP @A+DPTR -6. If out of range: route to STALL handler -``` - -The jump table at CODE:0076 contains 2-byte AJMP instruction targets, one per command from 0x80 upward. +--- +title: Vendor Commands +description: Complete USB vendor command reference with bRequest codes, parameters, and firmware version compatibility. +--- + +import { Tabs, TabItem, Badge, Aside } from '@astrojs/starlight/components'; + +All vendor commands use USB control transfers with `USB_TYPE_VENDOR` (bmRequestType bit 6 set). The vendor command dispatcher at CODE:0056 validates `bRequest` in the range 0x80--0x9D (30 entries for v2.06/v2.13) or 0x80--0x9A (27 entries for Rev.2) and dispatches via an indexed jump table at CODE:0076. + + + + + + +## Stock Command Table (0x80--0x9D) + +| Cmd | Name | Dir | wValue | wIndex | wLength | Purpose | v2.06 | Rev.2 | v2.13 | +|-----|------|-----|--------|--------|---------|---------|-------|-------|-------| +| 0x80 | GET_8PSK_CONFIG | IN | 0 | 0 | 1 | Read [configuration status byte](/usb/config-status/) | | | | +| 0x81 | SET_8PSK_CONFIG | OUT | varies | 0 | 0 | Set config (reserved) | | | | +| 0x82 | (reserved) | -- | -- | -- | -- | Reserved | | | | +| 0x83 | I2C_WRITE | OUT | dev_addr | reg_addr | N | Write to I2C device | | | | +| 0x84 | I2C_READ | IN | dev_addr | reg_addr | N | Read from I2C device | | | | +| 0x85 | ARM_TRANSFER | OUT | 0/1 | 0 | 0 | Start (1) / stop (0) MPEG-2 stream | | | | +| 0x86 | TUNE_8PSK | OUT | 0 | 0 | 10 | Set [tuning parameters](/bcm4500/tuning-protocol/) | | | | +| 0x87 | GET_SIGNAL_STRENGTH | IN | 0 | 0 | 6 | Read [SNR and diagnostics](/bcm4500/signal-monitoring/) | | | | +| 0x88 | LOAD_BCM4500 | OUT | 1 | 0 | 0 | Initiate BCM4500 FW download | | | | +| 0x89 | BOOT_8PSK | IN | 0/1 | 0 | 1 | Power on (1) / off (0) demodulator | | | | +| 0x8A | START_INTERSIL | IN | 0/1 | 0 | 1 | Enable (1) / disable (0) LNB supply | | | | +| 0x8B | SET_LNB_VOLTAGE | OUT | 0/1 | 0 | 0 | 13V (0) or 18V (1) | | | | +| 0x8C | SET_22KHZ_TONE | OUT | 0/1 | 0 | 0 | Tone off (0) or on (1) | | | | +| 0x8D | SEND_DISEQC_COMMAND | OUT | msg[0] | 0 | len | DiSEqC message or tone burst | | | | +| 0x8E | SET_DVB_MODE | OUT | 1 | 0 | 0 | Enable DVB-S mode | | | | +| 0x8F | SET_DN_SWITCH | OUT | cmd7bit | 0 | 0 | Legacy Dish Network switch protocol | | | | +| 0x90 | GET_SIGNAL_LOCK | IN | 0 | 0 | 1 | Read [signal lock status](/bcm4500/signal-monitoring/) | | | | +| 0x92 | GET_FW_VERS | IN | 0 | 0 | 6 | Read firmware version + build date | | | | +| 0x93 | GET_SERIAL_NUMBER | IN | 0 | 0 | 4 | Read 4-byte serial from EEPROM | | | | +| 0x94 | USE_EXTRA_VOLT | OUT | 0/1 | 0 | 0 | Enable +1V LNB boost (14V/19V) | | | | +| 0x95 | GET_FPGA_VERS | IN | 0 | 0 | 1 | Read EEPROM hardware/platform ID | | | | + +### Detailed Parameter Formats + +**0x87 GET_SIGNAL_STRENGTH**: Returns 6 bytes. Bytes 0--1 contain a 16-bit SNR value (little-endian, dBu x 256 units). Bytes 2--5 are reserved/diagnostic BCM4500 registers. Version differences: v2.06 polls 3 registers (0xA2, 0xA8, 0xA4) up to 6 times; v2.13 consolidates to 1 register with a simplified poll. + +**0x8D SEND_DISEQC_COMMAND**: When `wLength > 0`, the payload is a standard DiSEqC message (3--6 bytes) with `wValue` set to `msg[0]` (framing byte, typically 0xE0 or 0xE1). When `wLength == 0` and `wValue == 0`, tone burst A is sent. When `wLength == 0` and `wValue != 0`, tone burst B is sent. + +**0x8F SET_DN_SWITCH**: `wValue` carries a 7-bit Dish Network switch command (LSB-first), bit-banged on GPIO P0.4 with specific timing. The 8th bit (0x80) of the original switch command selects LNB voltage and is sent separately via SET_LNB_VOLTAGE. + +**0x92 GET_FW_VERS**: Returns 6 bytes of hardcoded constants: + +```c title="GET_FW_VERS Response Format" +Byte 0: version minor_minor (e.g., 0x04) +Byte 1: version minor (e.g., 0x06) +Byte 2: version major (e.g., 0x02) +Byte 3: build day (e.g., 0x0D = 13) +Byte 4: build month (e.g., 0x07 = July) +Byte 5: build year - 2000 (e.g., 0x07 = 2007) + +Full version = byte[2] << 16 | byte[1] << 8 | byte[0] +Build date = (2000 + byte[5]) / byte[4] / byte[3] +``` + +**0x93 GET_SERIAL_NUMBER**: Returns 4 bytes read from I2C EEPROM at device address 0x51 (7-bit), extracted at 8-bit intervals using a shift/rotate routine. + +**0x94 USE_EXTRA_VOLT**: `wValue=1` writes 0x6A to XRAM 0xE0B6; `wValue=0` writes 0x62. The difference is bit 3 (0x08), which controls the voltage boost on the LNB power regulator. + +**0x95 GET_FPGA_VERS**: Reads from I2C EEPROM at 0x51. Despite the name, there is no FPGA on the SkyWalker-1 -- this returns a hardware platform ID. v2.06 reads EEPROM offset 0x31 (2 bytes); v2.13/Rev.2 read offset 0x00 (1 byte). + + + + +## Debug Commands (0x91, 0x96--0x98) + +These commands are not used by any driver (Linux or Windows). They appear to be manufacturing/debug interfaces. + +| Cmd | Name | Dir | wValue | wLength | Purpose | v2.06 | Rev.2 | v2.13 | +|-----|------|-----|--------|---------|---------|-------|-------|-------| +| 0x91 | I2C_ADDR_ADJUST | IN | 0=dec, 1=inc | 1 | Inc/dec internal IRAM counter | | | | +| 0x96 | SET_LNB_GPIO_MODE | OUT | 0/1 | 0 | Configure LNB GPIO output enables | | | | +| 0x97 | SET_GPIO_PINS | OUT | bitmap | 0 | Direct write to LNB GPIO pins | | | | +| 0x98 | GET_GPIO_STATUS | IN | 0 | 1 | Read LNB feedback GPIO pin | | | | + +### 0x91 I2C_ADDR_ADJUST + +Increments (`wValue != 0`) or decrements (`wValue == 0`) an internal IRAM counter and returns its current value (1 byte). The counter lives at IRAM 0x66 (v2.06) or IRAM 0x18 (v2.13/Rev.2). Likely used for I2C address or tuner register index adjustment during development. + +### 0x96 SET_LNB_GPIO_MODE + +Configures GPIO output enable registers for the LNB voltage regulator hardware: + +| Mode | v2.06/v2.13 | Rev.2 | +|------|-------------|-------| +| Default (wValue=0) | OEB=0xF0 | OEB=0xE7, OEA=0x9E | +| Active (wValue=1) | IOB=(IOB & 0xF7) OR 0x06; OEB=0xFE | IOB.4 clear; P0.6, P0.0 set; OEA OR= 0x41 | + +### 0x97 SET_GPIO_PINS + +Direct GPIO pin write for LNB control: + +| wValue Bit | v2.06/v2.13 Target | Rev.2 Target | +|-----------|-------------------|-------------| +| bit 1 | IOB.1 (Port B) | P0.6 (Port A) | +| bit 2 | IOB.2 (Port B) | P0.0 (Port A) | +| bit 3 | IOB.3 (Port B) | IOB.4 (Port B) | + +### 0x98 GET_GPIO_STATUS + +Returns 1 byte (0 or 1) from a single GPIO input pin -- likely an LNB overcurrent or power-good feedback signal: + +| Version | Pin Read | +|---------|----------| +| v2.06/v2.13 | IOB.0 (Port B bit 0) | +| Rev.2 | P0.5 (Port A bit 5) | + + + + +## Extended Commands (0x99--0x9D) + +| Cmd | Name | Dir | wValue | wLength | Purpose | v2.06 | Rev.2 | v2.13 | +|-----|------|-----|--------|---------|---------|-------|-------|-------| +| 0x99 | GET_DEMOD_STATUS | IN | 0 | 1 | Read BCM4500 register 0xF9 | | | | +| 0x9A | INIT_DEMOD | OUT | 0 | 0 | Trigger demod re-init (3 attempts) | | | | +| 0x9B | (reserved) | -- | -- | -- | Reserved | | | | +| 0x9C | DELAY_COMMAND | OUT | delay | 0 | Host-controlled tuning delay + poll | | | | +| 0x9D | CW3K_INIT / SET_MODE_FLAG | OUT | 0/1 | 0 | CW3K init or conditional demod reset | | | | + +### Driver Usage Notes + +- The Linux driver only sends LOAD_BCM4500 (0x88) for Rev.1 Warm (PID 0x0201). On SkyWalker-1, `bm8pskFW_Loaded` is already set and 0x88 routes to STALL. +- The Linux driver only sends CW3K_INIT (0x9D) for SkyWalker CW3K (PID 0x0206). +- Rev.2 supports only commands 0x80--0x9A (27 entries). Commands 0x9B--0x9D are out of range and produce undefined behavior. + + + + +## Custom Firmware Commands (0xB0--0xB9) + +Commands added in custom firmware v3.01.0--v3.02.0 for development, diagnostics, and signal monitoring: + +| Cmd | Name | Dir | wValue | wIndex | wLength | Purpose | +|-----|------|-----|--------|--------|---------|---------| +| 0xB0 | SPECTRUM_SWEEP | OUT | 0 | 0 | 10 | Step through freq range, read SNR at each step | +| 0xB1 | RAW_DEMOD_READ | IN | reg | 0 | 1 | Read BCM4500 indirect register | +| 0xB2 | RAW_DEMOD_WRITE | OUT | reg | data | 0 | Write BCM4500 indirect register | +| 0xB3 | BLIND_SCAN | OUT | 0 | 0 | 16 | Try symbol rates at given freq, report lock | +| 0xB4 | I2C_BUS_SCAN | IN | 0 | 0 | 16 | Probe all 7-bit addresses, return 16-byte bitmap | +| 0xB5 | I2C_RAW_READ | IN | addr7 | reg | N | Combined write-read from any I2C device | +| 0xB6 | I2C_DIAG | IN | page | 0 | 8 | Step-by-step indirect register diagnostic | + +### Parameter Formats + +**0xB0 SPECTRUM_SWEEP**: 10-byte EP0 payload: `[start_freq(u32 LE kHz), stop_freq(u32 LE kHz), step_khz(u16 LE)]`. Programs BCM4500 at each frequency step, reads SNR, packs u16 LE results into EP2 bulk FIFO. + +**0xB3 BLIND_SCAN**: 16-byte EP0 payload: `[freq_khz(u32 LE), sr_min(u32 LE sps), sr_max(u32 LE sps), sr_step(u32 LE sps)]`. Returns 8 bytes on lock `[freq_khz(4) + sr_locked(4)]` or 1 byte 0x00 if no lock found. + +**0xB4 I2C_BUS_SCAN**: Returns a 16-byte bitmap (128 bits for addresses 0x00--0x77). Each bit set = ACK received at that 7-bit address. + +### Signal Monitoring Commands (v3.02.0) + +| Cmd | Name | Dir | wValue | wIndex | wLength | Purpose | +|-----|------|-----|--------|--------|---------|---------| +| 0xB7 | SIGNAL_MONITOR | IN | 0 | 0 | 8 | Fast combined signal read: SNR(2) + AGC1(2) + AGC2(2) + lock(1) + status(1) | +| 0xB8 | TUNE_MONITOR | OUT+IN | dwell_ms | 0 | 10 | Tune + dwell + signal read. OUT=tune payload, IN=result | +| 0xB9 | MULTI_REG_READ | IN | start_reg | count | count | Batch read of contiguous BCM4500 indirect registers (1--64) | + +**0xB7 SIGNAL_MONITOR**: Returns 8 bytes in one transfer: `[snr_lo, snr_hi, agc1_lo, agc1_hi, agc2_lo, agc2_hi, lock_0xA4, status_0xA2]`. SNR is from indirect regs 0x00--0x01 (dBu × 256). AGC values from regs 0x02--0x05. Lock/status from direct registers. Enables ~50 Hz polling. + +**0xB8 TUNE_MONITOR**: Two-phase protocol sharing bRequest=0xB8. Phase 1 (OUT, bmRequestType=0x40): 10-byte EP0 payload in TUNE_8PSK format, `wValue=dwell_ms` (1--255). Firmware tunes, waits dwell_ms, reads signal into static buffer. Phase 2 (IN, bmRequestType=0xC0): returns 10-byte result `[snr(6), lock(1), status(1), dwell_echo(2)]`. The OUT phase blocks for the full dwell time. + +**0xB9 MULTI_REG_READ**: `wValue` = start register, `wIndex` = count (1--64). Returns count bytes, one per register, in a single USB transfer. Each register still requires an individual I2C indirect read internally, but saves 63 USB round-trips for register exploration. + + + + +## Vendor Command Dispatch Mechanism + +The dispatch logic at CODE:0056 (identical address across all stock versions): + +``` +1. Check bmRequestType bit 6 -> vendor request? +2. Read bRequest from SETUPDAT[1] +3. Subtract 0x80 (command base offset) +4. Compare against maximum: < 0x1E (v2.06/v2.13) or < 0x1B (Rev.2) +5. If in range: double the index (2 bytes per AJMP) -> JMP @A+DPTR +6. If out of range: route to STALL handler +``` + +The jump table at CODE:0076 contains 2-byte AJMP instruction targets, one per command from 0x80 upward. diff --git a/site/src/styles/custom.css b/site/src/styles/custom.css index e634f0c..16fed39 100644 --- a/site/src/styles/custom.css +++ b/site/src/styles/custom.css @@ -1,69 +1,69 @@ -/* Teal/Steel Engineering Theme for SkyWalker-1 Docs */ - -:root { - --sl-color-accent-low: #0d3b3b; - --sl-color-accent: #1a8a8a; - --sl-color-accent-high: #b8e6e6; - --sl-hue: 180; - --sl-color-white: #f0f4f5; - --sl-color-gray-1: #dde4e6; - --sl-color-gray-2: #b8c4c8; - --sl-color-gray-3: #7a8f96; - --sl-color-gray-4: #4a5c63; - --sl-color-gray-5: #2a3a40; - --sl-color-gray-6: #1a2a30; - --sl-color-black: #0e1519; -} - -:root[data-theme='light'] { - --sl-color-accent-low: #d0f0f0; - --sl-color-accent: #0d7377; - --sl-color-accent-high: #043d3f; - --sl-color-white: #1a2a30; - --sl-color-gray-1: #2a3a40; - --sl-color-gray-2: #4a5c63; - --sl-color-gray-3: #7a8f96; - --sl-color-gray-4: #b8c4c8; - --sl-color-gray-5: #dde4e6; - --sl-color-gray-6: #eef2f3; - --sl-color-black: #f5f8f9; -} - -/* Ensure link colors use teal */ -:root { - --sl-color-text-accent: var(--sl-color-accent); -} - -/* Table styling for hardware register tables */ -table { - font-size: 0.9rem; -} - -th { - background-color: var(--sl-color-gray-6); -} - -/* Code block enhancement */ -.expressive-code { - --ec-brdCol: var(--sl-color-gray-5); -} - -/* Badge color overrides for hardware status */ -.sl-badge--success { - --sl-color-bg-badge: #0d5e3a; - color: #a8e6cf; -} - -.sl-badge--danger { - --sl-color-bg-badge: #5e1a0d; - color: #e6b8a8; -} - -/* Mobile horizontal scroll for wide tables */ -@media (max-width: 50rem) { - table { - display: block; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } -} +/* Teal/Steel Engineering Theme for SkyWalker-1 Docs */ + +:root { + --sl-color-accent-low: #0d3b3b; + --sl-color-accent: #1a8a8a; + --sl-color-accent-high: #b8e6e6; + --sl-hue: 180; + --sl-color-white: #f0f4f5; + --sl-color-gray-1: #dde4e6; + --sl-color-gray-2: #b8c4c8; + --sl-color-gray-3: #7a8f96; + --sl-color-gray-4: #4a5c63; + --sl-color-gray-5: #2a3a40; + --sl-color-gray-6: #1a2a30; + --sl-color-black: #0e1519; +} + +:root[data-theme='light'] { + --sl-color-accent-low: #d0f0f0; + --sl-color-accent: #0d7377; + --sl-color-accent-high: #043d3f; + --sl-color-white: #1a2a30; + --sl-color-gray-1: #2a3a40; + --sl-color-gray-2: #4a5c63; + --sl-color-gray-3: #7a8f96; + --sl-color-gray-4: #b8c4c8; + --sl-color-gray-5: #dde4e6; + --sl-color-gray-6: #eef2f3; + --sl-color-black: #f5f8f9; +} + +/* Ensure link colors use teal */ +:root { + --sl-color-text-accent: var(--sl-color-accent); +} + +/* Table styling for hardware register tables */ +table { + font-size: 0.9rem; +} + +th { + background-color: var(--sl-color-gray-6); +} + +/* Code block enhancement */ +.expressive-code { + --ec-brdCol: var(--sl-color-gray-5); +} + +/* Badge color overrides for hardware status */ +.sl-badge--success { + --sl-color-bg-badge: #0d5e3a; + color: #a8e6cf; +} + +.sl-badge--danger { + --sl-color-bg-badge: #5e1a0d; + color: #e6b8a8; +} + +/* Mobile horizontal scroll for wide tables */ +@media (max-width: 50rem) { + table { + display: block; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} diff --git a/site/tsconfig.json b/site/tsconfig.json index 48285b0..d78f81e 100644 --- a/site/tsconfig.json +++ b/site/tsconfig.json @@ -1,3 +1,3 @@ -{ - "extends": "astro/tsconfigs/base" -} +{ + "extends": "astro/tsconfigs/base" +} diff --git a/skywalker1-hardware-reference.md b/skywalker1-hardware-reference.md index 0ec14a3..916510d 100644 --- a/skywalker1-hardware-reference.md +++ b/skywalker1-hardware-reference.md @@ -1,811 +1,811 @@ -# Genpix SkyWalker-1 Hardware Reference - -Consolidated technical reference for the Genpix SkyWalker-1 DVB-S USB 2.0 satellite receiver, derived from Linux kernel driver analysis, firmware reverse engineering (Ghidra), and Windows BDA driver source review. - ---- - -## 1. Overview - -The Genpix SkyWalker-1 is a standalone USB 2.0 DVB-S satellite receiver built around two ICs: - -- **Cypress CY7C68013A** (FX2LP) -- USB 2.0 Hi-Speed microcontroller (8051 core, 48 MHz) -- **Broadcom BCM4500** -- DVB-S / 8PSK satellite demodulator - -The FX2 handles USB communication, LNB control, DiSEqC signaling, and orchestrates tuning via I2C commands to the BCM4500. The BCM4500 performs RF demodulation, FEC decoding, and outputs an MPEG-2 transport stream on an 8-bit parallel bus. The GPIF engine inside the FX2 transfers the transport stream directly into a USB bulk endpoint with zero firmware intervention in the data path. - -### Supported Modulations - -| Index | Modulation | Constant | -|-------|-----------|----------| -| 0 | DVB-S QPSK | `ADV_MOD_DVB_QPSK` | -| 1 | Turbo-coded QPSK | `ADV_MOD_TURBO_QPSK` | -| 2 | Turbo-coded 8PSK | `ADV_MOD_TURBO_8PSK` | -| 3 | Turbo-coded 16QAM | `ADV_MOD_TURBO_16QAM` | -| 4 | Digicipher II Combo | `ADV_MOD_DCII_C_QPSK` | -| 5 | Digicipher II I-stream (split) | `ADV_MOD_DCII_I_QPSK` | -| 6 | Digicipher II Q-stream (split) | `ADV_MOD_DCII_Q_QPSK` | -| 7 | Digicipher II Offset QPSK | `ADV_MOD_DCII_C_OQPSK` | -| 8 | DSS QPSK | `ADV_MOD_DSS_QPSK` | -| 9 | DVB-S BPSK | `ADV_MOD_DVB_BPSK` | - -DVB-S2 is **not** supported (incompatible FEC architecture). - -### RF Specifications - -| Parameter | Value | -|-----------|-------| -| IF frequency range | 950 -- 2150 MHz | -| Symbol rate | 256 Ksps -- 30 Msps | -| Input connector | IEC F-type female | -| LNB voltage | 13/18V (or 14/19V with USE_EXTRA_VOLT) | -| LNB current | 450 mA continuous / 750 mA burst | -| Switch control | 22 kHz, Tone Burst, DiSEqC 1.0/1.2, Legacy Dish Network | - ---- - -## 2. USB Interface - -### VID/PID Table - -All Genpix products share VID `0x09C0`: - -| PID | Product | cold_ids | warm_ids | Kernel Module | Notes | -|-----|---------|----------|----------|---------------|-------| -| 0x0200 | 8PSK-to-USB2 Rev.1 Cold | Yes | No | gp8psk | Requires FW01 upload to RAM | -| 0x0201 | 8PSK-to-USB2 Rev.1 Warm | No | Yes | gp8psk | Requires FW02 (BCM4500) | -| 0x0202 | 8PSK-to-USB2 Rev.2 | No | Yes | gp8psk | Boots from EEPROM | -| 0x0203 | **SkyWalker-1** | No | Yes | gp8psk | Boots from EEPROM | -| 0x0204 | SkyWalker-1 (alternate) | No | Yes | gp8psk | Boots from EEPROM | -| 0x0205 | SkyWalker-2 | -- | -- | -- | Not in kernel 6.16.5 | -| 0x0206 | SkyWalker CW3K | No | Yes | gp8psk | Requires CW3K_INIT (0x9D) | - -PID 0x0203 was added to the kernel device table after v6.6.1. - -### USB Endpoints and Streaming Properties - -| Property | Value | -|----------|-------| -| Control endpoint | EP0 (default, vendor requests) | -| Bulk IN endpoint | EP2 (0x82) -- MPEG-2 transport stream | -| Generic bulk CTRL endpoint | 0x01 (BCM4500 FW02 upload only) | -| URB count | 7 | -| URB buffer size | 8192 bytes each | -| Stream type | USB_BULK | -| FX2 controller type | CYPRESS_FX2 | - -### Warm Boot Behavior - -The SkyWalker-1 (PID 0x0203) enumerates directly as a "warm" device. The DVB-USB framework skips firmware download when `cold_ids` is NULL. No host-side firmware files (`dvb-usb-gp8psk-01.fw` or `dvb-usb-gp8psk-02.fw`) are required. These files were never open-sourced or included in the `linux-firmware` package. - -| Device | Needs FW01? | Needs FW02? | Boot Source | -|--------|-------------|-------------|-------------| -| Rev.1 Cold (0x0200) | Yes | -- | RAM (empty) | -| Rev.1 Warm (0x0201) | No | Yes | RAM (FW01 loaded) | -| Rev.2 (0x0202) | No | No | EEPROM | -| SkyWalker-1 (0x0203) | No | No | EEPROM | -| SkyWalker CW3K (0x0206) | No | No | EEPROM | - ---- - -## 3. Complete Vendor Command Reference - -All vendor commands use USB control transfers: -- **USB Type**: `USB_TYPE_VENDOR` -- **Timeout**: 2000 ms -- **Retry**: Up to 3 attempts for IN operations if partial data received -- **Data buffer maximum**: 80 bytes (kernel driver) - -### 3.1 Command Table - -The vendor command dispatcher at CODE:0056 validates `bRequest` in the range 0x80--0x9D (30 entries) and dispatches via an indexed jump table at CODE:0076. Rev.2 supports only 0x80--0x9A (27 entries). - -| Cmd | Name | Dir | wValue | wIndex | wLength | Purpose | Linux | Windows | v2.06 | Rev.2 | v2.13 | -|-----|------|-----|--------|--------|---------|---------|-------|---------|-------|-------|-------| -| 0x80 | GET_8PSK_CONFIG | IN | 0 | 0 | 1 | Read configuration status byte | Yes | Yes | OK | OK | OK | -| 0x81 | SET_8PSK_CONFIG | OUT | varies | 0 | 0 | Set config (reserved) | No | No | STALL | STALL | STALL | -| 0x82 | (reserved) | -- | -- | -- | -- | Reserved | No | No | STALL | STALL | STALL | -| 0x83 | I2C_WRITE | OUT | dev_addr | reg_addr | N | Write to I2C device | Yes | Yes | OK | OK | OK | -| 0x84 | I2C_READ | IN | dev_addr | reg_addr | N | Read from I2C device | Yes | Yes | OK | OK | OK | -| 0x85 | ARM_TRANSFER | OUT | 0/1 | 0 | 0 | Start (1) / stop (0) MPEG-2 stream | Yes | Yes | OK | OK | OK | -| 0x86 | TUNE_8PSK | OUT | 0 | 0 | 10 | Set tuning parameters (see Section 6) | Yes | Yes | OK | OK | OK | -| 0x87 | GET_SIGNAL_STRENGTH | IN | 0 | 0 | 6 | Read SNR and diagnostics | Yes | Yes | OK | OK | Changed | -| 0x88 | LOAD_BCM4500 | OUT | 1 | 0 | 0 | Initiate BCM4500 FW download | Yes* | No | STALL | STALL | STALL | -| 0x89 | BOOT_8PSK | IN | 0/1 | 0 | 1 | Power on (1) / off (0) demodulator | Yes | Yes | OK | OK | OK | -| 0x8A | START_INTERSIL | IN | 0/1 | 0 | 1 | Enable (1) / disable (0) LNB supply | Yes | Yes | OK | OK | OK | -| 0x8B | SET_LNB_VOLTAGE | OUT | 0/1 | 0 | 0 | 13V (0) or 18V (1) | Yes | Yes | OK | OK | OK | -| 0x8C | SET_22KHZ_TONE | OUT | 0/1 | 0 | 0 | Tone off (0) or on (1) | Yes | Yes | OK | OK | OK | -| 0x8D | SEND_DISEQC_COMMAND | OUT | msg[0] | 0 | len | DiSEqC message or tone burst | Yes | Yes | OK | OK | OK | -| 0x8E | SET_DVB_MODE | OUT | 1 | 0 | 0 | Enable DVB-S mode | Yes | No | STALL | STALL | STALL | -| 0x8F | SET_DN_SWITCH | OUT | cmd7bit | 0 | 0 | Legacy Dish Network switch protocol | Yes | Yes | OK | OK | OK | -| 0x90 | GET_SIGNAL_LOCK | IN | 0 | 0 | 1 | Read signal lock status | Yes | Yes | OK | OK | OK | -| 0x91 | I2C_ADDR_ADJUST | IN | 0/1 | 0 | 1 | Inc/dec internal IRAM counter (debug) | No | No | OK | OK | OK | -| 0x92 | GET_FW_VERS | IN | 0 | 0 | 6 | Read firmware version + build date | Yes | No | OK | OK | OK | -| 0x93 | GET_SERIAL_NUMBER | IN | 0 | 0 | 4 | Read 4-byte serial from I2C EEPROM | No | Yes | OK | OK | OK | -| 0x94 | USE_EXTRA_VOLT | OUT | 0/1 | 0 | 0 | Enable +1V LNB boost (14V/19V) | Yes | Yes | OK | OK | OK | -| 0x95 | GET_FPGA_VERS | IN | 0 | 0 | 1 | Read EEPROM hardware/platform ID | Yes | No | OK | OK | OK | -| 0x96 | SET_LNB_GPIO_MODE | OUT | 0/1 | 0 | 0 | Configure LNB GPIO output enables | No | No | OK | OK | OK | -| 0x97 | SET_GPIO_PINS | OUT | bitmap | 0 | 0 | Direct write to LNB GPIO pins | No | No | OK | OK | OK | -| 0x98 | GET_GPIO_STATUS | IN | 0 | 0 | 1 | Read LNB feedback GPIO pin | No | No | OK | OK | OK | -| 0x99 | GET_DEMOD_STATUS | IN | 0 | 0 | 1 | Read BCM4500 register 0xF9 | No | No | STALL | Proto | OK | -| 0x9A | INIT_DEMOD | OUT | 0 | 0 | 0 | Trigger demod re-init (3 attempts) | No | No | STALL | Proto | OK | -| 0x9B | (reserved) | -- | -- | -- | -- | Reserved | No | No | STALL | N/A | STALL | -| 0x9C | DELAY_COMMAND | OUT | delay | 0 | 0 | Host-controlled tuning delay + poll | No | No | STALL | N/A | OK | -| 0x9D | CW3K_INIT / SET_MODE_FLAG | OUT | 0/1 | 0 | 0 | CW3K init or conditional demod reset | Yes** | No | OK | N/A | Changed | - -\* Linux driver only sends LOAD_BCM4500 for Rev.1 Warm (PID 0x0201). On SkyWalker-1, `bm8pskFW_Loaded` is already set and 0x88 routes to STALL. - -\*\* Linux driver only sends CW3K_INIT for SkyWalker CW3K (PID 0x0206). - -**Status key**: OK = implemented, STALL = routes to stall handler (endpoint stall returned), Proto = partial/prototype implementation, N/A = command index out of range (Rev.2 only supports 0x80--0x9A), Changed = implementation differs between versions. - -### 3.2 Detailed Parameter Formats - -**0x8D SEND_DISEQC_COMMAND**: When `wLength > 0`, the payload is a standard DiSEqC message (3--6 bytes) with `wValue` set to `msg[0]` (framing byte, typically 0xE0 or 0xE1). When `wLength == 0` and `wValue == 0`, a tone burst A is sent. When `wLength == 0` and `wValue != 0`, a tone burst B is sent. - -**0x8F SET_DN_SWITCH**: `wValue` carries a 7-bit Dish Network switch command (LSB-first), bit-banged on GPIO P0.4 with specific timing. The 8th bit of the original switch command selects LNB voltage and is sent separately via SET_LNB_VOLTAGE. - -**0x92 GET_FW_VERS**: Returns 6 bytes: `[minor_minor, minor, major, day, month, year-2000]`. Full version = `major<<16 | minor<<8 | minor_minor`. Build date = `(2000+year)/month/day`. - -**0x93 GET_SERIAL_NUMBER**: Returns 4 bytes read from I2C EEPROM at device address 0x51 (7-bit), extracted at 8-bit intervals using a shift/rotate routine. - -**0x94 USE_EXTRA_VOLT**: `wValue=1` writes 0x6A to XRAM 0xE0B6; `wValue=0` writes 0x62. The difference is bit 3 (0x08), which controls the extra voltage boost on the LNB power regulator. - -**0x95 GET_FPGA_VERS**: Reads from I2C EEPROM at 0x51. Despite the name, there is no FPGA -- this returns the EEPROM-stored hardware platform ID. v2.06 reads offset 0x31 (2 bytes); v2.13/Rev.2 read offset 0x00 (1 byte). - -**0x96--0x98**: Internal debug commands for LNB GPIO control. 0x96 configures output enables (OEB/OEA), 0x97 writes pin states, 0x98 reads a feedback pin. GPIO assignments differ between v2.06/v2.13 (Port B) and Rev.2 (Port A + Port B). See Section 10 for pin details. - ---- - -## 4. Configuration Status Byte - -Returned by GET_8PSK_CONFIG (0x80). Stored in IRAM at version-dependent addresses. - -``` -Bit 7 (0x80): bmArmed - MPEG-2 stream transfer armed / GPIF active -Bit 6 (0x40): bmDCtuned - DC offset tuning complete (set for DCII modes) -Bit 5 (0x20): bmSEL18V - 18V LNB voltage selected (else 13V) -Bit 4 (0x10): bm22kHz - 22 kHz tone active -Bit 3 (0x08): bmDVBmode - DVB mode enabled -Bit 2 (0x04): bmIntersilOn - LNB power supply enabled -Bit 1 (0x02): bm8pskFW_Loaded - BCM4500 firmware loaded (always set on SkyWalker-1) -Bit 0 (0x01): bm8pskStarted - Device booted and running -``` - -| Version | IRAM Address | -|---------|-------------| -| v2.06 | 0x6D | -| Rev.2 v2.10.4 | 0x4E | -| v2.13 | 0x4F | - ---- - -## 5. Boot Sequence - -### 5.1 Two-Stage Firmware Architecture - -The FX2 supports two firmware sources: - -1. **Host RAM upload** (Rev.1 Cold only): The host writes 8051 code to FX2 RAM via USB 0xA0 vendor requests, using the built-in boot ROM. This requires `dvb-usb-gp8psk-01.fw` in binary hexline format. - -2. **EEPROM boot** (Rev.2, SkyWalker-1, CW3K): The FX2 boot ROM reads firmware from an external I2C EEPROM in Cypress C2 format on power-up. No host interaction needed. - -### 5.2 C2 EEPROM Format - -SkyWalker-1 firmware is stored in Cypress C2 IIC second-stage boot format: - -**Header (8 bytes):** - -| Offset | Size | Field | SkyWalker-1 Value | -|--------|------|-------|-------------------| -| 0 | 1 | Marker | 0xC2 (external memory, large code model) | -| 1 | 2 | VID (LE) | 0x09C0 | -| 3 | 2 | PID (LE) | 0x0203 | -| 5 | 2 | DID (LE) | 0x0000 | -| 7 | 1 | Config | 0x40 (400 kHz I2C) | - -**Code segments** follow: 2-byte length (BE) + 2-byte target address (BE) + data. Maximum segment size is 1023 bytes (FX2 I2C boot ROM buffer limit). All SkyWalker-1 variants use 10 segments. - -**Terminator**: 0x80xx (high bit set) + 2-byte entry point address (0xE600 = CPUCS). - -### 5.3 Power-On Boot Sequence - -``` -1. GET_8PSK_CONFIG (0x80) -- read config status byte - |-- Check bit 0: bm8pskStarted? - -2. If not started: - |-- BOOT_8PSK (0x89, wValue=1) - |-- GET_FW_VERS (0x92) -- read firmware version - -3. If bit 1 clear (bm8pskFW_Loaded): - |-- LOAD_BCM4500 (0x88) -- Rev.1 Warm only; STALLs on SkyWalker-1 - -4. If bit 2 clear (bmIntersilOn): - |-- START_INTERSIL (0x8A, wValue=1) -- enable LNB power supply - -5. SET_DVB_MODE (0x8E, wValue=1) -- STALLs on all SkyWalker-1 FW versions - -6. ARM_TRANSFER (0x85, wValue=0) -- abort any pending MPEG transfer - -7. Device ready for tuning -``` - -### 5.4 Firmware Version Identification - -The kernel reads firmware version on boot via GET_FW_VERS (0x92) and logs: -``` -gp8psk: FW Version = 2.06.4 (0x20604) Build 2007/07/13 -``` - -Kernel revision constants (from `gp8psk-fe.h`): -``` -GP8PSK_FW_REV1 = 0x020604 (v2.06.4) -GP8PSK_FW_REV2 = 0x020704 (v2.07.4) -``` - -If `fw_vers >= GP8PSK_FW_REV2`, the kernel enables Rev.2-specific code paths. The v2.10 and v2.13 firmwares are newer than either kernel constant. - ---- - -## 6. Tuning Protocol - -### 6.1 TUNE_8PSK Command Format (0x86) - -The host sends a 10-byte OUT payload via USB control transfer: - -``` -USB SETUP: bmRequestType=0x40, bRequest=0x86, wValue=0, wIndex=0, wLength=10 - -EP0BUF Layout: - Byte Content Encoding - ---- ------------------ ---------------- - [0] Symbol Rate byte 0 Little-endian LSB - [1] Symbol Rate byte 1 - [2] Symbol Rate byte 2 - [3] Symbol Rate byte 3 Little-endian MSB - [4] Frequency byte 0 Little-endian LSB - [5] Frequency byte 1 - [6] Frequency byte 2 - [7] Frequency byte 3 Little-endian MSB - [8] Modulation Type 0--9 (see Section 1 table) - [9] Inner FEC Rate Index into modulation-specific table -``` - -**Symbol Rate** is in samples per second (sps). The Windows driver multiplies ksps by 1000. - -**Frequency** is the IF frequency in kHz (950000--2150000), computed by the host as `(RF_freq - LO_freq) * multiplier`. - -### 6.2 Firmware EP0BUF Parsing - -The firmware reads the 10-byte payload from EP0BUF (XRAM 0xE740--0xE749) and stores: - -| Source | Destination | Notes | -|--------|-------------|-------| -| EP0BUF[8] (mod) | IRAM 0x4D | Direct copy | -| EP0BUF[9] (FEC) | IRAM 0x4F | Direct copy | -| EP0BUF[4--7] (freq) | XRAM 0xE0DB--0xE0DE | Byte-reversed (LE to BE) | -| EP0BUF[0--3] (SR) | XRAM 0xE0CB--0xE0CE | Byte-reversed (LE to BE) | - -The byte reversal converts host little-endian to BCM4500 big-endian so values can be written directly to the demodulator via I2C. - -### 6.3 Modulation Dispatch - -After parsing, the firmware validates the modulation type (< 10) and dispatches via a jump table to modulation-specific handlers. Each handler: - -1. Validates the FEC index against the maximum for that modulation -2. Looks up a preconfigured byte from an XRAM table -3. Writes configuration to four XRAM registers - -**FEC Rate Lookup Tables** (populated from the CODE-space init table at boot): - -| XRAM Base | Modulation | Max FEC Index | Notes | -|-----------|-----------|---------------|-------| -| 0xE0F9 | DVB-S QPSK, DSS, BPSK | 7 | Standard Viterbi rates (1/2, 2/3, 3/4, 5/6, 7/8, auto, none) | -| 0xE0B7 | Turbo QPSK | 5 | Turbo code rates | -| 0xE0B1 | Turbo 8PSK | 5 | Turbo code rates | -| 0xE0BC | Turbo 16QAM | 1 | Single code rate | -| 0xE0BD | DCII (all variants) | 9 | Combined code rate + modulation | - -**BCM4500 XRAM Configuration after dispatch:** - -| XRAM Addr | Register | DVB-S QPSK | Turbo (Q/8/16) | DCII | DSS/BPSK | -|-----------|----------|-----------|---------------|------|----------| -| 0xE0EB | FEC Code Rate | Table lookup | Table lookup | 0xFC (fixed) | Table lookup OR 0x80 | -| 0xE0EC | Modulation Type | 0x09 | 0x09 | From DCII table | 0x09 | -| 0xE0F5 | Demod Mode | 0x10 | 0x10 | 0x10/0x11/0x12/0x16 | 0x10 | -| 0xE0F6 | Turbo Flag | 0x00 | 0x01 | 0x00 | 0x00 | - -**DCII Demod Mode values:** - -| Modulation | XRAM 0xE0F5 Value | -|-----------|-------------------| -| DCII Combo (4) | 0x10 | -| DCII Offset QPSK (7) | 0x11 | -| DCII I-stream (5) | 0x12 | -| DCII Q-stream (6) | 0x16 | - -DSS (8) and DVB BPSK (9) share the same handler; they use the DVB-S QPSK FEC table but OR the lookup value with 0x80. - -### 6.4 Complete Tuning Sequence (Host to Satellite) - -``` -=== Phase 1: LNB Configuration (separate vendor commands) === -1. SET_LNB_VOLTAGE (0x8B) -- GPIO P0.4 (no I2C) - H / Circular-L -> wValue=1 (18V) - V / Circular-R -> wValue=0 (13V) -2. SET_22KHZ_TONE (0x8C) -- GPIO P0.3 (no I2C) - High band -> wValue=1 (tone on) - Low band -> wValue=0 (tone off) -3. SEND_DISEQC_COMMAND (0x8D) -- if multi-switch needed - -=== Phase 2: Tune Command === -4. TUNE_8PSK (0x86) -- 10-byte payload - -=== Phase 3: Firmware Internal Processing === -5. EP0BUF parsing: mod/FEC to IRAM, freq/SR byte-reversed to XRAM -6. Modulation dispatch: FEC lookup, XRAM config registers set -7. GPIO P3.6: DVB mode select - -=== Phase 4: BCM4500 I2C Programming (3 outer retries x 3 I2C addresses) === -8. Poll BCM4500 ready: I2C READ regs 0xA2, 0xA8, 0xA4 -9. Write page: I2C WRITE reg 0xA6 <- 0x00 -10. Write config: I2C WRITE reg 0xA7 <- [freq, SR, FEC, mod, demod params] -11. Execute: I2C WRITE reg 0xA8 <- 0x03 (indirect write command) -12. Poll completion: I2C READ regs 0xA8, 0xA2 -13. Verify: I2C READ reg 0xA7 (read-back compare) - -=== Phase 5: Signal Acquisition (host polling) === -14. GET_SIGNAL_LOCK (0x90) -- poll until non-zero -15. GET_SIGNAL_STRENGTH (0x87) -- read SNR -``` - -### 6.5 Signal Lock (GET_SIGNAL_LOCK, 0x90) - -Returns 1 byte from BCM4500 register 0xA4. Bit 5 (0x20) indicates signal lock. Both the Linux and Windows drivers interpret any non-zero value as locked and report full lock status (`FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_SIGNAL | FE_HAS_CARRIER`). - -### 6.6 Signal Strength (GET_SIGNAL_STRENGTH, 0x87) - -Returns 6 bytes. The first two bytes contain a 16-bit SNR value (little-endian, in dBu * 256 units): - -``` -Byte 0: SNR low byte (LSB) -Byte 1: SNR high byte (MSB) -Bytes 2-5: Reserved / BCM4500 diagnostic registers -``` - -**SNR scaling formula** (from Windows BDA driver): -``` -snr_raw = (buf[1] << 8) | buf[0] -if snr_raw <= 0x0F00: - signal_strength = snr_raw * 17 // maps to 0--65535 -else: - signal_strength = 0xFFFF // 100% at SNR >= 0x0F00 -``` - -The firmware performs a multi-step I2C transaction to read signal quality: BCM4500 indirect register write/read via 0xA6/0xA7/0xA8, with read-back verification. - -Version differences: -- v2.06: polls 3 registers (0xA2, 0xA8, 0xA4), loops up to 6 times -- v2.13: simplified polling (consolidated to 1 register), different call chain -- Rev.2: explicit write/read-back verification step - ---- - -## 7. BCM4500 Demodulator Interface - -### 7.1 I2C Bus - -The BCM4500 is accessed via the FX2's I2C controller at XRAM 0xE678. - -| Parameter | Value | -|-----------|-------| -| Primary I2C address | 0x10 (7-bit) | -| Alternate addresses | 0x3F, 0x7F (probed by v2.13 INT0 handler) | -| Bus speed | 400 kHz (set via C2 header config byte 0x40) | -| EEPROM address | 0x51 (7-bit, 24Cxx-family, for serial number / platform ID) | - -The FX2 I2C controller is accessed through XRAM registers: -- 0xE678: I2C control/status register -- 0xE679: I2C data register - -### 7.2 Indirect Register Protocol - -The BCM4500 uses an indirect register access scheme through three I2C-accessible registers: - -``` -Register 0xA6: Page/address select (write page number) -Register 0xA7: Data register (read/write indirect data) -Register 0xA8: Command register (write 0x03 to execute indirect write) -``` - -**Write sequence:** -``` -1. I2C WRITE device 0x10, reg 0xA6 <- page_number (typically 0x00) -2. I2C WRITE device 0x10, reg 0xA7 <- data_bytes (N bytes) -3. I2C WRITE device 0x10, reg 0xA8 <- 0x03 (execute) -4. Poll reg 0xA8 until command complete -5. Read back reg 0xA7 to verify -``` - -### 7.3 Status Registers - -| Register | Function | -|----------|----------| -| 0xA2 | BCM4500 status (polled for readiness) | -| 0xA4 | Lock/ready register; bit 5 = signal locked | -| 0xA8 | Command register; bit 0 = command done (polled) | -| 0xF9 | Demod status (read by v2.13 GET_DEMOD_STATUS / INT0 polling) | - -### 7.4 Demod Scan - -The tune function tries up to 3 different I2C address configurations per attempt, with 3 outer retries (total: up to 9 I2C programming attempts). This supports hardware variants where the BCM4500 may appear at different bus addresses. - -The demod scan function (Rev.2 FUN_CODE_1dd0) iterates through parameter sets computed from the iteration index (address offsets multiplied by 0x11), calling the indirect write function (FUN_CODE_1670) for each. - -v2.13 adds a more sophisticated probe at boot: INT0 polls addresses 0x7F and 0x3F up to 40 times (0x28), setting a "no demod found" flag (`_1_4`) if neither responds. This flag prevents tuning attempts on boards with absent or failed demodulators. - ---- - -## 8. GPIF Streaming Path - -### 8.1 Data Flow - -``` -BCM4500 Cypress FX2 (CY7C68013A) USB Host -Demodulator P3.5 GPIF Engine EP2 FIFO EP2 (0x82) - (I2C:0x10) <-----> (Master Read) (AUTOIN) ------------> Bulk IN - 8-bit 0xE4xx wfm 4x buffer 7 URBs - parallel 8-bit x 8KB -``` - -The path is fully hardware-managed. The GPIF engine reads data from the BCM4500's 8-bit parallel transport stream output directly into the EP2 FIFO. The AUTOIN bit causes automatic USB commit when the FIFO buffer is full. The FLOWSTATE engine automatically re-triggers GPIF transactions when buffer space becomes available. No firmware intervention occurs in the data path after initial setup. - -### 8.2 Key Register Configuration - -All values are identical across the three firmware versions: - -| Register | Address | Value | Function | -|----------|---------|-------|----------| -| IFCONFIG | 0xE601 | 0xEE | Internal 48 MHz clock, GPIF master mode, async, debug output | -| EP2FIFOCFG | 0xE618 | 0x0C | AUTOIN=1, ZEROLENIN=1, 8-bit data path | -| REVCTL | 0xE60B | 0x03 | NOAUTOARM + SKIPCOMMIT | -| CPUCS | 0xE600 | bits [4:3]=10 | 48 MHz CPU clock | -| FLOWSTATEA | 0xE668 | OR= 0x09 | FSEN (flow state enable) + FS[3] | -| GPIFIE | 0xE65C | OR= 0x3D | Waveform, TC, DONE, FIFO flag, WF2 interrupts | - -**IFCONFIG decode (0xEE = 1110_1110):** -- Bit 7: IFCLKSRC=1 (internal clock) -- Bit 6: 3048MHZ=1 (48 MHz) -- Bit 5: IFCLKOE=1 (clock output to BCM4500) -- Bit 4: IFCLKPOL=0 (non-inverted) -- Bit 3: ASYNC=1 (RDY pin handshaking, not clock-edge sampling) -- Bit 2: GSTATE=1 (debug state output on PORTE) -- Bits 1:0: IFCFG=10 (GPIF internal master) - -### 8.3 ARM_TRANSFER Sequences - -**Start streaming (wValue=1):** -1. Set config_byte bit 7 (streaming active) -2. Load GPIF transaction count: GPIFTCB3:2 = 0x8000 (effectively infinite) -3. Reset GPIF address and EP2 FIFO byte count -4. Assert P3.5 LOW (BCM4500 transport stream enable) -5. Wait for initial GPIF transaction (poll GPIFTRIG bit 7) -6. De-assert P3.5 HIGH -7. Trigger continuous GPIF read: GPIFTRIG = 0x04 (read into EP2) -8. Set P0.7 LOW (streaming indicator) - -**Stop streaming (wValue=0):** -1. Set P0.7 HIGH (streaming stopped) -2. Write EP2FIFOBCH = 0xFF (force-flush current buffer) -3. Wait for GPIF idle (poll GPIFTRIG bit 7) -4. Write OUTPKTEND = 0x82 (skip/discard partial EP2 packet) -5. Clear config_byte bit 7 (streaming inactive) -6. Set P3 bits 7:5 = 1 (de-assert all BCM4500 control lines) - -### 8.4 Interrupt Handling - -INT4 and INT6 (GPIF/FIFO events) share a common handler that sets a software flag and clears the hardware interrupt. The main loop polls this flag, enters CPU idle mode (PCON.0) between events, and checks EP2CS for buffer availability before re-arming the GPIF. - ---- - -## 9. LNB and DiSEqC Control - -### 9.1 LNB Voltage - -LNB voltage is controlled via GPIO P0.4 on all firmware versions. No I2C is involved. - -| wValue | Voltage | GPIO P0.4 | Polarization | -|--------|---------|-----------|-------------| -| 0 | 13V | LOW | Vertical / Circular-Right | -| 1 | 18V | HIGH | Horizontal / Circular-Left | - -**USE_EXTRA_VOLT** (0x94) enables a +1V boost (13V->14V, 18V->19V) for long cable runs, by writing to XRAM 0xE0B6 (0x62=normal, 0x6A=boosted; difference is bit 3). - -### 9.2 22 kHz Tone - -The 22 kHz tone is controlled via GPIO P0.3 on all firmware versions. P0.3 gates an external 22 kHz oscillator on the PCB. The firmware does not generate the 22 kHz signal directly. - -| wValue | State | GPIO P0.3 | Band | -|--------|-------|-----------|------| -| 0 | OFF | LOW | Low band (9.75 GHz LO on universal LNB) | -| 1 | ON | HIGH | High band (10.6 GHz LO on universal LNB) | - -### 9.3 DiSEqC Protocol - -All firmware versions implement DiSEqC via Timer2-based GPIO bit-bang. The algorithm is identical across versions; only the data pin assignment differs (see Section 10). - -**Timer2 configuration (identical across all versions):** - -| Parameter | Value | -|-----------|-------| -| T2CON | 0x04 (auto-reload, running) | -| RCAP2H:RCAP2L | 0xF82F (reload = 63535) | -| CKCON.T2M | 0 (Timer2 clock = 48 MHz / 12 = 4 MHz) | -| Tick period | (65536 - 63535) / 4 MHz = 500.25 us | - -**DiSEqC timing:** - -| Parameter | Value | -|-----------|-------| -| Bit period | 1.5 ms (3 Timer2 ticks) | -| Byte period | 13.5 ms (9 bits: 8 data + 1 parity) | -| Tone burst | 12.5 ms (25 ticks) | -| Pre-TX settling delay | 7.5 ms (15 ticks) | -| Data '0' | 1.0 ms tone + 0.5 ms silence (2/3 duty) | -| Data '1' | 0.5 ms tone + 1.0 ms silence (1/3 duty) | -| Carrier frequency | 22 kHz (external oscillator gated by P0.3) | - -**Manchester encoding** (decompiled from Rev.2 FUN_CODE_213c): -``` -Each DiSEqC bit = 3 Timer2 ticks: - Tick 1: inter-bit gap (carrier OFF) - Tick 2: carrier ON via P0.3 - Tick 3: if data='1', carrier OFF early; if data='0', carrier stays ON - End: carrier OFF -``` - -**Byte transmission**: 8 data bits MSB-first + 1 odd parity bit, each encoded as a Manchester symbol. The parity bit is '1' when the number of '1' data bits is even. - -**Tone burst** (mini DiSEqC): 25 consecutive Timer2 ticks of carrier (12.5 ms). - -### 9.4 SET_DN_SWITCH (0x8F) -- Legacy Dish Network Protocol - -A 7-bit serial command bit-banged on GPIO P0.4 with specific timing: - -1. Assert P0.4 HIGH (start pulse) -2. Delay ~32 cycles -3. De-assert P0.4 -4. Delay ~8 cycles -5. Shift out 7 bits LSB-first via P0.4, with ~8 cycle delays between bits - -The Linux kernel calls this via the `dishnetwork_send_legacy_command` frontend callback. The 8th bit (0x80) of the original switch command controls LNB voltage and is sent separately via SET_LNB_VOLTAGE. - ---- - -## 10. GPIO Pin Map - -### Port 0 (SFR 0x80, a.k.a. IOA) - -| Pin | v2.06 | Rev.2 v2.10 | v2.13 | Notes | -|-----|-------|-------------|-------|-------| -| P0.0 | -- | LNB control (0x97) | **DiSEqC data** | DiSEqC data pin moved across versions | -| P0.1 | -- | -- | -- | | -| P0.2 | Init set | Init set (0x84) | Init set | BCM4500 control | -| P0.3 | **22 kHz tone** | **22 kHz tone** | **22 kHz tone** | Gates external 22 kHz oscillator | -| P0.4 | **LNB 13V/18V** | **LNB 13V/18V** + DiSEqC data | **LNB 13V/18V** | Also SET_DN_SWITCH bit-bang (all versions) | -| P0.5 | -- | GPIO status (0x98) input | -- | LNB feedback on Rev.2 | -| P0.6 | -- | GPIO control (0x97) | -- | LNB control on Rev.2 | -| P0.7 | **DiSEqC data** | Streaming indicator | Streaming indicator | DiSEqC data on v2.06 only | - -### Port 3 (SFR 0xB0) - -| Pin | Function | Notes | -|-----|----------|-------| -| P3.0 | Init HIGH | | -| P3.4 | GPIO control | Used by FUN_CODE_1fcf (Rev.2) | -| P3.5 | **TS_EN** | Transport stream enable: LOW=active, HIGH=idle | -| P3.6 | **DVB mode** | BCM4500 mode select; DiSEqC port direction (Rev.2) | -| P3.7 | BCM4500 control | De-asserted (HIGH) when streaming stops | - -### Port B (XRAM-mapped IOB) - -Used by internal debug commands 0x96--0x98: - -| Pin | v2.06/v2.13 | Rev.2 | -|-----|-------------|-------| -| IOB.0 | GPIO status input (0x98) | -- | -| IOB.1 | LNB control (0x97) | -- | -| IOB.2 | LNB control (0x97) | -- | -| IOB.3 | LNB GPIO mode (0x96) | -- | -| IOB.4 | -- | LNB GPIO mode (0x96) + control (0x97) | - -**Init values (Rev.2):** -- P0 = 0x84 (P0.7=1, P0.2=1) -- P3 = 0xE1 (P3.7:5=1, P3.0=1) -- IPL1 = 0x9E - -### DiSEqC Data Pin Summary - -| Version | Data Pin | Carrier Pin | -|---------|----------|-------------| -| v2.06 | P0.7 | P0.3 | -| Rev.2 v2.10 | P0.4 | P0.3 | -| v2.13 | P0.0 | P0.3 | - -The carrier pin (P0.3) is the same across all versions. The data pin is used only internally by the firmware's Manchester encoding logic to control whether the carrier is cut short or held for the full bit period. - ---- - -## 11. Firmware Versions - -### 11.1 Version Table - -| Firmware | Version | Build Date | PID | Functions | Binary Size | Stack Ptr | -|----------|---------|------------|-----|-----------|-------------|-----------| -| v2.06.04 | 0x020604 | 2007-07-13 | 0x0203 | 61 | ~9,472 bytes | SP=0x72 | -| Rev.2 v2.10.04 | 0x020A04 | 2010-03-12 | 0x0202 | 107 | ~8,843 bytes | SP=0x4F | -| v2.13.01 | 0x020D01 | 2010-03-12 | 0x0203 | 88 | ~9,322 bytes | SP=0x50 | - -Note: Rev.2 v2.10 targets a different product (PID 0x0202). The v2.13 family has three sub-variants (FW1/FW2/FW3) targeting different SkyWalker-1 hardware sub-revisions. - -### 11.2 GET_FW_VERS (0x92) Format - -Returns 6 bytes of hardcoded constants: - -``` -Byte 0: version minor_minor (e.g., 0x04) -Byte 1: version minor (e.g., 0x06) -Byte 2: version major (e.g., 0x02) -Byte 3: build day (e.g., 0x0D = 13) -Byte 4: build month (e.g., 0x07 = July) -Byte 5: build year - 2000 (e.g., 0x07 = 2007) -``` - -Full version number: `byte[2] << 16 | byte[1] << 8 | byte[0]` - -Kernel constants for comparison: -``` -GP8PSK_FW_REV1 = 0x020604 -GP8PSK_FW_REV2 = 0x020704 -``` - -### 11.3 Binary Comparison Matrix - -Byte-level similarity (percentage of matching bytes within shared length): - -| | v2.06 | v2.13.1 | v2.13.2 | v2.13.3 | Rev.2 | -|---|---|---|---|---|---| -| v2.06 | -- | 4.8% | 4.3% | 4.3% | 6.0% | -| v2.13.1 | | -- | 57.2% | 59.4% | 8.0% | -| v2.13.2 | | | -- | 83.5% | 5.8% | -| v2.13.3 | | | | -- | 5.8% | -| Rev.2 | | | | | -- | - -The very low similarity between major versions (4--8%) indicates complete recompilation with different linker configurations. Functions are relocated even when logic is identical. Within the v2.13 family, FW2 and FW3 are 83.5% similar (minor hardware tuning), while FW1 differs more (57--59%, different demod interface). - -### 11.4 Key Differences Between Versions - -| Feature | v2.06 | Rev.2 v2.10 | v2.13 | -|---------|-------|-------------|-------| -| Vendor commands | 30 (0x80--0x9D) | 27 (0x80--0x9A) | 30 (0x80--0x9D) | -| INT0 purpose | USB re-enumeration | USB re-enumeration | Demod availability polling | -| Demod probe at init | No | No | Yes (40 attempts at 0x7F + 0x3F) | -| Retry loops | No | No | Yes (20-attempt with checksum verification) | -| HW revision detection | No | Yes (descriptor walker) | Yes (flag `_1_3`) | -| DiSEqC data pin | P0.7 | P0.4 | P0.0 | -| Config byte IRAM | 0x6D | 0x4E | 0x4F | -| Descriptor base | 0x1200 | 0x0E00 | 0x0E00 | -| Init table address | CODE:0B46 | CODE:0B48 | CODE:0B88 | -| BCM4500 status poll | 3 registers | 3 registers | 1 register (consolidated) | -| Anti-tampering string | No | No | Yes (at offset 0x1880) | -| New commands | -- | 0x99/0x9A proto | 0x99 GET_DEMOD_STATUS, 0x9A INIT_DEMOD, 0x9C DELAY_COMMAND | -| 0x9D behavior | HW revision mode switch | N/A (out of range) | Conditional demod reset | - -### 11.5 EEPROM Format Details - -All SkyWalker-1 C2 files use uniform 1023-byte segments: - -``` -Segment Address Length -------- ------- ------ -1 0x0000 1023 Contains reset vector, interrupt handlers -2 0x03FF 1023 -3 0x07FE 1023 -4 0x0BFD 1023 -5 0x0FFC 1023 -6 0x13FB 1023 -7 0x17FA 1023 -8 0x1BF9 1023 -9 0x1FF8 1023 -10 0x23F7 varies (115--265 bytes depending on version) -``` - -### 11.6 Anti-Tampering (v2.13 only) - -At firmware offset 0x1880, all v2.13 sub-variants contain: -``` -"Tampering is detected. Attempt is logged. Warranty is voided ! \n" -``` -Followed by I2C register write commands (`01 10 aa 82 02 41 41 83`). This string and mechanism are absent from v2.06 and Rev.2 firmware. - -### 11.7 Rev.2 as Transitional Firmware - -Rev.2 v2.10.4 sits architecturally between v2.06 and v2.13: -- Adopted v2.13's stack pointer (SP=0x4F) and descriptor base (0x0E00) -- Retained v2.06's INT0 USB re-enumeration behavior -- Lacks v2.13's demodulator polling, retry loops, and 3 additional vendor commands -- Has the most functions (107) but smallest binary (~8.7 KB) due to granular decomposition -- The INT0 repurposing was the last major architectural change between Rev.2 and v2.13 - ---- - -## 12. Internal Debug Commands - -Commands 0x91 and 0x96--0x98 are not used by any driver (Linux or Windows). They appear to be manufacturing/debug interfaces. - -### 0x91 I2C_ADDR_ADJUST - -Increments (wValue != 0) or decrements (wValue == 0) an internal IRAM counter and returns its current value (1 byte). The counter is at IRAM 0x66 (v2.06) or IRAM 0x18 (v2.13/Rev.2). Likely used for I2C bus address or tuner register index adjustment during development. - -### 0x96 SET_LNB_GPIO_MODE - -Configures GPIO output enable registers for LNB voltage regulator hardware: - -| Mode | v2.06/v2.13 | Rev.2 | -|------|-------------|-------| -| Default (wValue=0) | OEB=0xF0 | OEB=0xE7, OEA=0x9E | -| Active (wValue=1) | IOB=(IOB & 0xF7) OR 0x06; OEB=0xFE | IOB.4 clear; P0.6, P0.0 set; OEA OR= 0x41 | - -### 0x97 SET_GPIO_PINS - -Direct GPIO pin write for LNB control: - -| wValue Bit | v2.06/v2.13 Target | Rev.2 Target | -|-----------|-------------------|-------------| -| bit 1 | IOB.1 | P0.6 (Port A) | -| bit 2 | IOB.2 | P0.0 (Port A) | -| bit 3 | IOB.3 | IOB.4 (Port B) | - -### 0x98 GET_GPIO_STATUS - -Returns 1 byte (0 or 1) from a single GPIO input pin -- likely an LNB overcurrent or power-good feedback signal: - -| Version | Pin Read | -|---------|----------| -| v2.06/v2.13 | IOB.0 (Port B bit 0) | -| Rev.2 | P0.5 (Port A bit 5) | - ---- - -## Sources - -### Firmware Analysis - -- Ghidra decompilation/disassembly of three firmware images: - - v2.06.04 (Ghidra port 8193) -- extracted from SkyWalker-1 EEPROM - - Rev.2 v2.10.04 (Ghidra port 8197) -- extracted from Rev.2 hardware - - v2.13.01 FW1 (Ghidra port 8194) -- extracted from Windows updater -- Firmware dumps: `/home/rpm/claude/ham/satellite/genpix/skywalker-1/firmware-dump/` - -### Driver Source - -- Linux kernel 6.16.5: `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h`, `gp8psk-fe.c`, `gp8psk-fe.h` -- Linux kernel: `drivers/media/usb/dvb-usb/dvb-usb-firmware.c` -- Windows BDA driver: `SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp` -- Windows BDA driver: `SkyWalker1_Final_Release/Include/SkyWalker1Control.h`, `SkyWalker1CommonDef.h` - -### Hardware Documentation - -- Cypress CY7C68013A (FX2LP) Technical Reference Manual -- Genpix Electronics official site: https://www.genpix-electronics.com/index.php?act=viewDoc&docId=9 -- Device `dmesg` output from running SkyWalker-1 hardware (v2.06.04 firmware) - -### Phase 1 Analysis Reports - -1. `gp8psk-driver-analysis.md` -- Linux kernel driver analysis -2. `firmware-analysis-v206-vs-v213.md` -- Cross-version firmware comparison -3. `rev2-deep-analysis.md` -- Rev.2 deep function inventory (107 functions) -4. `gpif-streaming-analysis.md` -- GPIF/MPEG-2 streaming path -5. `kernel-fw01-analysis.md` -- Kernel firmware format and EEPROM boot -6. `vendor-commands-unknown.md` -- Complete vendor command decode (0x8F, 0x91--0x98) -7. `tuning-protocol-analysis.md` -- TUNE_8PSK protocol deep dive +# Genpix SkyWalker-1 Hardware Reference + +Consolidated technical reference for the Genpix SkyWalker-1 DVB-S USB 2.0 satellite receiver, derived from Linux kernel driver analysis, firmware reverse engineering (Ghidra), and Windows BDA driver source review. + +--- + +## 1. Overview + +The Genpix SkyWalker-1 is a standalone USB 2.0 DVB-S satellite receiver built around two ICs: + +- **Cypress CY7C68013A** (FX2LP) -- USB 2.0 Hi-Speed microcontroller (8051 core, 48 MHz) +- **Broadcom BCM4500** -- DVB-S / 8PSK satellite demodulator + +The FX2 handles USB communication, LNB control, DiSEqC signaling, and orchestrates tuning via I2C commands to the BCM4500. The BCM4500 performs RF demodulation, FEC decoding, and outputs an MPEG-2 transport stream on an 8-bit parallel bus. The GPIF engine inside the FX2 transfers the transport stream directly into a USB bulk endpoint with zero firmware intervention in the data path. + +### Supported Modulations + +| Index | Modulation | Constant | +|-------|-----------|----------| +| 0 | DVB-S QPSK | `ADV_MOD_DVB_QPSK` | +| 1 | Turbo-coded QPSK | `ADV_MOD_TURBO_QPSK` | +| 2 | Turbo-coded 8PSK | `ADV_MOD_TURBO_8PSK` | +| 3 | Turbo-coded 16QAM | `ADV_MOD_TURBO_16QAM` | +| 4 | Digicipher II Combo | `ADV_MOD_DCII_C_QPSK` | +| 5 | Digicipher II I-stream (split) | `ADV_MOD_DCII_I_QPSK` | +| 6 | Digicipher II Q-stream (split) | `ADV_MOD_DCII_Q_QPSK` | +| 7 | Digicipher II Offset QPSK | `ADV_MOD_DCII_C_OQPSK` | +| 8 | DSS QPSK | `ADV_MOD_DSS_QPSK` | +| 9 | DVB-S BPSK | `ADV_MOD_DVB_BPSK` | + +DVB-S2 is **not** supported (incompatible FEC architecture). + +### RF Specifications + +| Parameter | Value | +|-----------|-------| +| IF frequency range | 950 -- 2150 MHz | +| Symbol rate | 256 Ksps -- 30 Msps | +| Input connector | IEC F-type female | +| LNB voltage | 13/18V (or 14/19V with USE_EXTRA_VOLT) | +| LNB current | 450 mA continuous / 750 mA burst | +| Switch control | 22 kHz, Tone Burst, DiSEqC 1.0/1.2, Legacy Dish Network | + +--- + +## 2. USB Interface + +### VID/PID Table + +All Genpix products share VID `0x09C0`: + +| PID | Product | cold_ids | warm_ids | Kernel Module | Notes | +|-----|---------|----------|----------|---------------|-------| +| 0x0200 | 8PSK-to-USB2 Rev.1 Cold | Yes | No | gp8psk | Requires FW01 upload to RAM | +| 0x0201 | 8PSK-to-USB2 Rev.1 Warm | No | Yes | gp8psk | Requires FW02 (BCM4500) | +| 0x0202 | 8PSK-to-USB2 Rev.2 | No | Yes | gp8psk | Boots from EEPROM | +| 0x0203 | **SkyWalker-1** | No | Yes | gp8psk | Boots from EEPROM | +| 0x0204 | SkyWalker-1 (alternate) | No | Yes | gp8psk | Boots from EEPROM | +| 0x0205 | SkyWalker-2 | -- | -- | -- | Not in kernel 6.16.5 | +| 0x0206 | SkyWalker CW3K | No | Yes | gp8psk | Requires CW3K_INIT (0x9D) | + +PID 0x0203 was added to the kernel device table after v6.6.1. + +### USB Endpoints and Streaming Properties + +| Property | Value | +|----------|-------| +| Control endpoint | EP0 (default, vendor requests) | +| Bulk IN endpoint | EP2 (0x82) -- MPEG-2 transport stream | +| Generic bulk CTRL endpoint | 0x01 (BCM4500 FW02 upload only) | +| URB count | 7 | +| URB buffer size | 8192 bytes each | +| Stream type | USB_BULK | +| FX2 controller type | CYPRESS_FX2 | + +### Warm Boot Behavior + +The SkyWalker-1 (PID 0x0203) enumerates directly as a "warm" device. The DVB-USB framework skips firmware download when `cold_ids` is NULL. No host-side firmware files (`dvb-usb-gp8psk-01.fw` or `dvb-usb-gp8psk-02.fw`) are required. These files were never open-sourced or included in the `linux-firmware` package. + +| Device | Needs FW01? | Needs FW02? | Boot Source | +|--------|-------------|-------------|-------------| +| Rev.1 Cold (0x0200) | Yes | -- | RAM (empty) | +| Rev.1 Warm (0x0201) | No | Yes | RAM (FW01 loaded) | +| Rev.2 (0x0202) | No | No | EEPROM | +| SkyWalker-1 (0x0203) | No | No | EEPROM | +| SkyWalker CW3K (0x0206) | No | No | EEPROM | + +--- + +## 3. Complete Vendor Command Reference + +All vendor commands use USB control transfers: +- **USB Type**: `USB_TYPE_VENDOR` +- **Timeout**: 2000 ms +- **Retry**: Up to 3 attempts for IN operations if partial data received +- **Data buffer maximum**: 80 bytes (kernel driver) + +### 3.1 Command Table + +The vendor command dispatcher at CODE:0056 validates `bRequest` in the range 0x80--0x9D (30 entries) and dispatches via an indexed jump table at CODE:0076. Rev.2 supports only 0x80--0x9A (27 entries). + +| Cmd | Name | Dir | wValue | wIndex | wLength | Purpose | Linux | Windows | v2.06 | Rev.2 | v2.13 | +|-----|------|-----|--------|--------|---------|---------|-------|---------|-------|-------|-------| +| 0x80 | GET_8PSK_CONFIG | IN | 0 | 0 | 1 | Read configuration status byte | Yes | Yes | OK | OK | OK | +| 0x81 | SET_8PSK_CONFIG | OUT | varies | 0 | 0 | Set config (reserved) | No | No | STALL | STALL | STALL | +| 0x82 | (reserved) | -- | -- | -- | -- | Reserved | No | No | STALL | STALL | STALL | +| 0x83 | I2C_WRITE | OUT | dev_addr | reg_addr | N | Write to I2C device | Yes | Yes | OK | OK | OK | +| 0x84 | I2C_READ | IN | dev_addr | reg_addr | N | Read from I2C device | Yes | Yes | OK | OK | OK | +| 0x85 | ARM_TRANSFER | OUT | 0/1 | 0 | 0 | Start (1) / stop (0) MPEG-2 stream | Yes | Yes | OK | OK | OK | +| 0x86 | TUNE_8PSK | OUT | 0 | 0 | 10 | Set tuning parameters (see Section 6) | Yes | Yes | OK | OK | OK | +| 0x87 | GET_SIGNAL_STRENGTH | IN | 0 | 0 | 6 | Read SNR and diagnostics | Yes | Yes | OK | OK | Changed | +| 0x88 | LOAD_BCM4500 | OUT | 1 | 0 | 0 | Initiate BCM4500 FW download | Yes* | No | STALL | STALL | STALL | +| 0x89 | BOOT_8PSK | IN | 0/1 | 0 | 1 | Power on (1) / off (0) demodulator | Yes | Yes | OK | OK | OK | +| 0x8A | START_INTERSIL | IN | 0/1 | 0 | 1 | Enable (1) / disable (0) LNB supply | Yes | Yes | OK | OK | OK | +| 0x8B | SET_LNB_VOLTAGE | OUT | 0/1 | 0 | 0 | 13V (0) or 18V (1) | Yes | Yes | OK | OK | OK | +| 0x8C | SET_22KHZ_TONE | OUT | 0/1 | 0 | 0 | Tone off (0) or on (1) | Yes | Yes | OK | OK | OK | +| 0x8D | SEND_DISEQC_COMMAND | OUT | msg[0] | 0 | len | DiSEqC message or tone burst | Yes | Yes | OK | OK | OK | +| 0x8E | SET_DVB_MODE | OUT | 1 | 0 | 0 | Enable DVB-S mode | Yes | No | STALL | STALL | STALL | +| 0x8F | SET_DN_SWITCH | OUT | cmd7bit | 0 | 0 | Legacy Dish Network switch protocol | Yes | Yes | OK | OK | OK | +| 0x90 | GET_SIGNAL_LOCK | IN | 0 | 0 | 1 | Read signal lock status | Yes | Yes | OK | OK | OK | +| 0x91 | I2C_ADDR_ADJUST | IN | 0/1 | 0 | 1 | Inc/dec internal IRAM counter (debug) | No | No | OK | OK | OK | +| 0x92 | GET_FW_VERS | IN | 0 | 0 | 6 | Read firmware version + build date | Yes | No | OK | OK | OK | +| 0x93 | GET_SERIAL_NUMBER | IN | 0 | 0 | 4 | Read 4-byte serial from I2C EEPROM | No | Yes | OK | OK | OK | +| 0x94 | USE_EXTRA_VOLT | OUT | 0/1 | 0 | 0 | Enable +1V LNB boost (14V/19V) | Yes | Yes | OK | OK | OK | +| 0x95 | GET_FPGA_VERS | IN | 0 | 0 | 1 | Read EEPROM hardware/platform ID | Yes | No | OK | OK | OK | +| 0x96 | SET_LNB_GPIO_MODE | OUT | 0/1 | 0 | 0 | Configure LNB GPIO output enables | No | No | OK | OK | OK | +| 0x97 | SET_GPIO_PINS | OUT | bitmap | 0 | 0 | Direct write to LNB GPIO pins | No | No | OK | OK | OK | +| 0x98 | GET_GPIO_STATUS | IN | 0 | 0 | 1 | Read LNB feedback GPIO pin | No | No | OK | OK | OK | +| 0x99 | GET_DEMOD_STATUS | IN | 0 | 0 | 1 | Read BCM4500 register 0xF9 | No | No | STALL | Proto | OK | +| 0x9A | INIT_DEMOD | OUT | 0 | 0 | 0 | Trigger demod re-init (3 attempts) | No | No | STALL | Proto | OK | +| 0x9B | (reserved) | -- | -- | -- | -- | Reserved | No | No | STALL | N/A | STALL | +| 0x9C | DELAY_COMMAND | OUT | delay | 0 | 0 | Host-controlled tuning delay + poll | No | No | STALL | N/A | OK | +| 0x9D | CW3K_INIT / SET_MODE_FLAG | OUT | 0/1 | 0 | 0 | CW3K init or conditional demod reset | Yes** | No | OK | N/A | Changed | + +\* Linux driver only sends LOAD_BCM4500 for Rev.1 Warm (PID 0x0201). On SkyWalker-1, `bm8pskFW_Loaded` is already set and 0x88 routes to STALL. + +\*\* Linux driver only sends CW3K_INIT for SkyWalker CW3K (PID 0x0206). + +**Status key**: OK = implemented, STALL = routes to stall handler (endpoint stall returned), Proto = partial/prototype implementation, N/A = command index out of range (Rev.2 only supports 0x80--0x9A), Changed = implementation differs between versions. + +### 3.2 Detailed Parameter Formats + +**0x8D SEND_DISEQC_COMMAND**: When `wLength > 0`, the payload is a standard DiSEqC message (3--6 bytes) with `wValue` set to `msg[0]` (framing byte, typically 0xE0 or 0xE1). When `wLength == 0` and `wValue == 0`, a tone burst A is sent. When `wLength == 0` and `wValue != 0`, a tone burst B is sent. + +**0x8F SET_DN_SWITCH**: `wValue` carries a 7-bit Dish Network switch command (LSB-first), bit-banged on GPIO P0.4 with specific timing. The 8th bit of the original switch command selects LNB voltage and is sent separately via SET_LNB_VOLTAGE. + +**0x92 GET_FW_VERS**: Returns 6 bytes: `[minor_minor, minor, major, day, month, year-2000]`. Full version = `major<<16 | minor<<8 | minor_minor`. Build date = `(2000+year)/month/day`. + +**0x93 GET_SERIAL_NUMBER**: Returns 4 bytes read from I2C EEPROM at device address 0x51 (7-bit), extracted at 8-bit intervals using a shift/rotate routine. + +**0x94 USE_EXTRA_VOLT**: `wValue=1` writes 0x6A to XRAM 0xE0B6; `wValue=0` writes 0x62. The difference is bit 3 (0x08), which controls the extra voltage boost on the LNB power regulator. + +**0x95 GET_FPGA_VERS**: Reads from I2C EEPROM at 0x51. Despite the name, there is no FPGA -- this returns the EEPROM-stored hardware platform ID. v2.06 reads offset 0x31 (2 bytes); v2.13/Rev.2 read offset 0x00 (1 byte). + +**0x96--0x98**: Internal debug commands for LNB GPIO control. 0x96 configures output enables (OEB/OEA), 0x97 writes pin states, 0x98 reads a feedback pin. GPIO assignments differ between v2.06/v2.13 (Port B) and Rev.2 (Port A + Port B). See Section 10 for pin details. + +--- + +## 4. Configuration Status Byte + +Returned by GET_8PSK_CONFIG (0x80). Stored in IRAM at version-dependent addresses. + +``` +Bit 7 (0x80): bmArmed - MPEG-2 stream transfer armed / GPIF active +Bit 6 (0x40): bmDCtuned - DC offset tuning complete (set for DCII modes) +Bit 5 (0x20): bmSEL18V - 18V LNB voltage selected (else 13V) +Bit 4 (0x10): bm22kHz - 22 kHz tone active +Bit 3 (0x08): bmDVBmode - DVB mode enabled +Bit 2 (0x04): bmIntersilOn - LNB power supply enabled +Bit 1 (0x02): bm8pskFW_Loaded - BCM4500 firmware loaded (always set on SkyWalker-1) +Bit 0 (0x01): bm8pskStarted - Device booted and running +``` + +| Version | IRAM Address | +|---------|-------------| +| v2.06 | 0x6D | +| Rev.2 v2.10.4 | 0x4E | +| v2.13 | 0x4F | + +--- + +## 5. Boot Sequence + +### 5.1 Two-Stage Firmware Architecture + +The FX2 supports two firmware sources: + +1. **Host RAM upload** (Rev.1 Cold only): The host writes 8051 code to FX2 RAM via USB 0xA0 vendor requests, using the built-in boot ROM. This requires `dvb-usb-gp8psk-01.fw` in binary hexline format. + +2. **EEPROM boot** (Rev.2, SkyWalker-1, CW3K): The FX2 boot ROM reads firmware from an external I2C EEPROM in Cypress C2 format on power-up. No host interaction needed. + +### 5.2 C2 EEPROM Format + +SkyWalker-1 firmware is stored in Cypress C2 IIC second-stage boot format: + +**Header (8 bytes):** + +| Offset | Size | Field | SkyWalker-1 Value | +|--------|------|-------|-------------------| +| 0 | 1 | Marker | 0xC2 (external memory, large code model) | +| 1 | 2 | VID (LE) | 0x09C0 | +| 3 | 2 | PID (LE) | 0x0203 | +| 5 | 2 | DID (LE) | 0x0000 | +| 7 | 1 | Config | 0x40 (400 kHz I2C) | + +**Code segments** follow: 2-byte length (BE) + 2-byte target address (BE) + data. Maximum segment size is 1023 bytes (FX2 I2C boot ROM buffer limit). All SkyWalker-1 variants use 10 segments. + +**Terminator**: 0x80xx (high bit set) + 2-byte entry point address (0xE600 = CPUCS). + +### 5.3 Power-On Boot Sequence + +``` +1. GET_8PSK_CONFIG (0x80) -- read config status byte + |-- Check bit 0: bm8pskStarted? + +2. If not started: + |-- BOOT_8PSK (0x89, wValue=1) + |-- GET_FW_VERS (0x92) -- read firmware version + +3. If bit 1 clear (bm8pskFW_Loaded): + |-- LOAD_BCM4500 (0x88) -- Rev.1 Warm only; STALLs on SkyWalker-1 + +4. If bit 2 clear (bmIntersilOn): + |-- START_INTERSIL (0x8A, wValue=1) -- enable LNB power supply + +5. SET_DVB_MODE (0x8E, wValue=1) -- STALLs on all SkyWalker-1 FW versions + +6. ARM_TRANSFER (0x85, wValue=0) -- abort any pending MPEG transfer + +7. Device ready for tuning +``` + +### 5.4 Firmware Version Identification + +The kernel reads firmware version on boot via GET_FW_VERS (0x92) and logs: +``` +gp8psk: FW Version = 2.06.4 (0x20604) Build 2007/07/13 +``` + +Kernel revision constants (from `gp8psk-fe.h`): +``` +GP8PSK_FW_REV1 = 0x020604 (v2.06.4) +GP8PSK_FW_REV2 = 0x020704 (v2.07.4) +``` + +If `fw_vers >= GP8PSK_FW_REV2`, the kernel enables Rev.2-specific code paths. The v2.10 and v2.13 firmwares are newer than either kernel constant. + +--- + +## 6. Tuning Protocol + +### 6.1 TUNE_8PSK Command Format (0x86) + +The host sends a 10-byte OUT payload via USB control transfer: + +``` +USB SETUP: bmRequestType=0x40, bRequest=0x86, wValue=0, wIndex=0, wLength=10 + +EP0BUF Layout: + Byte Content Encoding + ---- ------------------ ---------------- + [0] Symbol Rate byte 0 Little-endian LSB + [1] Symbol Rate byte 1 + [2] Symbol Rate byte 2 + [3] Symbol Rate byte 3 Little-endian MSB + [4] Frequency byte 0 Little-endian LSB + [5] Frequency byte 1 + [6] Frequency byte 2 + [7] Frequency byte 3 Little-endian MSB + [8] Modulation Type 0--9 (see Section 1 table) + [9] Inner FEC Rate Index into modulation-specific table +``` + +**Symbol Rate** is in samples per second (sps). The Windows driver multiplies ksps by 1000. + +**Frequency** is the IF frequency in kHz (950000--2150000), computed by the host as `(RF_freq - LO_freq) * multiplier`. + +### 6.2 Firmware EP0BUF Parsing + +The firmware reads the 10-byte payload from EP0BUF (XRAM 0xE740--0xE749) and stores: + +| Source | Destination | Notes | +|--------|-------------|-------| +| EP0BUF[8] (mod) | IRAM 0x4D | Direct copy | +| EP0BUF[9] (FEC) | IRAM 0x4F | Direct copy | +| EP0BUF[4--7] (freq) | XRAM 0xE0DB--0xE0DE | Byte-reversed (LE to BE) | +| EP0BUF[0--3] (SR) | XRAM 0xE0CB--0xE0CE | Byte-reversed (LE to BE) | + +The byte reversal converts host little-endian to BCM4500 big-endian so values can be written directly to the demodulator via I2C. + +### 6.3 Modulation Dispatch + +After parsing, the firmware validates the modulation type (< 10) and dispatches via a jump table to modulation-specific handlers. Each handler: + +1. Validates the FEC index against the maximum for that modulation +2. Looks up a preconfigured byte from an XRAM table +3. Writes configuration to four XRAM registers + +**FEC Rate Lookup Tables** (populated from the CODE-space init table at boot): + +| XRAM Base | Modulation | Max FEC Index | Notes | +|-----------|-----------|---------------|-------| +| 0xE0F9 | DVB-S QPSK, DSS, BPSK | 7 | Standard Viterbi rates (1/2, 2/3, 3/4, 5/6, 7/8, auto, none) | +| 0xE0B7 | Turbo QPSK | 5 | Turbo code rates | +| 0xE0B1 | Turbo 8PSK | 5 | Turbo code rates | +| 0xE0BC | Turbo 16QAM | 1 | Single code rate | +| 0xE0BD | DCII (all variants) | 9 | Combined code rate + modulation | + +**BCM4500 XRAM Configuration after dispatch:** + +| XRAM Addr | Register | DVB-S QPSK | Turbo (Q/8/16) | DCII | DSS/BPSK | +|-----------|----------|-----------|---------------|------|----------| +| 0xE0EB | FEC Code Rate | Table lookup | Table lookup | 0xFC (fixed) | Table lookup OR 0x80 | +| 0xE0EC | Modulation Type | 0x09 | 0x09 | From DCII table | 0x09 | +| 0xE0F5 | Demod Mode | 0x10 | 0x10 | 0x10/0x11/0x12/0x16 | 0x10 | +| 0xE0F6 | Turbo Flag | 0x00 | 0x01 | 0x00 | 0x00 | + +**DCII Demod Mode values:** + +| Modulation | XRAM 0xE0F5 Value | +|-----------|-------------------| +| DCII Combo (4) | 0x10 | +| DCII Offset QPSK (7) | 0x11 | +| DCII I-stream (5) | 0x12 | +| DCII Q-stream (6) | 0x16 | + +DSS (8) and DVB BPSK (9) share the same handler; they use the DVB-S QPSK FEC table but OR the lookup value with 0x80. + +### 6.4 Complete Tuning Sequence (Host to Satellite) + +``` +=== Phase 1: LNB Configuration (separate vendor commands) === +1. SET_LNB_VOLTAGE (0x8B) -- GPIO P0.4 (no I2C) + H / Circular-L -> wValue=1 (18V) + V / Circular-R -> wValue=0 (13V) +2. SET_22KHZ_TONE (0x8C) -- GPIO P0.3 (no I2C) + High band -> wValue=1 (tone on) + Low band -> wValue=0 (tone off) +3. SEND_DISEQC_COMMAND (0x8D) -- if multi-switch needed + +=== Phase 2: Tune Command === +4. TUNE_8PSK (0x86) -- 10-byte payload + +=== Phase 3: Firmware Internal Processing === +5. EP0BUF parsing: mod/FEC to IRAM, freq/SR byte-reversed to XRAM +6. Modulation dispatch: FEC lookup, XRAM config registers set +7. GPIO P3.6: DVB mode select + +=== Phase 4: BCM4500 I2C Programming (3 outer retries x 3 I2C addresses) === +8. Poll BCM4500 ready: I2C READ regs 0xA2, 0xA8, 0xA4 +9. Write page: I2C WRITE reg 0xA6 <- 0x00 +10. Write config: I2C WRITE reg 0xA7 <- [freq, SR, FEC, mod, demod params] +11. Execute: I2C WRITE reg 0xA8 <- 0x03 (indirect write command) +12. Poll completion: I2C READ regs 0xA8, 0xA2 +13. Verify: I2C READ reg 0xA7 (read-back compare) + +=== Phase 5: Signal Acquisition (host polling) === +14. GET_SIGNAL_LOCK (0x90) -- poll until non-zero +15. GET_SIGNAL_STRENGTH (0x87) -- read SNR +``` + +### 6.5 Signal Lock (GET_SIGNAL_LOCK, 0x90) + +Returns 1 byte from BCM4500 register 0xA4. Bit 5 (0x20) indicates signal lock. Both the Linux and Windows drivers interpret any non-zero value as locked and report full lock status (`FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_SIGNAL | FE_HAS_CARRIER`). + +### 6.6 Signal Strength (GET_SIGNAL_STRENGTH, 0x87) + +Returns 6 bytes. The first two bytes contain a 16-bit SNR value (little-endian, in dBu * 256 units): + +``` +Byte 0: SNR low byte (LSB) +Byte 1: SNR high byte (MSB) +Bytes 2-5: Reserved / BCM4500 diagnostic registers +``` + +**SNR scaling formula** (from Windows BDA driver): +``` +snr_raw = (buf[1] << 8) | buf[0] +if snr_raw <= 0x0F00: + signal_strength = snr_raw * 17 // maps to 0--65535 +else: + signal_strength = 0xFFFF // 100% at SNR >= 0x0F00 +``` + +The firmware performs a multi-step I2C transaction to read signal quality: BCM4500 indirect register write/read via 0xA6/0xA7/0xA8, with read-back verification. + +Version differences: +- v2.06: polls 3 registers (0xA2, 0xA8, 0xA4), loops up to 6 times +- v2.13: simplified polling (consolidated to 1 register), different call chain +- Rev.2: explicit write/read-back verification step + +--- + +## 7. BCM4500 Demodulator Interface + +### 7.1 I2C Bus + +The BCM4500 is accessed via the FX2's I2C controller at XRAM 0xE678. + +| Parameter | Value | +|-----------|-------| +| Primary I2C address | 0x10 (7-bit) | +| Alternate addresses | 0x3F, 0x7F (probed by v2.13 INT0 handler) | +| Bus speed | 400 kHz (set via C2 header config byte 0x40) | +| EEPROM address | 0x51 (7-bit, 24Cxx-family, for serial number / platform ID) | + +The FX2 I2C controller is accessed through XRAM registers: +- 0xE678: I2C control/status register +- 0xE679: I2C data register + +### 7.2 Indirect Register Protocol + +The BCM4500 uses an indirect register access scheme through three I2C-accessible registers: + +``` +Register 0xA6: Page/address select (write page number) +Register 0xA7: Data register (read/write indirect data) +Register 0xA8: Command register (write 0x03 to execute indirect write) +``` + +**Write sequence:** +``` +1. I2C WRITE device 0x10, reg 0xA6 <- page_number (typically 0x00) +2. I2C WRITE device 0x10, reg 0xA7 <- data_bytes (N bytes) +3. I2C WRITE device 0x10, reg 0xA8 <- 0x03 (execute) +4. Poll reg 0xA8 until command complete +5. Read back reg 0xA7 to verify +``` + +### 7.3 Status Registers + +| Register | Function | +|----------|----------| +| 0xA2 | BCM4500 status (polled for readiness) | +| 0xA4 | Lock/ready register; bit 5 = signal locked | +| 0xA8 | Command register; bit 0 = command done (polled) | +| 0xF9 | Demod status (read by v2.13 GET_DEMOD_STATUS / INT0 polling) | + +### 7.4 Demod Scan + +The tune function tries up to 3 different I2C address configurations per attempt, with 3 outer retries (total: up to 9 I2C programming attempts). This supports hardware variants where the BCM4500 may appear at different bus addresses. + +The demod scan function (Rev.2 FUN_CODE_1dd0) iterates through parameter sets computed from the iteration index (address offsets multiplied by 0x11), calling the indirect write function (FUN_CODE_1670) for each. + +v2.13 adds a more sophisticated probe at boot: INT0 polls addresses 0x7F and 0x3F up to 40 times (0x28), setting a "no demod found" flag (`_1_4`) if neither responds. This flag prevents tuning attempts on boards with absent or failed demodulators. + +--- + +## 8. GPIF Streaming Path + +### 8.1 Data Flow + +``` +BCM4500 Cypress FX2 (CY7C68013A) USB Host +Demodulator P3.5 GPIF Engine EP2 FIFO EP2 (0x82) + (I2C:0x10) <-----> (Master Read) (AUTOIN) ------------> Bulk IN + 8-bit 0xE4xx wfm 4x buffer 7 URBs + parallel 8-bit x 8KB +``` + +The path is fully hardware-managed. The GPIF engine reads data from the BCM4500's 8-bit parallel transport stream output directly into the EP2 FIFO. The AUTOIN bit causes automatic USB commit when the FIFO buffer is full. The FLOWSTATE engine automatically re-triggers GPIF transactions when buffer space becomes available. No firmware intervention occurs in the data path after initial setup. + +### 8.2 Key Register Configuration + +All values are identical across the three firmware versions: + +| Register | Address | Value | Function | +|----------|---------|-------|----------| +| IFCONFIG | 0xE601 | 0xEE | Internal 48 MHz clock, GPIF master mode, async, debug output | +| EP2FIFOCFG | 0xE618 | 0x0C | AUTOIN=1, ZEROLENIN=1, 8-bit data path | +| REVCTL | 0xE60B | 0x03 | NOAUTOARM + SKIPCOMMIT | +| CPUCS | 0xE600 | bits [4:3]=10 | 48 MHz CPU clock | +| FLOWSTATEA | 0xE668 | OR= 0x09 | FSEN (flow state enable) + FS[3] | +| GPIFIE | 0xE65C | OR= 0x3D | Waveform, TC, DONE, FIFO flag, WF2 interrupts | + +**IFCONFIG decode (0xEE = 1110_1110):** +- Bit 7: IFCLKSRC=1 (internal clock) +- Bit 6: 3048MHZ=1 (48 MHz) +- Bit 5: IFCLKOE=1 (clock output to BCM4500) +- Bit 4: IFCLKPOL=0 (non-inverted) +- Bit 3: ASYNC=1 (RDY pin handshaking, not clock-edge sampling) +- Bit 2: GSTATE=1 (debug state output on PORTE) +- Bits 1:0: IFCFG=10 (GPIF internal master) + +### 8.3 ARM_TRANSFER Sequences + +**Start streaming (wValue=1):** +1. Set config_byte bit 7 (streaming active) +2. Load GPIF transaction count: GPIFTCB3:2 = 0x8000 (effectively infinite) +3. Reset GPIF address and EP2 FIFO byte count +4. Assert P3.5 LOW (BCM4500 transport stream enable) +5. Wait for initial GPIF transaction (poll GPIFTRIG bit 7) +6. De-assert P3.5 HIGH +7. Trigger continuous GPIF read: GPIFTRIG = 0x04 (read into EP2) +8. Set P0.7 LOW (streaming indicator) + +**Stop streaming (wValue=0):** +1. Set P0.7 HIGH (streaming stopped) +2. Write EP2FIFOBCH = 0xFF (force-flush current buffer) +3. Wait for GPIF idle (poll GPIFTRIG bit 7) +4. Write OUTPKTEND = 0x82 (skip/discard partial EP2 packet) +5. Clear config_byte bit 7 (streaming inactive) +6. Set P3 bits 7:5 = 1 (de-assert all BCM4500 control lines) + +### 8.4 Interrupt Handling + +INT4 and INT6 (GPIF/FIFO events) share a common handler that sets a software flag and clears the hardware interrupt. The main loop polls this flag, enters CPU idle mode (PCON.0) between events, and checks EP2CS for buffer availability before re-arming the GPIF. + +--- + +## 9. LNB and DiSEqC Control + +### 9.1 LNB Voltage + +LNB voltage is controlled via GPIO P0.4 on all firmware versions. No I2C is involved. + +| wValue | Voltage | GPIO P0.4 | Polarization | +|--------|---------|-----------|-------------| +| 0 | 13V | LOW | Vertical / Circular-Right | +| 1 | 18V | HIGH | Horizontal / Circular-Left | + +**USE_EXTRA_VOLT** (0x94) enables a +1V boost (13V->14V, 18V->19V) for long cable runs, by writing to XRAM 0xE0B6 (0x62=normal, 0x6A=boosted; difference is bit 3). + +### 9.2 22 kHz Tone + +The 22 kHz tone is controlled via GPIO P0.3 on all firmware versions. P0.3 gates an external 22 kHz oscillator on the PCB. The firmware does not generate the 22 kHz signal directly. + +| wValue | State | GPIO P0.3 | Band | +|--------|-------|-----------|------| +| 0 | OFF | LOW | Low band (9.75 GHz LO on universal LNB) | +| 1 | ON | HIGH | High band (10.6 GHz LO on universal LNB) | + +### 9.3 DiSEqC Protocol + +All firmware versions implement DiSEqC via Timer2-based GPIO bit-bang. The algorithm is identical across versions; only the data pin assignment differs (see Section 10). + +**Timer2 configuration (identical across all versions):** + +| Parameter | Value | +|-----------|-------| +| T2CON | 0x04 (auto-reload, running) | +| RCAP2H:RCAP2L | 0xF82F (reload = 63535) | +| CKCON.T2M | 0 (Timer2 clock = 48 MHz / 12 = 4 MHz) | +| Tick period | (65536 - 63535) / 4 MHz = 500.25 us | + +**DiSEqC timing:** + +| Parameter | Value | +|-----------|-------| +| Bit period | 1.5 ms (3 Timer2 ticks) | +| Byte period | 13.5 ms (9 bits: 8 data + 1 parity) | +| Tone burst | 12.5 ms (25 ticks) | +| Pre-TX settling delay | 7.5 ms (15 ticks) | +| Data '0' | 1.0 ms tone + 0.5 ms silence (2/3 duty) | +| Data '1' | 0.5 ms tone + 1.0 ms silence (1/3 duty) | +| Carrier frequency | 22 kHz (external oscillator gated by P0.3) | + +**Manchester encoding** (decompiled from Rev.2 FUN_CODE_213c): +``` +Each DiSEqC bit = 3 Timer2 ticks: + Tick 1: inter-bit gap (carrier OFF) + Tick 2: carrier ON via P0.3 + Tick 3: if data='1', carrier OFF early; if data='0', carrier stays ON + End: carrier OFF +``` + +**Byte transmission**: 8 data bits MSB-first + 1 odd parity bit, each encoded as a Manchester symbol. The parity bit is '1' when the number of '1' data bits is even. + +**Tone burst** (mini DiSEqC): 25 consecutive Timer2 ticks of carrier (12.5 ms). + +### 9.4 SET_DN_SWITCH (0x8F) -- Legacy Dish Network Protocol + +A 7-bit serial command bit-banged on GPIO P0.4 with specific timing: + +1. Assert P0.4 HIGH (start pulse) +2. Delay ~32 cycles +3. De-assert P0.4 +4. Delay ~8 cycles +5. Shift out 7 bits LSB-first via P0.4, with ~8 cycle delays between bits + +The Linux kernel calls this via the `dishnetwork_send_legacy_command` frontend callback. The 8th bit (0x80) of the original switch command controls LNB voltage and is sent separately via SET_LNB_VOLTAGE. + +--- + +## 10. GPIO Pin Map + +### Port 0 (SFR 0x80, a.k.a. IOA) + +| Pin | v2.06 | Rev.2 v2.10 | v2.13 | Notes | +|-----|-------|-------------|-------|-------| +| P0.0 | -- | LNB control (0x97) | **DiSEqC data** | DiSEqC data pin moved across versions | +| P0.1 | -- | -- | -- | | +| P0.2 | Init set | Init set (0x84) | Init set | BCM4500 control | +| P0.3 | **22 kHz tone** | **22 kHz tone** | **22 kHz tone** | Gates external 22 kHz oscillator | +| P0.4 | **LNB 13V/18V** | **LNB 13V/18V** + DiSEqC data | **LNB 13V/18V** | Also SET_DN_SWITCH bit-bang (all versions) | +| P0.5 | -- | GPIO status (0x98) input | -- | LNB feedback on Rev.2 | +| P0.6 | -- | GPIO control (0x97) | -- | LNB control on Rev.2 | +| P0.7 | **DiSEqC data** | Streaming indicator | Streaming indicator | DiSEqC data on v2.06 only | + +### Port 3 (SFR 0xB0) + +| Pin | Function | Notes | +|-----|----------|-------| +| P3.0 | Init HIGH | | +| P3.4 | GPIO control | Used by FUN_CODE_1fcf (Rev.2) | +| P3.5 | **TS_EN** | Transport stream enable: LOW=active, HIGH=idle | +| P3.6 | **DVB mode** | BCM4500 mode select; DiSEqC port direction (Rev.2) | +| P3.7 | BCM4500 control | De-asserted (HIGH) when streaming stops | + +### Port B (XRAM-mapped IOB) + +Used by internal debug commands 0x96--0x98: + +| Pin | v2.06/v2.13 | Rev.2 | +|-----|-------------|-------| +| IOB.0 | GPIO status input (0x98) | -- | +| IOB.1 | LNB control (0x97) | -- | +| IOB.2 | LNB control (0x97) | -- | +| IOB.3 | LNB GPIO mode (0x96) | -- | +| IOB.4 | -- | LNB GPIO mode (0x96) + control (0x97) | + +**Init values (Rev.2):** +- P0 = 0x84 (P0.7=1, P0.2=1) +- P3 = 0xE1 (P3.7:5=1, P3.0=1) +- IPL1 = 0x9E + +### DiSEqC Data Pin Summary + +| Version | Data Pin | Carrier Pin | +|---------|----------|-------------| +| v2.06 | P0.7 | P0.3 | +| Rev.2 v2.10 | P0.4 | P0.3 | +| v2.13 | P0.0 | P0.3 | + +The carrier pin (P0.3) is the same across all versions. The data pin is used only internally by the firmware's Manchester encoding logic to control whether the carrier is cut short or held for the full bit period. + +--- + +## 11. Firmware Versions + +### 11.1 Version Table + +| Firmware | Version | Build Date | PID | Functions | Binary Size | Stack Ptr | +|----------|---------|------------|-----|-----------|-------------|-----------| +| v2.06.04 | 0x020604 | 2007-07-13 | 0x0203 | 61 | ~9,472 bytes | SP=0x72 | +| Rev.2 v2.10.04 | 0x020A04 | 2010-03-12 | 0x0202 | 107 | ~8,843 bytes | SP=0x4F | +| v2.13.01 | 0x020D01 | 2010-03-12 | 0x0203 | 88 | ~9,322 bytes | SP=0x50 | + +Note: Rev.2 v2.10 targets a different product (PID 0x0202). The v2.13 family has three sub-variants (FW1/FW2/FW3) targeting different SkyWalker-1 hardware sub-revisions. + +### 11.2 GET_FW_VERS (0x92) Format + +Returns 6 bytes of hardcoded constants: + +``` +Byte 0: version minor_minor (e.g., 0x04) +Byte 1: version minor (e.g., 0x06) +Byte 2: version major (e.g., 0x02) +Byte 3: build day (e.g., 0x0D = 13) +Byte 4: build month (e.g., 0x07 = July) +Byte 5: build year - 2000 (e.g., 0x07 = 2007) +``` + +Full version number: `byte[2] << 16 | byte[1] << 8 | byte[0]` + +Kernel constants for comparison: +``` +GP8PSK_FW_REV1 = 0x020604 +GP8PSK_FW_REV2 = 0x020704 +``` + +### 11.3 Binary Comparison Matrix + +Byte-level similarity (percentage of matching bytes within shared length): + +| | v2.06 | v2.13.1 | v2.13.2 | v2.13.3 | Rev.2 | +|---|---|---|---|---|---| +| v2.06 | -- | 4.8% | 4.3% | 4.3% | 6.0% | +| v2.13.1 | | -- | 57.2% | 59.4% | 8.0% | +| v2.13.2 | | | -- | 83.5% | 5.8% | +| v2.13.3 | | | | -- | 5.8% | +| Rev.2 | | | | | -- | + +The very low similarity between major versions (4--8%) indicates complete recompilation with different linker configurations. Functions are relocated even when logic is identical. Within the v2.13 family, FW2 and FW3 are 83.5% similar (minor hardware tuning), while FW1 differs more (57--59%, different demod interface). + +### 11.4 Key Differences Between Versions + +| Feature | v2.06 | Rev.2 v2.10 | v2.13 | +|---------|-------|-------------|-------| +| Vendor commands | 30 (0x80--0x9D) | 27 (0x80--0x9A) | 30 (0x80--0x9D) | +| INT0 purpose | USB re-enumeration | USB re-enumeration | Demod availability polling | +| Demod probe at init | No | No | Yes (40 attempts at 0x7F + 0x3F) | +| Retry loops | No | No | Yes (20-attempt with checksum verification) | +| HW revision detection | No | Yes (descriptor walker) | Yes (flag `_1_3`) | +| DiSEqC data pin | P0.7 | P0.4 | P0.0 | +| Config byte IRAM | 0x6D | 0x4E | 0x4F | +| Descriptor base | 0x1200 | 0x0E00 | 0x0E00 | +| Init table address | CODE:0B46 | CODE:0B48 | CODE:0B88 | +| BCM4500 status poll | 3 registers | 3 registers | 1 register (consolidated) | +| Anti-tampering string | No | No | Yes (at offset 0x1880) | +| New commands | -- | 0x99/0x9A proto | 0x99 GET_DEMOD_STATUS, 0x9A INIT_DEMOD, 0x9C DELAY_COMMAND | +| 0x9D behavior | HW revision mode switch | N/A (out of range) | Conditional demod reset | + +### 11.5 EEPROM Format Details + +All SkyWalker-1 C2 files use uniform 1023-byte segments: + +``` +Segment Address Length +------- ------- ------ +1 0x0000 1023 Contains reset vector, interrupt handlers +2 0x03FF 1023 +3 0x07FE 1023 +4 0x0BFD 1023 +5 0x0FFC 1023 +6 0x13FB 1023 +7 0x17FA 1023 +8 0x1BF9 1023 +9 0x1FF8 1023 +10 0x23F7 varies (115--265 bytes depending on version) +``` + +### 11.6 Anti-Tampering (v2.13 only) + +At firmware offset 0x1880, all v2.13 sub-variants contain: +``` +"Tampering is detected. Attempt is logged. Warranty is voided ! \n" +``` +Followed by I2C register write commands (`01 10 aa 82 02 41 41 83`). This string and mechanism are absent from v2.06 and Rev.2 firmware. + +### 11.7 Rev.2 as Transitional Firmware + +Rev.2 v2.10.4 sits architecturally between v2.06 and v2.13: +- Adopted v2.13's stack pointer (SP=0x4F) and descriptor base (0x0E00) +- Retained v2.06's INT0 USB re-enumeration behavior +- Lacks v2.13's demodulator polling, retry loops, and 3 additional vendor commands +- Has the most functions (107) but smallest binary (~8.7 KB) due to granular decomposition +- The INT0 repurposing was the last major architectural change between Rev.2 and v2.13 + +--- + +## 12. Internal Debug Commands + +Commands 0x91 and 0x96--0x98 are not used by any driver (Linux or Windows). They appear to be manufacturing/debug interfaces. + +### 0x91 I2C_ADDR_ADJUST + +Increments (wValue != 0) or decrements (wValue == 0) an internal IRAM counter and returns its current value (1 byte). The counter is at IRAM 0x66 (v2.06) or IRAM 0x18 (v2.13/Rev.2). Likely used for I2C bus address or tuner register index adjustment during development. + +### 0x96 SET_LNB_GPIO_MODE + +Configures GPIO output enable registers for LNB voltage regulator hardware: + +| Mode | v2.06/v2.13 | Rev.2 | +|------|-------------|-------| +| Default (wValue=0) | OEB=0xF0 | OEB=0xE7, OEA=0x9E | +| Active (wValue=1) | IOB=(IOB & 0xF7) OR 0x06; OEB=0xFE | IOB.4 clear; P0.6, P0.0 set; OEA OR= 0x41 | + +### 0x97 SET_GPIO_PINS + +Direct GPIO pin write for LNB control: + +| wValue Bit | v2.06/v2.13 Target | Rev.2 Target | +|-----------|-------------------|-------------| +| bit 1 | IOB.1 | P0.6 (Port A) | +| bit 2 | IOB.2 | P0.0 (Port A) | +| bit 3 | IOB.3 | IOB.4 (Port B) | + +### 0x98 GET_GPIO_STATUS + +Returns 1 byte (0 or 1) from a single GPIO input pin -- likely an LNB overcurrent or power-good feedback signal: + +| Version | Pin Read | +|---------|----------| +| v2.06/v2.13 | IOB.0 (Port B bit 0) | +| Rev.2 | P0.5 (Port A bit 5) | + +--- + +## Sources + +### Firmware Analysis + +- Ghidra decompilation/disassembly of three firmware images: + - v2.06.04 (Ghidra port 8193) -- extracted from SkyWalker-1 EEPROM + - Rev.2 v2.10.04 (Ghidra port 8197) -- extracted from Rev.2 hardware + - v2.13.01 FW1 (Ghidra port 8194) -- extracted from Windows updater +- Firmware dumps: `/home/rpm/claude/ham/satellite/genpix/skywalker-1/firmware-dump/` + +### Driver Source + +- Linux kernel 6.16.5: `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h`, `gp8psk-fe.c`, `gp8psk-fe.h` +- Linux kernel: `drivers/media/usb/dvb-usb/dvb-usb-firmware.c` +- Windows BDA driver: `SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp` +- Windows BDA driver: `SkyWalker1_Final_Release/Include/SkyWalker1Control.h`, `SkyWalker1CommonDef.h` + +### Hardware Documentation + +- Cypress CY7C68013A (FX2LP) Technical Reference Manual +- Genpix Electronics official site: https://www.genpix-electronics.com/index.php?act=viewDoc&docId=9 +- Device `dmesg` output from running SkyWalker-1 hardware (v2.06.04 firmware) + +### Phase 1 Analysis Reports + +1. `gp8psk-driver-analysis.md` -- Linux kernel driver analysis +2. `firmware-analysis-v206-vs-v213.md` -- Cross-version firmware comparison +3. `rev2-deep-analysis.md` -- Rev.2 deep function inventory (107 functions) +4. `gpif-streaming-analysis.md` -- GPIF/MPEG-2 streaming path +5. `kernel-fw01-analysis.md` -- Kernel firmware format and EEPROM boot +6. `vendor-commands-unknown.md` -- Complete vendor command decode (0x8F, 0x91--0x98) +7. `tuning-protocol-analysis.md` -- TUNE_8PSK protocol deep dive diff --git a/tools/arc_survey.py b/tools/arc_survey.py index 2935d7e..5843f20 100644 --- a/tools/arc_survey.py +++ b/tools/arc_survey.py @@ -1,451 +1,451 @@ -#!/usr/bin/env python3 -""" -Multi-satellite arc survey for the Genpix SkyWalker-1. - -Automated "satellite census": points the dish motor to each known GEO -longitude, runs a full-band carrier survey at each position, and aggregates -results into a comprehensive sky map. The diff capability tracks changes -between survey runs. - -Usage: - python arc_survey.py --observer-lon -96.8 --slots "97W,99W,101W,103W" - python arc_survey.py --observer-lon -96.8 --file slots.json - python arc_survey.py --observer-lon -96.8 --arc -120 -60 --step 3 - python arc_survey.py --resume arc-survey-2026-02-17.json - -The tool saves progress after each orbital slot, so interrupted surveys -can be resumed. Each slot's catalog is saved individually, and a summary -report covers the entire arc. -""" - -import sys -import os -import argparse -import time -import json -from datetime import datetime, timezone -from pathlib import Path - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from skywalker_lib import SkyWalker1, usals_angle -from survey_engine import SurveyEngine -from carrier_catalog import CarrierCatalog, CATALOG_DIR - - -# Common North American GEO orbital slots -NA_ORBITAL_SLOTS = { - "129W": -129.0, "125W": -125.0, "123W": -123.0, "121W": -121.0, - "119W": -119.0, "118.7W": -118.7, "116.8W": -116.8, "114.9W": -114.9, - "113W": -113.0, "111.1W": -111.1, "110W": -110.0, "107.3W": -107.3, - "105W": -105.0, "103W": -103.0, "101W": -101.0, "99W": -99.0, - "97W": -97.0, "95W": -95.0, "93W": -93.0, "91W": -91.0, - "89W": -89.0, "87W": -87.0, "85W": -85.0, "83W": -83.0, - "82W": -82.0, "79W": -79.0, "77W": -77.0, "75W": -75.0, - "72.7W": -72.7, "70W": -70.0, "67W": -67.0, "65W": -65.0, - "63W": -63.0, "61.5W": -61.5, "58W": -58.0, "55.5W": -55.5, -} - -ARC_SURVEY_DIR = CATALOG_DIR.parent / "arc-surveys" - - -class ArcSurvey: - """Multi-position orbital arc survey with persistence and resume.""" - - def __init__(self, sw: SkyWalker1, observer_lon: float, - observer_lat: float = 0.0, settle_time: float = 15.0): - self.sw = sw - self.observer_lon = observer_lon - self.observer_lat = observer_lat - self.settle_time = settle_time - - def survey_slot(self, name: str, sat_lon: float, - coarse_step: float = 5.0, - band: str = "", pol: str = "", - callback=None) -> CarrierCatalog: - """Survey a single orbital slot: move dish, wait, run survey.""" - - # Calculate motor angle - angle = usals_angle(self.observer_lon, sat_lon, self.observer_lat) - direction = "west" if angle < 0 else "east" - - if callback: - callback("moving", 0, - f"Moving to {name} ({sat_lon:.1f}), " - f"angle {abs(angle):.1f} deg {direction}") - - # Command the motor - self.sw.motor_goto_x(self.observer_lon, sat_lon) - - # Wait for motor to settle (larger angles need more time) - settle = max(self.settle_time, abs(angle) * 0.3) - if callback: - callback("settling", 20, f"Settling {settle:.0f}s...") - time.sleep(settle) - - # Verify we have signal (check AGC for any RF energy) - sig = self.sw.signal_monitor() - if callback: - callback("signal_check", 30, - f"AGC1={sig['agc1']}, power={sig['power_db']:.1f} dB") - - # Run the six-stage survey - def survey_cb(stage, pct, msg): - overall_pct = 30 + int(pct * 0.7) - if callback: - callback(stage, overall_pct, msg) - - engine = SurveyEngine(self.sw, callback=survey_cb) - catalog = engine.run_full_scan( - coarse_step=coarse_step, - ts_capture_secs=2.0, - ) - - catalog.name = f"{name} ({sat_lon:.1f})" - catalog.band = band - catalog.pol = pol - catalog.notes = (f"Arc survey position: {name}, " - f"observer: {self.observer_lon:.2f} lon, " - f"motor angle: {angle:.2f} deg") - - if callback: - callback("complete", 100, - f"{name}: {len(catalog.carriers)} carriers, " - f"{sum(1 for c in catalog.carriers if c.locked)} locked") - - return catalog - - def run_arc(self, slots: list[tuple[str, float]], - coarse_step: float = 5.0, - band: str = "", pol: str = "", - save_individual: bool = True, - resume_state: dict | None = None) -> dict: - """Survey an entire arc of orbital slots. - - slots: list of (name, sat_lon) tuples - resume_state: previous arc survey state dict for resuming - - Returns a complete arc survey result dict. - """ - ARC_SURVEY_DIR.mkdir(parents=True, exist_ok=True) - date_str = datetime.now().strftime("%Y-%m-%d") - - # Initialize or resume state - if resume_state: - state = resume_state - completed_names = set(state.get("completed_slots", {}).keys()) - else: - state = { - "started": datetime.now(timezone.utc).isoformat(), - "observer_lon": self.observer_lon, - "observer_lat": self.observer_lat, - "total_slots": len(slots), - "completed_slots": {}, - "skipped_slots": {}, - "summary": { - "total_carriers": 0, - "total_locked": 0, - "total_services": 0, - }, - } - completed_names = set() - - state_path = ARC_SURVEY_DIR / f"arc-survey-{date_str}.json" - - for i, (name, sat_lon) in enumerate(slots): - if name in completed_names: - print(f" [{i+1}/{len(slots)}] Skipping {name} (already surveyed)") - continue - - print(f"\n [{i+1}/{len(slots)}] Surveying {name} ({sat_lon:.1f} lon)") - - def progress_cb(stage, pct, msg): - print(f" [{pct:3d}%] {stage}: {msg}") - - try: - catalog = self.survey_slot( - name, sat_lon, - coarse_step=coarse_step, - band=band, pol=pol, - callback=progress_cb, - ) - - # Save individual catalog - if save_individual: - slot_filename = f"arc-{date_str}-{name.replace('.', '_')}.json" - cat_path = catalog.save(slot_filename) - print(f" Saved: {cat_path}") - - # Update state - carrier_count = len(catalog.carriers) - locked_count = sum(1 for c in catalog.carriers if c.locked) - service_count = sum(len(c.services) for c in catalog.carriers) - - state["completed_slots"][name] = { - "sat_lon": sat_lon, - "completed": datetime.now(timezone.utc).isoformat(), - "carriers": carrier_count, - "locked": locked_count, - "services": service_count, - "catalog_file": slot_filename if save_individual else None, - } - state["summary"]["total_carriers"] += carrier_count - state["summary"]["total_locked"] += locked_count - state["summary"]["total_services"] += service_count - - except KeyboardInterrupt: - print(f"\n Survey interrupted at {name}") - try: - self.sw.motor_halt() - except Exception: - pass - state["interrupted_at"] = name - _save_state(state, state_path) - print(f" Motor halted. Progress saved to {state_path}") - print(f" Resume with: python arc_survey.py --resume {state_path}") - return state - - except Exception as e: - print(f" Error at {name}: {e}") - try: - self.sw.motor_halt() - except Exception: - pass - state["skipped_slots"][name] = { - "sat_lon": sat_lon, - "error": str(e), - } - - # Save state after each slot for resume capability - _save_state(state, state_path) - - # Final summary - state["completed"] = datetime.now(timezone.utc).isoformat() - _save_state(state, state_path) - - return state - - -def _save_state(state: dict, path: Path) -> None: - """Save arc survey state to JSON.""" - with open(path, 'w') as f: - json.dump(state, f, indent=2) - - -def parse_slot_string(slot_str: str) -> list[tuple[str, float]]: - """Parse a comma-separated slot string like '97W,99W,101W'. - - Accepts formats: '97W', '97.5W', '3E', '-97', '-97.5' - """ - slots = [] - for part in slot_str.split(','): - part = part.strip() - if not part: - continue - - if part in NA_ORBITAL_SLOTS: - slots.append((part, NA_ORBITAL_SLOTS[part])) - elif part.upper().endswith('W'): - lon = -float(part[:-1]) - slots.append((part.upper(), lon)) - elif part.upper().endswith('E'): - lon = float(part[:-1]) - slots.append((part.upper(), lon)) - else: - lon = float(part) - name = f"{abs(lon):.1f}{'W' if lon < 0 else 'E'}" - slots.append((name, lon)) - - return slots - - -def generate_arc_range(start_lon: float, stop_lon: float, - step: float) -> list[tuple[str, float]]: - """Generate orbital slots at regular intervals across an arc.""" - slots = [] - lon = start_lon - while lon <= stop_lon: - name = f"{abs(lon):.1f}{'W' if lon < 0 else 'E'}" - slots.append((name, lon)) - lon += step - return slots - - -def print_summary(state: dict) -> None: - """Print a human-readable arc survey summary.""" - print(f"\n Arc Survey Summary") - print(f" ==================") - print(f" Observer: {state['observer_lon']:.2f} lon") - print(f" Slots surveyed: {len(state['completed_slots'])} / {state['total_slots']}") - print(f" Total carriers: {state['summary']['total_carriers']}") - print(f" Total locked: {state['summary']['total_locked']}") - print(f" Total services: {state['summary']['total_services']}") - - if state.get("skipped_slots"): - print(f" Skipped: {len(state['skipped_slots'])}") - - print(f"\n Per-slot results:") - for name, info in sorted(state["completed_slots"].items(), - key=lambda x: x[1]["sat_lon"]): - lock_str = f"{info['locked']}/{info['carriers']}" - svc_str = f"{info['services']} svc" if info['services'] else "" - print(f" {name:>8s} ({info['sat_lon']:+7.1f}): " - f"{lock_str:>7s} locked {svc_str}") - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - prog="arc_survey.py", - description="Multi-satellite arc survey for SkyWalker-1", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -examples: - # Survey specific slots (North American arc) - %(prog)s --observer-lon -96.8 --slots "97W,99W,101W,103W" - - # Survey an arc range at 3-degree intervals - %(prog)s --observer-lon -96.8 --arc -120 -60 --step 3 - - # Load slots from a JSON file - %(prog)s --observer-lon -96.8 --file my-slots.json - - # Resume an interrupted survey - %(prog)s --resume ~/.skywalker1/arc-surveys/arc-survey-2026-02-17.json - - # List common North American orbital slots - %(prog)s --list-slots - -slot file format (JSON): - [ - {"name": "97W", "lon": -97.0}, - {"name": "99W", "lon": -99.0} - ] - -notes: - - Motor settle time scales with angle (min 15s, + 0.3s per degree) - - Each slot takes 5-15 minutes depending on carrier density - - Progress is saved after each slot; Ctrl-C to pause safely - - Individual catalogs saved to ~/.skywalker1/surveys/ - - Arc survey state saved to ~/.skywalker1/arc-surveys/ -""", - ) - - parser.add_argument('-v', '--verbose', action='store_true') - parser.add_argument('--observer-lon', type=float, - help="Observer longitude (negative=west, e.g. -96.8)") - parser.add_argument('--observer-lat', type=float, default=0.0, - help="Observer latitude (default: 0.0)") - - source = parser.add_mutually_exclusive_group() - source.add_argument('--slots', type=str, - help="Comma-separated slot list (e.g. '97W,99W,101W')") - source.add_argument('--file', type=str, - help="JSON file with slot definitions") - source.add_argument('--arc', nargs=2, type=float, metavar=('START', 'STOP'), - help="Arc range in degrees longitude") - source.add_argument('--resume', type=str, - help="Resume from a saved arc survey state file") - source.add_argument('--list-slots', action='store_true', - help="List common NA orbital slots and exit") - - parser.add_argument('--step', type=float, default=3.0, - help="Step size for --arc mode (default: 3.0 degrees)") - parser.add_argument('--coarse-step', type=float, default=5.0, - help="Coarse sweep step in MHz (default: 5.0)") - parser.add_argument('--settle-time', type=float, default=15.0, - help="Minimum motor settle time in seconds (default: 15)") - parser.add_argument('--pol', type=str, default="", - help="Polarization label (H/V, for catalog metadata)") - parser.add_argument('--band', type=str, default="", - help="Band label (low/high, for catalog metadata)") - - return parser - - -def main(): - parser = build_parser() - args = parser.parse_args() - - if args.list_slots: - print("Common North American GEO orbital slots:") - for name in sorted(NA_ORBITAL_SLOTS, key=lambda n: NA_ORBITAL_SLOTS[n]): - lon = NA_ORBITAL_SLOTS[name] - print(f" {name:>8s} {lon:+7.1f}") - return - - # Determine slot list - resume_state = None - - if args.resume: - with open(args.resume) as f: - resume_state = json.load(f) - observer_lon = resume_state["observer_lon"] - observer_lat = resume_state.get("observer_lat", 0.0) - # Reconstruct slots from state - all_slot_names = ( - list(resume_state.get("completed_slots", {}).keys()) + - list(resume_state.get("skipped_slots", {}).keys()) - ) - # We need the original slot list — reconstruct from completed + remaining - slots = [] - for name, info in resume_state.get("completed_slots", {}).items(): - slots.append((name, info["sat_lon"])) - for name, info in resume_state.get("skipped_slots", {}).items(): - slots.append((name, info["sat_lon"])) - # Sort by longitude - slots.sort(key=lambda x: x[1]) - print(f"Resuming arc survey: {len(resume_state.get('completed_slots', {}))} " - f"of {len(slots)} slots completed") - - else: - if not args.observer_lon and args.observer_lon != 0: - parser.error("--observer-lon is required (or use --resume)") - - observer_lon = args.observer_lon - observer_lat = args.observer_lat - - if args.slots: - slots = parse_slot_string(args.slots) - elif args.file: - with open(args.file) as f: - data = json.load(f) - slots = [(d["name"], d["lon"]) for d in data] - elif args.arc: - start, stop = sorted(args.arc) - slots = generate_arc_range(start, stop, args.step) - else: - parser.error("Specify --slots, --file, --arc, or --resume") - - if not slots: - print("No orbital slots to survey", file=sys.stderr) - sys.exit(1) - - print(f"Arc Survey") - print(f" Observer: {observer_lon:.2f} lon, {observer_lat:.2f} lat") - print(f" Orbital slots: {len(slots)}") - for name, lon in slots: - angle = usals_angle(observer_lon, lon, observer_lat) - direction = "W" if angle < 0 else "E" - print(f" {name:>8s} {lon:+7.1f} (motor: {abs(angle):.1f} deg {direction})") - print() - - with SkyWalker1(verbose=args.verbose) as sw: - sw.ensure_booted() - - survey = ArcSurvey( - sw, observer_lon, observer_lat, - settle_time=args.settle_time, - ) - - state = survey.run_arc( - slots, - coarse_step=args.coarse_step, - band=args.band, pol=args.pol, - resume_state=resume_state, - ) - - print_summary(state) - - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +""" +Multi-satellite arc survey for the Genpix SkyWalker-1. + +Automated "satellite census": points the dish motor to each known GEO +longitude, runs a full-band carrier survey at each position, and aggregates +results into a comprehensive sky map. The diff capability tracks changes +between survey runs. + +Usage: + python arc_survey.py --observer-lon -96.8 --slots "97W,99W,101W,103W" + python arc_survey.py --observer-lon -96.8 --file slots.json + python arc_survey.py --observer-lon -96.8 --arc -120 -60 --step 3 + python arc_survey.py --resume arc-survey-2026-02-17.json + +The tool saves progress after each orbital slot, so interrupted surveys +can be resumed. Each slot's catalog is saved individually, and a summary +report covers the entire arc. +""" + +import sys +import os +import argparse +import time +import json +from datetime import datetime, timezone +from pathlib import Path + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from skywalker_lib import SkyWalker1, usals_angle +from survey_engine import SurveyEngine +from carrier_catalog import CarrierCatalog, CATALOG_DIR + + +# Common North American GEO orbital slots +NA_ORBITAL_SLOTS = { + "129W": -129.0, "125W": -125.0, "123W": -123.0, "121W": -121.0, + "119W": -119.0, "118.7W": -118.7, "116.8W": -116.8, "114.9W": -114.9, + "113W": -113.0, "111.1W": -111.1, "110W": -110.0, "107.3W": -107.3, + "105W": -105.0, "103W": -103.0, "101W": -101.0, "99W": -99.0, + "97W": -97.0, "95W": -95.0, "93W": -93.0, "91W": -91.0, + "89W": -89.0, "87W": -87.0, "85W": -85.0, "83W": -83.0, + "82W": -82.0, "79W": -79.0, "77W": -77.0, "75W": -75.0, + "72.7W": -72.7, "70W": -70.0, "67W": -67.0, "65W": -65.0, + "63W": -63.0, "61.5W": -61.5, "58W": -58.0, "55.5W": -55.5, +} + +ARC_SURVEY_DIR = CATALOG_DIR.parent / "arc-surveys" + + +class ArcSurvey: + """Multi-position orbital arc survey with persistence and resume.""" + + def __init__(self, sw: SkyWalker1, observer_lon: float, + observer_lat: float = 0.0, settle_time: float = 15.0): + self.sw = sw + self.observer_lon = observer_lon + self.observer_lat = observer_lat + self.settle_time = settle_time + + def survey_slot(self, name: str, sat_lon: float, + coarse_step: float = 5.0, + band: str = "", pol: str = "", + callback=None) -> CarrierCatalog: + """Survey a single orbital slot: move dish, wait, run survey.""" + + # Calculate motor angle + angle = usals_angle(self.observer_lon, sat_lon, self.observer_lat) + direction = "west" if angle < 0 else "east" + + if callback: + callback("moving", 0, + f"Moving to {name} ({sat_lon:.1f}), " + f"angle {abs(angle):.1f} deg {direction}") + + # Command the motor + self.sw.motor_goto_x(self.observer_lon, sat_lon) + + # Wait for motor to settle (larger angles need more time) + settle = max(self.settle_time, abs(angle) * 0.3) + if callback: + callback("settling", 20, f"Settling {settle:.0f}s...") + time.sleep(settle) + + # Verify we have signal (check AGC for any RF energy) + sig = self.sw.signal_monitor() + if callback: + callback("signal_check", 30, + f"AGC1={sig['agc1']}, power={sig['power_db']:.1f} dB") + + # Run the six-stage survey + def survey_cb(stage, pct, msg): + overall_pct = 30 + int(pct * 0.7) + if callback: + callback(stage, overall_pct, msg) + + engine = SurveyEngine(self.sw, callback=survey_cb) + catalog = engine.run_full_scan( + coarse_step=coarse_step, + ts_capture_secs=2.0, + ) + + catalog.name = f"{name} ({sat_lon:.1f})" + catalog.band = band + catalog.pol = pol + catalog.notes = (f"Arc survey position: {name}, " + f"observer: {self.observer_lon:.2f} lon, " + f"motor angle: {angle:.2f} deg") + + if callback: + callback("complete", 100, + f"{name}: {len(catalog.carriers)} carriers, " + f"{sum(1 for c in catalog.carriers if c.locked)} locked") + + return catalog + + def run_arc(self, slots: list[tuple[str, float]], + coarse_step: float = 5.0, + band: str = "", pol: str = "", + save_individual: bool = True, + resume_state: dict | None = None) -> dict: + """Survey an entire arc of orbital slots. + + slots: list of (name, sat_lon) tuples + resume_state: previous arc survey state dict for resuming + + Returns a complete arc survey result dict. + """ + ARC_SURVEY_DIR.mkdir(parents=True, exist_ok=True) + date_str = datetime.now().strftime("%Y-%m-%d") + + # Initialize or resume state + if resume_state: + state = resume_state + completed_names = set(state.get("completed_slots", {}).keys()) + else: + state = { + "started": datetime.now(timezone.utc).isoformat(), + "observer_lon": self.observer_lon, + "observer_lat": self.observer_lat, + "total_slots": len(slots), + "completed_slots": {}, + "skipped_slots": {}, + "summary": { + "total_carriers": 0, + "total_locked": 0, + "total_services": 0, + }, + } + completed_names = set() + + state_path = ARC_SURVEY_DIR / f"arc-survey-{date_str}.json" + + for i, (name, sat_lon) in enumerate(slots): + if name in completed_names: + print(f" [{i+1}/{len(slots)}] Skipping {name} (already surveyed)") + continue + + print(f"\n [{i+1}/{len(slots)}] Surveying {name} ({sat_lon:.1f} lon)") + + def progress_cb(stage, pct, msg): + print(f" [{pct:3d}%] {stage}: {msg}") + + try: + catalog = self.survey_slot( + name, sat_lon, + coarse_step=coarse_step, + band=band, pol=pol, + callback=progress_cb, + ) + + # Save individual catalog + if save_individual: + slot_filename = f"arc-{date_str}-{name.replace('.', '_')}.json" + cat_path = catalog.save(slot_filename) + print(f" Saved: {cat_path}") + + # Update state + carrier_count = len(catalog.carriers) + locked_count = sum(1 for c in catalog.carriers if c.locked) + service_count = sum(len(c.services) for c in catalog.carriers) + + state["completed_slots"][name] = { + "sat_lon": sat_lon, + "completed": datetime.now(timezone.utc).isoformat(), + "carriers": carrier_count, + "locked": locked_count, + "services": service_count, + "catalog_file": slot_filename if save_individual else None, + } + state["summary"]["total_carriers"] += carrier_count + state["summary"]["total_locked"] += locked_count + state["summary"]["total_services"] += service_count + + except KeyboardInterrupt: + print(f"\n Survey interrupted at {name}") + try: + self.sw.motor_halt() + except Exception: + pass + state["interrupted_at"] = name + _save_state(state, state_path) + print(f" Motor halted. Progress saved to {state_path}") + print(f" Resume with: python arc_survey.py --resume {state_path}") + return state + + except Exception as e: + print(f" Error at {name}: {e}") + try: + self.sw.motor_halt() + except Exception: + pass + state["skipped_slots"][name] = { + "sat_lon": sat_lon, + "error": str(e), + } + + # Save state after each slot for resume capability + _save_state(state, state_path) + + # Final summary + state["completed"] = datetime.now(timezone.utc).isoformat() + _save_state(state, state_path) + + return state + + +def _save_state(state: dict, path: Path) -> None: + """Save arc survey state to JSON.""" + with open(path, 'w') as f: + json.dump(state, f, indent=2) + + +def parse_slot_string(slot_str: str) -> list[tuple[str, float]]: + """Parse a comma-separated slot string like '97W,99W,101W'. + + Accepts formats: '97W', '97.5W', '3E', '-97', '-97.5' + """ + slots = [] + for part in slot_str.split(','): + part = part.strip() + if not part: + continue + + if part in NA_ORBITAL_SLOTS: + slots.append((part, NA_ORBITAL_SLOTS[part])) + elif part.upper().endswith('W'): + lon = -float(part[:-1]) + slots.append((part.upper(), lon)) + elif part.upper().endswith('E'): + lon = float(part[:-1]) + slots.append((part.upper(), lon)) + else: + lon = float(part) + name = f"{abs(lon):.1f}{'W' if lon < 0 else 'E'}" + slots.append((name, lon)) + + return slots + + +def generate_arc_range(start_lon: float, stop_lon: float, + step: float) -> list[tuple[str, float]]: + """Generate orbital slots at regular intervals across an arc.""" + slots = [] + lon = start_lon + while lon <= stop_lon: + name = f"{abs(lon):.1f}{'W' if lon < 0 else 'E'}" + slots.append((name, lon)) + lon += step + return slots + + +def print_summary(state: dict) -> None: + """Print a human-readable arc survey summary.""" + print(f"\n Arc Survey Summary") + print(f" ==================") + print(f" Observer: {state['observer_lon']:.2f} lon") + print(f" Slots surveyed: {len(state['completed_slots'])} / {state['total_slots']}") + print(f" Total carriers: {state['summary']['total_carriers']}") + print(f" Total locked: {state['summary']['total_locked']}") + print(f" Total services: {state['summary']['total_services']}") + + if state.get("skipped_slots"): + print(f" Skipped: {len(state['skipped_slots'])}") + + print(f"\n Per-slot results:") + for name, info in sorted(state["completed_slots"].items(), + key=lambda x: x[1]["sat_lon"]): + lock_str = f"{info['locked']}/{info['carriers']}" + svc_str = f"{info['services']} svc" if info['services'] else "" + print(f" {name:>8s} ({info['sat_lon']:+7.1f}): " + f"{lock_str:>7s} locked {svc_str}") + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="arc_survey.py", + description="Multi-satellite arc survey for SkyWalker-1", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +examples: + # Survey specific slots (North American arc) + %(prog)s --observer-lon -96.8 --slots "97W,99W,101W,103W" + + # Survey an arc range at 3-degree intervals + %(prog)s --observer-lon -96.8 --arc -120 -60 --step 3 + + # Load slots from a JSON file + %(prog)s --observer-lon -96.8 --file my-slots.json + + # Resume an interrupted survey + %(prog)s --resume ~/.skywalker1/arc-surveys/arc-survey-2026-02-17.json + + # List common North American orbital slots + %(prog)s --list-slots + +slot file format (JSON): + [ + {"name": "97W", "lon": -97.0}, + {"name": "99W", "lon": -99.0} + ] + +notes: + - Motor settle time scales with angle (min 15s, + 0.3s per degree) + - Each slot takes 5-15 minutes depending on carrier density + - Progress is saved after each slot; Ctrl-C to pause safely + - Individual catalogs saved to ~/.skywalker1/surveys/ + - Arc survey state saved to ~/.skywalker1/arc-surveys/ +""", + ) + + parser.add_argument('-v', '--verbose', action='store_true') + parser.add_argument('--observer-lon', type=float, + help="Observer longitude (negative=west, e.g. -96.8)") + parser.add_argument('--observer-lat', type=float, default=0.0, + help="Observer latitude (default: 0.0)") + + source = parser.add_mutually_exclusive_group() + source.add_argument('--slots', type=str, + help="Comma-separated slot list (e.g. '97W,99W,101W')") + source.add_argument('--file', type=str, + help="JSON file with slot definitions") + source.add_argument('--arc', nargs=2, type=float, metavar=('START', 'STOP'), + help="Arc range in degrees longitude") + source.add_argument('--resume', type=str, + help="Resume from a saved arc survey state file") + source.add_argument('--list-slots', action='store_true', + help="List common NA orbital slots and exit") + + parser.add_argument('--step', type=float, default=3.0, + help="Step size for --arc mode (default: 3.0 degrees)") + parser.add_argument('--coarse-step', type=float, default=5.0, + help="Coarse sweep step in MHz (default: 5.0)") + parser.add_argument('--settle-time', type=float, default=15.0, + help="Minimum motor settle time in seconds (default: 15)") + parser.add_argument('--pol', type=str, default="", + help="Polarization label (H/V, for catalog metadata)") + parser.add_argument('--band', type=str, default="", + help="Band label (low/high, for catalog metadata)") + + return parser + + +def main(): + parser = build_parser() + args = parser.parse_args() + + if args.list_slots: + print("Common North American GEO orbital slots:") + for name in sorted(NA_ORBITAL_SLOTS, key=lambda n: NA_ORBITAL_SLOTS[n]): + lon = NA_ORBITAL_SLOTS[name] + print(f" {name:>8s} {lon:+7.1f}") + return + + # Determine slot list + resume_state = None + + if args.resume: + with open(args.resume) as f: + resume_state = json.load(f) + observer_lon = resume_state["observer_lon"] + observer_lat = resume_state.get("observer_lat", 0.0) + # Reconstruct slots from state + all_slot_names = ( + list(resume_state.get("completed_slots", {}).keys()) + + list(resume_state.get("skipped_slots", {}).keys()) + ) + # We need the original slot list — reconstruct from completed + remaining + slots = [] + for name, info in resume_state.get("completed_slots", {}).items(): + slots.append((name, info["sat_lon"])) + for name, info in resume_state.get("skipped_slots", {}).items(): + slots.append((name, info["sat_lon"])) + # Sort by longitude + slots.sort(key=lambda x: x[1]) + print(f"Resuming arc survey: {len(resume_state.get('completed_slots', {}))} " + f"of {len(slots)} slots completed") + + else: + if not args.observer_lon and args.observer_lon != 0: + parser.error("--observer-lon is required (or use --resume)") + + observer_lon = args.observer_lon + observer_lat = args.observer_lat + + if args.slots: + slots = parse_slot_string(args.slots) + elif args.file: + with open(args.file) as f: + data = json.load(f) + slots = [(d["name"], d["lon"]) for d in data] + elif args.arc: + start, stop = sorted(args.arc) + slots = generate_arc_range(start, stop, args.step) + else: + parser.error("Specify --slots, --file, --arc, or --resume") + + if not slots: + print("No orbital slots to survey", file=sys.stderr) + sys.exit(1) + + print(f"Arc Survey") + print(f" Observer: {observer_lon:.2f} lon, {observer_lat:.2f} lat") + print(f" Orbital slots: {len(slots)}") + for name, lon in slots: + angle = usals_angle(observer_lon, lon, observer_lat) + direction = "W" if angle < 0 else "E" + print(f" {name:>8s} {lon:+7.1f} (motor: {abs(angle):.1f} deg {direction})") + print() + + with SkyWalker1(verbose=args.verbose) as sw: + sw.ensure_booted() + + survey = ArcSurvey( + sw, observer_lon, observer_lat, + settle_time=args.settle_time, + ) + + state = survey.run_arc( + slots, + coarse_step=args.coarse_step, + band=args.band, pol=args.pol, + resume_state=resume_state, + ) + + print_summary(state) + + +if __name__ == "__main__": + main() diff --git a/tools/beacon_logger.py b/tools/beacon_logger.py index 9830423..851c286 100644 --- a/tools/beacon_logger.py +++ b/tools/beacon_logger.py @@ -1,376 +1,376 @@ -#!/usr/bin/env python3 -""" -Long-term satellite beacon logger for the Genpix SkyWalker-1. - -Locks onto a stable Ku-band transponder and logs SNR/AGC at configurable -intervals for hours, days, or weeks. Produces propagation datasets useful -for rain fade analysis, diurnal thermal drift measurement, antenna mount -stability assessment, and ITU propagation model validation. - -Usage: - python beacon_logger.py --freq 12015 --sr 20000 # log to stdout - python beacon_logger.py --freq 12015 --sr 20000 -o log.csv # log to CSV - python beacon_logger.py --freq 12015 --sr 20000 --daemon # background mode - python beacon_logger.py --generate-systemd # print unit file - -The tool automatically re-locks on signal loss and logs statistics per -reporting interval (min/max/mean/stddev of SNR over each window). -""" - -import sys -import os -import argparse -import time -import csv -import math -import json -import signal -from datetime import datetime, timezone - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from skywalker_lib import SkyWalker1, MODULATIONS, MOD_FEC_GROUP, FEC_RATES - - -def compute_stats(values: list[float]) -> dict: - """Compute min/max/mean/stddev for a list of measurements.""" - if not values: - return {"min": 0, "max": 0, "mean": 0, "stddev": 0, "count": 0} - - n = len(values) - mean = sum(values) / n - variance = sum((v - mean) ** 2 for v in values) / n if n > 1 else 0 - return { - "min": round(min(values), 3), - "max": round(max(values), 3), - "mean": round(mean, 3), - "stddev": round(math.sqrt(variance), 3), - "count": n, - } - - -class BeaconLogger: - """Persistent signal logger with auto-relock and statistics.""" - - def __init__(self, sw: SkyWalker1, freq_khz: int, sr_sps: int, - mod_index: int = 0, fec_index: int = 5, - sample_interval: float = 1.0, report_interval: float = 60.0): - self.sw = sw - self.freq_khz = freq_khz - self.sr_sps = sr_sps - self.mod_index = mod_index - self.fec_index = fec_index - self.sample_interval = sample_interval - self.report_interval = report_interval - - self._running = False - self._relock_count = 0 - self._total_samples = 0 - - def tune_and_lock(self) -> bool: - """Tune to the beacon frequency and check for lock.""" - self.sw.tune(self.sr_sps, self.freq_khz, self.mod_index, self.fec_index) - time.sleep(0.5) - sig = self.sw.signal_monitor() - return sig.get("locked", False) - - def run(self, duration_secs: float, csv_path: str | None = None, - json_path: str | None = None, quiet: bool = False) -> None: - """Main logging loop. - - Samples signal at sample_interval, computes statistics over - report_interval, outputs to CSV/JSON/stdout. - """ - self._running = True - - # Register signal handlers for clean shutdown - def _stop(signum, frame): - self._running = False - - signal.signal(signal.SIGTERM, _stop) - signal.signal(signal.SIGINT, _stop) - - # Initial tune - locked = self.tune_and_lock() - if not locked: - print(f"Warning: no lock at {self.freq_khz} kHz, will keep trying", - file=sys.stderr) - - # Open CSV - csv_file = None - csv_writer = None - if csv_path: - csv_file = open(csv_path, 'w', newline='') - csv_writer = csv.writer(csv_file) - csv_writer.writerow([ - "timestamp", "elapsed_s", "snr_db", "agc1", "agc2", - "power_db", "locked", "relock_count", - ]) - - # Open JSON log (append mode, one JSON object per report line) - json_file = None - if json_path: - json_file = open(json_path, 'a') - - start_time = time.time() - last_report = start_time - window_snr = [] - window_power = [] - window_agc1 = [] - lock_count_window = 0 - sample_count_window = 0 - - try: - while self._running and (time.time() - start_time) < duration_secs: - now = time.time() - elapsed = now - start_time - - # Sample - try: - sig = self.sw.signal_monitor() - except Exception as e: - if not quiet: - print(f" USB error: {e}", file=sys.stderr) - time.sleep(self.sample_interval) - continue - - self._total_samples += 1 - sample_count_window += 1 - - snr_db = sig["snr_db"] - agc1 = sig["agc1"] - agc2 = sig["agc2"] - power_db = sig["power_db"] - locked = sig["locked"] - - if locked: - lock_count_window += 1 - window_snr.append(snr_db) - window_power.append(power_db) - window_agc1.append(agc1) - - # Write raw sample to CSV - if csv_writer: - csv_writer.writerow([ - datetime.now(timezone.utc).isoformat(), - f"{elapsed:.1f}", - f"{snr_db:.3f}", - agc1, agc2, - f"{power_db:.3f}", - int(locked), - self._relock_count, - ]) - csv_file.flush() - - # Auto-relock - if not locked: - if not quiet: - print(f" [{elapsed:.0f}s] Signal lost, attempting relock...", - file=sys.stderr) - if self.tune_and_lock(): - self._relock_count += 1 - if not quiet: - print(f" [{elapsed:.0f}s] Relocked (count: {self._relock_count})", - file=sys.stderr) - - # Periodic report - if now - last_report >= self.report_interval: - report = { - "timestamp": datetime.now(timezone.utc).isoformat(), - "elapsed_s": round(elapsed, 1), - "samples": sample_count_window, - "lock_pct": round(100 * lock_count_window / max(sample_count_window, 1), 1), - "snr": compute_stats(window_snr), - "power": compute_stats(window_power), - "agc1": compute_stats(window_agc1), - "relock_count": self._relock_count, - } - - if not quiet: - snr_s = report["snr"] - print(f" [{elapsed:7.0f}s] SNR {snr_s['mean']:5.1f} dB " - f"(min {snr_s['min']:.1f}, max {snr_s['max']:.1f}, " - f"std {snr_s['stddev']:.2f}) " - f"lock {report['lock_pct']:.0f}% " - f"relocks {self._relock_count}") - - if json_file: - json_file.write(json.dumps(report) + "\n") - json_file.flush() - - # Reset window - window_snr.clear() - window_power.clear() - window_agc1.clear() - lock_count_window = 0 - sample_count_window = 0 - last_report = now - - time.sleep(self.sample_interval) - - finally: - if csv_file: - csv_file.close() - if json_file: - json_file.close() - - total_elapsed = time.time() - start_time - if not quiet: - print(f"\n Session complete: {self._total_samples} samples in " - f"{total_elapsed:.0f}s, {self._relock_count} relocks") - - -def generate_systemd_unit(args) -> str: - """Generate a systemd unit file for daemon operation.""" - cmd_parts = ["python3", os.path.abspath(__file__)] - cmd_parts.extend(["--freq", str(args.freq)]) - cmd_parts.extend(["--sr", str(args.sr)]) - if args.output: - cmd_parts.extend(["--output", os.path.abspath(args.output)]) - if args.json_output: - cmd_parts.extend(["--json-output", os.path.abspath(args.json_output)]) - cmd_parts.extend(["--duration", str(args.duration)]) - cmd_parts.extend(["--sample-interval", str(args.sample_interval)]) - cmd_parts.extend(["--report-interval", str(args.report_interval)]) - cmd_parts.append("--quiet") - - return f"""[Unit] -Description=SkyWalker-1 Beacon Logger ({args.freq} kHz) -After=network.target - -[Service] -Type=simple -ExecStart={' '.join(cmd_parts)} -Restart=on-failure -RestartSec=30 -StandardOutput=journal -StandardError=journal - -[Install] -WantedBy=multi-user.target -""" - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - prog="beacon_logger.py", - description="Long-term satellite beacon logger for SkyWalker-1", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -examples: - %(prog)s --freq 12015 --sr 20000 # Ku-band beacon, stdout - %(prog)s --freq 12015 --sr 20000 -o beacon.csv # log to CSV - %(prog)s --freq 12015 --sr 20000 --json-output beacon.jsonl # per-minute JSON - %(prog)s --freq 12015 --sr 20000 --duration 86400 # 24-hour log - %(prog)s --freq 12015 --sr 20000 --daemon # background - %(prog)s --generate-systemd --freq 12015 --sr 20000 # print unit file - -The --freq is in kHz (IF frequency), not MHz. For Ku-band with a universal -LNB at LO 10750 MHz, a transponder at 12015 MHz has IF = 12015 - 10750 = 1265 MHz, -so you'd use --freq 1265000. - -For IF frequencies, multiply MHz by 1000 (e.g., 1265 MHz = 1265000 kHz). -""", - ) - - parser.add_argument('-v', '--verbose', action='store_true') - parser.add_argument('--freq', type=int, required=True, - help="IF frequency in kHz (e.g., 1265000 for 1265 MHz)") - parser.add_argument('--sr', type=int, default=20000000, - help="Symbol rate in sps (default: 20000000)") - parser.add_argument('--mod', type=str, default="qpsk", - help="Modulation type (default: qpsk)") - parser.add_argument('--fec', type=str, default="auto", - help="FEC rate (default: auto)") - - parser.add_argument('--output', '-o', type=str, default=None, - help="CSV output file (raw samples)") - parser.add_argument('--json-output', type=str, default=None, - help="JSONL output file (per-interval statistics)") - - parser.add_argument('--duration', type=float, default=3600, - help="Logging duration in seconds (default: 3600)") - parser.add_argument('--sample-interval', type=float, default=1.0, - help="Seconds between samples (default: 1.0)") - parser.add_argument('--report-interval', type=float, default=60.0, - help="Seconds between summary reports (default: 60)") - - parser.add_argument('--pol', type=str, default=None, choices=['H', 'V'], - help="LNB polarization (H=18V, V=13V)") - parser.add_argument('--band', type=str, default=None, choices=['low', 'high'], - help="LNB band (low=no tone, high=22kHz)") - - parser.add_argument('--daemon', action='store_true', - help="Run as daemon (suppress stdout)") - parser.add_argument('--quiet', action='store_true', - help="Suppress progress output to stderr") - parser.add_argument('--generate-systemd', action='store_true', - help="Print a systemd unit file and exit") - - return parser - - -def main(): - parser = build_parser() - args = parser.parse_args() - - if args.generate_systemd: - print(generate_systemd_unit(args)) - return - - # Resolve modulation/FEC indices - mod_entry = MODULATIONS.get(args.mod) - if mod_entry is None: - print(f"Unknown modulation '{args.mod}'. Valid: {list(MODULATIONS.keys())}", - file=sys.stderr) - sys.exit(1) - mod_idx = mod_entry[0] - - fec_group = MOD_FEC_GROUP.get(args.mod, "dvbs") - fec_table = FEC_RATES.get(fec_group, {}) - fec_idx = fec_table.get(args.fec, fec_table.get("auto", 0)) - - quiet = args.daemon or args.quiet - - with SkyWalker1(verbose=args.verbose) as sw: - sw.ensure_booted() - - # Configure LNB - if args.pol: - sw.set_lnb_voltage(args.pol.upper() in ("H", "L")) - if args.band: - sw.set_22khz_tone(args.band == "high") - - freq_mhz = args.freq / 1000.0 - sr_msps = args.sr / 1e6 - - if not quiet: - print(f"Beacon Logger") - print(f" Frequency: {freq_mhz:.3f} MHz IF ({args.freq} kHz)") - print(f" Symbol rate: {sr_msps:.3f} Msps") - print(f" Modulation: {args.mod}, FEC: {args.fec}") - print(f" Sample interval: {args.sample_interval}s") - print(f" Report interval: {args.report_interval}s") - print(f" Duration: {args.duration}s ({args.duration/3600:.1f}h)") - if args.output: - print(f" CSV output: {args.output}") - if args.json_output: - print(f" JSON output: {args.json_output}") - print() - - logger = BeaconLogger( - sw, args.freq, args.sr, - mod_index=mod_idx, fec_index=fec_idx, - sample_interval=args.sample_interval, - report_interval=args.report_interval, - ) - logger.run( - duration_secs=args.duration, - csv_path=args.output, - json_path=args.json_output, - quiet=quiet, - ) - - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +""" +Long-term satellite beacon logger for the Genpix SkyWalker-1. + +Locks onto a stable Ku-band transponder and logs SNR/AGC at configurable +intervals for hours, days, or weeks. Produces propagation datasets useful +for rain fade analysis, diurnal thermal drift measurement, antenna mount +stability assessment, and ITU propagation model validation. + +Usage: + python beacon_logger.py --freq 12015 --sr 20000 # log to stdout + python beacon_logger.py --freq 12015 --sr 20000 -o log.csv # log to CSV + python beacon_logger.py --freq 12015 --sr 20000 --daemon # background mode + python beacon_logger.py --generate-systemd # print unit file + +The tool automatically re-locks on signal loss and logs statistics per +reporting interval (min/max/mean/stddev of SNR over each window). +""" + +import sys +import os +import argparse +import time +import csv +import math +import json +import signal +from datetime import datetime, timezone + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from skywalker_lib import SkyWalker1, MODULATIONS, MOD_FEC_GROUP, FEC_RATES + + +def compute_stats(values: list[float]) -> dict: + """Compute min/max/mean/stddev for a list of measurements.""" + if not values: + return {"min": 0, "max": 0, "mean": 0, "stddev": 0, "count": 0} + + n = len(values) + mean = sum(values) / n + variance = sum((v - mean) ** 2 for v in values) / n if n > 1 else 0 + return { + "min": round(min(values), 3), + "max": round(max(values), 3), + "mean": round(mean, 3), + "stddev": round(math.sqrt(variance), 3), + "count": n, + } + + +class BeaconLogger: + """Persistent signal logger with auto-relock and statistics.""" + + def __init__(self, sw: SkyWalker1, freq_khz: int, sr_sps: int, + mod_index: int = 0, fec_index: int = 5, + sample_interval: float = 1.0, report_interval: float = 60.0): + self.sw = sw + self.freq_khz = freq_khz + self.sr_sps = sr_sps + self.mod_index = mod_index + self.fec_index = fec_index + self.sample_interval = sample_interval + self.report_interval = report_interval + + self._running = False + self._relock_count = 0 + self._total_samples = 0 + + def tune_and_lock(self) -> bool: + """Tune to the beacon frequency and check for lock.""" + self.sw.tune(self.sr_sps, self.freq_khz, self.mod_index, self.fec_index) + time.sleep(0.5) + sig = self.sw.signal_monitor() + return sig.get("locked", False) + + def run(self, duration_secs: float, csv_path: str | None = None, + json_path: str | None = None, quiet: bool = False) -> None: + """Main logging loop. + + Samples signal at sample_interval, computes statistics over + report_interval, outputs to CSV/JSON/stdout. + """ + self._running = True + + # Register signal handlers for clean shutdown + def _stop(signum, frame): + self._running = False + + signal.signal(signal.SIGTERM, _stop) + signal.signal(signal.SIGINT, _stop) + + # Initial tune + locked = self.tune_and_lock() + if not locked: + print(f"Warning: no lock at {self.freq_khz} kHz, will keep trying", + file=sys.stderr) + + # Open CSV + csv_file = None + csv_writer = None + if csv_path: + csv_file = open(csv_path, 'w', newline='') + csv_writer = csv.writer(csv_file) + csv_writer.writerow([ + "timestamp", "elapsed_s", "snr_db", "agc1", "agc2", + "power_db", "locked", "relock_count", + ]) + + # Open JSON log (append mode, one JSON object per report line) + json_file = None + if json_path: + json_file = open(json_path, 'a') + + start_time = time.time() + last_report = start_time + window_snr = [] + window_power = [] + window_agc1 = [] + lock_count_window = 0 + sample_count_window = 0 + + try: + while self._running and (time.time() - start_time) < duration_secs: + now = time.time() + elapsed = now - start_time + + # Sample + try: + sig = self.sw.signal_monitor() + except Exception as e: + if not quiet: + print(f" USB error: {e}", file=sys.stderr) + time.sleep(self.sample_interval) + continue + + self._total_samples += 1 + sample_count_window += 1 + + snr_db = sig["snr_db"] + agc1 = sig["agc1"] + agc2 = sig["agc2"] + power_db = sig["power_db"] + locked = sig["locked"] + + if locked: + lock_count_window += 1 + window_snr.append(snr_db) + window_power.append(power_db) + window_agc1.append(agc1) + + # Write raw sample to CSV + if csv_writer: + csv_writer.writerow([ + datetime.now(timezone.utc).isoformat(), + f"{elapsed:.1f}", + f"{snr_db:.3f}", + agc1, agc2, + f"{power_db:.3f}", + int(locked), + self._relock_count, + ]) + csv_file.flush() + + # Auto-relock + if not locked: + if not quiet: + print(f" [{elapsed:.0f}s] Signal lost, attempting relock...", + file=sys.stderr) + if self.tune_and_lock(): + self._relock_count += 1 + if not quiet: + print(f" [{elapsed:.0f}s] Relocked (count: {self._relock_count})", + file=sys.stderr) + + # Periodic report + if now - last_report >= self.report_interval: + report = { + "timestamp": datetime.now(timezone.utc).isoformat(), + "elapsed_s": round(elapsed, 1), + "samples": sample_count_window, + "lock_pct": round(100 * lock_count_window / max(sample_count_window, 1), 1), + "snr": compute_stats(window_snr), + "power": compute_stats(window_power), + "agc1": compute_stats(window_agc1), + "relock_count": self._relock_count, + } + + if not quiet: + snr_s = report["snr"] + print(f" [{elapsed:7.0f}s] SNR {snr_s['mean']:5.1f} dB " + f"(min {snr_s['min']:.1f}, max {snr_s['max']:.1f}, " + f"std {snr_s['stddev']:.2f}) " + f"lock {report['lock_pct']:.0f}% " + f"relocks {self._relock_count}") + + if json_file: + json_file.write(json.dumps(report) + "\n") + json_file.flush() + + # Reset window + window_snr.clear() + window_power.clear() + window_agc1.clear() + lock_count_window = 0 + sample_count_window = 0 + last_report = now + + time.sleep(self.sample_interval) + + finally: + if csv_file: + csv_file.close() + if json_file: + json_file.close() + + total_elapsed = time.time() - start_time + if not quiet: + print(f"\n Session complete: {self._total_samples} samples in " + f"{total_elapsed:.0f}s, {self._relock_count} relocks") + + +def generate_systemd_unit(args) -> str: + """Generate a systemd unit file for daemon operation.""" + cmd_parts = ["python3", os.path.abspath(__file__)] + cmd_parts.extend(["--freq", str(args.freq)]) + cmd_parts.extend(["--sr", str(args.sr)]) + if args.output: + cmd_parts.extend(["--output", os.path.abspath(args.output)]) + if args.json_output: + cmd_parts.extend(["--json-output", os.path.abspath(args.json_output)]) + cmd_parts.extend(["--duration", str(args.duration)]) + cmd_parts.extend(["--sample-interval", str(args.sample_interval)]) + cmd_parts.extend(["--report-interval", str(args.report_interval)]) + cmd_parts.append("--quiet") + + return f"""[Unit] +Description=SkyWalker-1 Beacon Logger ({args.freq} kHz) +After=network.target + +[Service] +Type=simple +ExecStart={' '.join(cmd_parts)} +Restart=on-failure +RestartSec=30 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +""" + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="beacon_logger.py", + description="Long-term satellite beacon logger for SkyWalker-1", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +examples: + %(prog)s --freq 12015 --sr 20000 # Ku-band beacon, stdout + %(prog)s --freq 12015 --sr 20000 -o beacon.csv # log to CSV + %(prog)s --freq 12015 --sr 20000 --json-output beacon.jsonl # per-minute JSON + %(prog)s --freq 12015 --sr 20000 --duration 86400 # 24-hour log + %(prog)s --freq 12015 --sr 20000 --daemon # background + %(prog)s --generate-systemd --freq 12015 --sr 20000 # print unit file + +The --freq is in kHz (IF frequency), not MHz. For Ku-band with a universal +LNB at LO 10750 MHz, a transponder at 12015 MHz has IF = 12015 - 10750 = 1265 MHz, +so you'd use --freq 1265000. + +For IF frequencies, multiply MHz by 1000 (e.g., 1265 MHz = 1265000 kHz). +""", + ) + + parser.add_argument('-v', '--verbose', action='store_true') + parser.add_argument('--freq', type=int, required=True, + help="IF frequency in kHz (e.g., 1265000 for 1265 MHz)") + parser.add_argument('--sr', type=int, default=20000000, + help="Symbol rate in sps (default: 20000000)") + parser.add_argument('--mod', type=str, default="qpsk", + help="Modulation type (default: qpsk)") + parser.add_argument('--fec', type=str, default="auto", + help="FEC rate (default: auto)") + + parser.add_argument('--output', '-o', type=str, default=None, + help="CSV output file (raw samples)") + parser.add_argument('--json-output', type=str, default=None, + help="JSONL output file (per-interval statistics)") + + parser.add_argument('--duration', type=float, default=3600, + help="Logging duration in seconds (default: 3600)") + parser.add_argument('--sample-interval', type=float, default=1.0, + help="Seconds between samples (default: 1.0)") + parser.add_argument('--report-interval', type=float, default=60.0, + help="Seconds between summary reports (default: 60)") + + parser.add_argument('--pol', type=str, default=None, choices=['H', 'V'], + help="LNB polarization (H=18V, V=13V)") + parser.add_argument('--band', type=str, default=None, choices=['low', 'high'], + help="LNB band (low=no tone, high=22kHz)") + + parser.add_argument('--daemon', action='store_true', + help="Run as daemon (suppress stdout)") + parser.add_argument('--quiet', action='store_true', + help="Suppress progress output to stderr") + parser.add_argument('--generate-systemd', action='store_true', + help="Print a systemd unit file and exit") + + return parser + + +def main(): + parser = build_parser() + args = parser.parse_args() + + if args.generate_systemd: + print(generate_systemd_unit(args)) + return + + # Resolve modulation/FEC indices + mod_entry = MODULATIONS.get(args.mod) + if mod_entry is None: + print(f"Unknown modulation '{args.mod}'. Valid: {list(MODULATIONS.keys())}", + file=sys.stderr) + sys.exit(1) + mod_idx = mod_entry[0] + + fec_group = MOD_FEC_GROUP.get(args.mod, "dvbs") + fec_table = FEC_RATES.get(fec_group, {}) + fec_idx = fec_table.get(args.fec, fec_table.get("auto", 0)) + + quiet = args.daemon or args.quiet + + with SkyWalker1(verbose=args.verbose) as sw: + sw.ensure_booted() + + # Configure LNB + if args.pol: + sw.set_lnb_voltage(args.pol.upper() in ("H", "L")) + if args.band: + sw.set_22khz_tone(args.band == "high") + + freq_mhz = args.freq / 1000.0 + sr_msps = args.sr / 1e6 + + if not quiet: + print(f"Beacon Logger") + print(f" Frequency: {freq_mhz:.3f} MHz IF ({args.freq} kHz)") + print(f" Symbol rate: {sr_msps:.3f} Msps") + print(f" Modulation: {args.mod}, FEC: {args.fec}") + print(f" Sample interval: {args.sample_interval}s") + print(f" Report interval: {args.report_interval}s") + print(f" Duration: {args.duration}s ({args.duration/3600:.1f}h)") + if args.output: + print(f" CSV output: {args.output}") + if args.json_output: + print(f" JSON output: {args.json_output}") + print() + + logger = BeaconLogger( + sw, args.freq, args.sr, + mod_index=mod_idx, fec_index=fec_idx, + sample_interval=args.sample_interval, + report_interval=args.report_interval, + ) + logger.run( + duration_secs=args.duration, + csv_path=args.output, + json_path=args.json_output, + quiet=quiet, + ) + + +if __name__ == "__main__": + main() diff --git a/tools/carrier_catalog.py b/tools/carrier_catalog.py index 3bff67c..4c34ed5 100644 --- a/tools/carrier_catalog.py +++ b/tools/carrier_catalog.py @@ -1,377 +1,377 @@ -#!/usr/bin/env python3 -""" -Carrier catalog: persistent JSON storage for survey results. - -Stores detected carriers with their parameters, services, and timestamps -in ~/.skywalker1/surveys/ for historical comparison and diff reporting. -""" - -import json -import os -from datetime import datetime, timezone -from pathlib import Path - -CATALOG_DIR = Path.home() / ".skywalker1" / "surveys" - - -class CarrierEntry: - """Single carrier identification from a survey.""" - - def __init__(self, freq_khz: int = 0, sr_sps: int = 0, - modulation: str = "", fec: str = "", - power_db: float = 0.0, snr_db: float = 0.0, - locked: bool = False, services: list = None, - first_seen: str = None, last_seen: str = None, - scan_count: int = 1, bw_mhz: float = 0.0, - classification: dict = None): - self.freq_khz = freq_khz - self.sr_sps = sr_sps - self.modulation = modulation - self.fec = fec - self.power_db = power_db - self.snr_db = snr_db - self.locked = locked - self.services = services or [] - now = datetime.now(timezone.utc).isoformat() - self.first_seen = first_seen or now - self.last_seen = last_seen or now - self.scan_count = scan_count - self.bw_mhz = bw_mhz - self.classification = classification or {} - - def to_dict(self) -> dict: - return { - "freq_khz": self.freq_khz, - "sr_sps": self.sr_sps, - "modulation": self.modulation, - "fec": self.fec, - "power_db": self.power_db, - "snr_db": self.snr_db, - "locked": self.locked, - "services": self.services, - "first_seen": self.first_seen, - "last_seen": self.last_seen, - "scan_count": self.scan_count, - "bw_mhz": self.bw_mhz, - "classification": self.classification, - } - - @classmethod - def from_dict(cls, d: dict) -> "CarrierEntry": - return cls( - freq_khz=d.get("freq_khz", 0), - sr_sps=d.get("sr_sps", 0), - modulation=d.get("modulation", ""), - fec=d.get("fec", ""), - power_db=d.get("power_db", 0.0), - snr_db=d.get("snr_db", 0.0), - locked=d.get("locked", False), - services=d.get("services", []), - first_seen=d.get("first_seen"), - last_seen=d.get("last_seen"), - scan_count=d.get("scan_count", 1), - bw_mhz=d.get("bw_mhz", 0.0), - classification=d.get("classification", {}), - ) - - @property - def freq_mhz(self) -> float: - return self.freq_khz / 1000.0 - - @property - def sr_ksps(self) -> float: - return self.sr_sps / 1000.0 - - def key(self) -> str: - """Unique key for diffing: frequency rounded to nearest 500 kHz.""" - rounded = round(self.freq_khz / 500) * 500 - return str(rounded) - - def summary(self) -> str: - """One-line human-readable summary.""" - lock_str = "LOCKED" if self.locked else "no lock" - sr_str = f"{self.sr_sps / 1e6:.3f} Msps" if self.sr_sps else "SR unknown" - mod_str = self.modulation if self.modulation else "mod unknown" - svc_str = f", {len(self.services)} svc" if self.services else "" - return (f"{self.freq_mhz:.1f} MHz {self.power_db:+.1f} dB " - f"{sr_str} {mod_str} {lock_str}{svc_str}") - - def __repr__(self): - return f"" - - -class CarrierCatalog: - """Collection of carriers from a survey.""" - - def __init__(self, name: str = "", band: str = "", pol: str = "", - lnb_lo_mhz: float = 0.0, notes: str = ""): - self.name = name - self.band = band - self.pol = pol - self.lnb_lo_mhz = lnb_lo_mhz - self.notes = notes - self.created = datetime.now(timezone.utc).isoformat() - self.carriers: list[CarrierEntry] = [] - self.sweep_params: dict = {} - - def add_carrier(self, entry: CarrierEntry) -> None: - """Add a carrier entry, merging with existing if frequency matches.""" - for existing in self.carriers: - if existing.key() == entry.key(): - # Update existing entry - existing.last_seen = entry.last_seen - existing.scan_count += 1 - existing.power_db = entry.power_db - existing.snr_db = entry.snr_db - existing.locked = entry.locked - if entry.sr_sps: - existing.sr_sps = entry.sr_sps - if entry.modulation: - existing.modulation = entry.modulation - if entry.fec: - existing.fec = entry.fec - if entry.services: - existing.services = entry.services - if entry.bw_mhz: - existing.bw_mhz = entry.bw_mhz - if entry.classification: - existing.classification = entry.classification - return - self.carriers.append(entry) - - def to_dict(self) -> dict: - return { - "name": self.name, - "band": self.band, - "pol": self.pol, - "lnb_lo_mhz": self.lnb_lo_mhz, - "notes": self.notes, - "created": self.created, - "sweep_params": self.sweep_params, - "carrier_count": len(self.carriers), - "locked_count": sum(1 for c in self.carriers if c.locked), - "carriers": [c.to_dict() for c in self.carriers], - } - - @classmethod - def from_dict(cls, d: dict) -> "CarrierCatalog": - cat = cls( - name=d.get("name", ""), - band=d.get("band", ""), - pol=d.get("pol", ""), - lnb_lo_mhz=d.get("lnb_lo_mhz", 0.0), - notes=d.get("notes", ""), - ) - cat.created = d.get("created", cat.created) - cat.sweep_params = d.get("sweep_params", {}) - for cd in d.get("carriers", []): - cat.carriers.append(CarrierEntry.from_dict(cd)) - return cat - - def save(self, filename: str = None) -> Path: - """ - Save catalog to JSON in CATALOG_DIR. - - If filename is not given, generates one from date/band/pol: - survey-YYYY-MM-DD-{band}-{pol}.json - """ - CATALOG_DIR.mkdir(parents=True, exist_ok=True) - - if filename is None: - date_str = datetime.now().strftime("%Y-%m-%d") - parts = ["survey", date_str] - if self.band: - parts.append(self.band) - if self.pol: - parts.append(self.pol) - filename = "-".join(parts) + ".json" - - path = CATALOG_DIR / filename - with open(path, 'w') as f: - json.dump(self.to_dict(), f, indent=2) - return path - - @classmethod - def load(cls, filename: str) -> "CarrierCatalog": - """Load a catalog from JSON. Accepts filename or full path.""" - path = Path(filename) - if not path.is_absolute(): - path = CATALOG_DIR / filename - with open(path) as f: - data = json.load(f) - return cls.from_dict(data) - - @classmethod - def list_surveys(cls) -> list: - """List saved survey files in CATALOG_DIR, newest first.""" - if not CATALOG_DIR.exists(): - return [] - files = sorted(CATALOG_DIR.glob("survey-*.json"), reverse=True) - results = [] - for f in files: - try: - with open(f) as fh: - data = json.load(fh) - results.append({ - "filename": f.name, - "path": str(f), - "created": data.get("created", ""), - "carrier_count": data.get("carrier_count", 0), - "locked_count": data.get("locked_count", 0), - "band": data.get("band", ""), - "pol": data.get("pol", ""), - }) - except (json.JSONDecodeError, OSError): - results.append({ - "filename": f.name, - "path": str(f), - "created": "", - "carrier_count": -1, - "locked_count": -1, - "band": "", - "pol": "", - }) - return results - - def summary(self) -> str: - """Multi-line text summary of the catalog.""" - lines = [] - lines.append(f"Survey: {self.name or '(unnamed)'}") - lines.append(f"Created: {self.created}") - if self.band or self.pol: - lines.append(f"Band: {self.band} Pol: {self.pol}") - if self.lnb_lo_mhz: - lines.append(f"LNB LO: {self.lnb_lo_mhz} MHz") - lines.append(f"Carriers: {len(self.carriers)} total, " - f"{sum(1 for c in self.carriers if c.locked)} locked") - lines.append("") - for i, c in enumerate(sorted(self.carriers, key=lambda x: x.freq_khz), 1): - lines.append(f" {i:3d}. {c.summary()}") - return "\n".join(lines) - - -class CatalogDiff: - """Compare two catalog snapshots to find changes.""" - - @staticmethod - def diff(old_catalog: CarrierCatalog, - new_catalog: CarrierCatalog) -> dict: - """ - Compare old and new catalogs. - - Returns dict with: - new - carriers in new but not old - missing - carriers in old but not new - changed - carriers at same freq but different SR/power/services - stable - carriers unchanged between scans - """ - old_map = {c.key(): c for c in old_catalog.carriers} - new_map = {c.key(): c for c in new_catalog.carriers} - - old_keys = set(old_map.keys()) - new_keys = set(new_map.keys()) - - result = { - "new": [], - "missing": [], - "changed": [], - "stable": [], - } - - # New carriers - for key in sorted(new_keys - old_keys): - result["new"].append(new_map[key].to_dict()) - - # Missing carriers - for key in sorted(old_keys - new_keys): - result["missing"].append(old_map[key].to_dict()) - - # Compare common carriers - for key in sorted(old_keys & new_keys): - old_c = old_map[key] - new_c = new_map[key] - - changes = _find_changes(old_c, new_c) - if changes: - result["changed"].append({ - "carrier": new_c.to_dict(), - "previous": old_c.to_dict(), - "changes": changes, - }) - else: - result["stable"].append(new_c.to_dict()) - - return result - - @staticmethod - def format_diff(diff_result: dict) -> str: - """Format a diff result as human-readable text.""" - lines = [] - - if diff_result["new"]: - lines.append(f"NEW CARRIERS ({len(diff_result['new'])}):") - for c in diff_result["new"]: - entry = CarrierEntry.from_dict(c) - lines.append(f" + {entry.summary()}") - lines.append("") - - if diff_result["missing"]: - lines.append(f"MISSING CARRIERS ({len(diff_result['missing'])}):") - for c in diff_result["missing"]: - entry = CarrierEntry.from_dict(c) - lines.append(f" - {entry.summary()}") - lines.append("") - - if diff_result["changed"]: - lines.append(f"CHANGED CARRIERS ({len(diff_result['changed'])}):") - for item in diff_result["changed"]: - entry = CarrierEntry.from_dict(item["carrier"]) - lines.append(f" ~ {entry.summary()}") - for change in item["changes"]: - lines.append(f" {change}") - lines.append("") - - stable_count = len(diff_result["stable"]) - lines.append(f"STABLE: {stable_count} carrier(s) unchanged") - - return "\n".join(lines) - - -def _find_changes(old: CarrierEntry, new: CarrierEntry) -> list: - """Compare two carriers at the same frequency, return list of change descriptions.""" - changes = [] - - # Frequency drift (within the 500 kHz key bucket) - if abs(old.freq_khz - new.freq_khz) > 100: - changes.append(f"freq: {old.freq_khz} -> {new.freq_khz} kHz") - - # Symbol rate change - if old.sr_sps and new.sr_sps and old.sr_sps != new.sr_sps: - changes.append(f"SR: {old.sr_sps} -> {new.sr_sps} sps") - - # Power change (>2 dB is significant) - if abs(old.power_db - new.power_db) > 2.0: - changes.append(f"power: {old.power_db:+.1f} -> {new.power_db:+.1f} dB") - - # Lock state change - if old.locked != new.locked: - changes.append(f"lock: {old.locked} -> {new.locked}") - - # Modulation change - if old.modulation and new.modulation and old.modulation != new.modulation: - changes.append(f"mod: {old.modulation} -> {new.modulation}") - - # Service list change - old_svcs = set(old.services) - new_svcs = set(new.services) - if old_svcs != new_svcs: - added = new_svcs - old_svcs - removed = old_svcs - new_svcs - parts = [] - if added: - parts.append(f"+{list(added)}") - if removed: - parts.append(f"-{list(removed)}") - changes.append(f"services: {', '.join(parts)}") - - return changes +#!/usr/bin/env python3 +""" +Carrier catalog: persistent JSON storage for survey results. + +Stores detected carriers with their parameters, services, and timestamps +in ~/.skywalker1/surveys/ for historical comparison and diff reporting. +""" + +import json +import os +from datetime import datetime, timezone +from pathlib import Path + +CATALOG_DIR = Path.home() / ".skywalker1" / "surveys" + + +class CarrierEntry: + """Single carrier identification from a survey.""" + + def __init__(self, freq_khz: int = 0, sr_sps: int = 0, + modulation: str = "", fec: str = "", + power_db: float = 0.0, snr_db: float = 0.0, + locked: bool = False, services: list = None, + first_seen: str = None, last_seen: str = None, + scan_count: int = 1, bw_mhz: float = 0.0, + classification: dict = None): + self.freq_khz = freq_khz + self.sr_sps = sr_sps + self.modulation = modulation + self.fec = fec + self.power_db = power_db + self.snr_db = snr_db + self.locked = locked + self.services = services or [] + now = datetime.now(timezone.utc).isoformat() + self.first_seen = first_seen or now + self.last_seen = last_seen or now + self.scan_count = scan_count + self.bw_mhz = bw_mhz + self.classification = classification or {} + + def to_dict(self) -> dict: + return { + "freq_khz": self.freq_khz, + "sr_sps": self.sr_sps, + "modulation": self.modulation, + "fec": self.fec, + "power_db": self.power_db, + "snr_db": self.snr_db, + "locked": self.locked, + "services": self.services, + "first_seen": self.first_seen, + "last_seen": self.last_seen, + "scan_count": self.scan_count, + "bw_mhz": self.bw_mhz, + "classification": self.classification, + } + + @classmethod + def from_dict(cls, d: dict) -> "CarrierEntry": + return cls( + freq_khz=d.get("freq_khz", 0), + sr_sps=d.get("sr_sps", 0), + modulation=d.get("modulation", ""), + fec=d.get("fec", ""), + power_db=d.get("power_db", 0.0), + snr_db=d.get("snr_db", 0.0), + locked=d.get("locked", False), + services=d.get("services", []), + first_seen=d.get("first_seen"), + last_seen=d.get("last_seen"), + scan_count=d.get("scan_count", 1), + bw_mhz=d.get("bw_mhz", 0.0), + classification=d.get("classification", {}), + ) + + @property + def freq_mhz(self) -> float: + return self.freq_khz / 1000.0 + + @property + def sr_ksps(self) -> float: + return self.sr_sps / 1000.0 + + def key(self) -> str: + """Unique key for diffing: frequency rounded to nearest 500 kHz.""" + rounded = round(self.freq_khz / 500) * 500 + return str(rounded) + + def summary(self) -> str: + """One-line human-readable summary.""" + lock_str = "LOCKED" if self.locked else "no lock" + sr_str = f"{self.sr_sps / 1e6:.3f} Msps" if self.sr_sps else "SR unknown" + mod_str = self.modulation if self.modulation else "mod unknown" + svc_str = f", {len(self.services)} svc" if self.services else "" + return (f"{self.freq_mhz:.1f} MHz {self.power_db:+.1f} dB " + f"{sr_str} {mod_str} {lock_str}{svc_str}") + + def __repr__(self): + return f"" + + +class CarrierCatalog: + """Collection of carriers from a survey.""" + + def __init__(self, name: str = "", band: str = "", pol: str = "", + lnb_lo_mhz: float = 0.0, notes: str = ""): + self.name = name + self.band = band + self.pol = pol + self.lnb_lo_mhz = lnb_lo_mhz + self.notes = notes + self.created = datetime.now(timezone.utc).isoformat() + self.carriers: list[CarrierEntry] = [] + self.sweep_params: dict = {} + + def add_carrier(self, entry: CarrierEntry) -> None: + """Add a carrier entry, merging with existing if frequency matches.""" + for existing in self.carriers: + if existing.key() == entry.key(): + # Update existing entry + existing.last_seen = entry.last_seen + existing.scan_count += 1 + existing.power_db = entry.power_db + existing.snr_db = entry.snr_db + existing.locked = entry.locked + if entry.sr_sps: + existing.sr_sps = entry.sr_sps + if entry.modulation: + existing.modulation = entry.modulation + if entry.fec: + existing.fec = entry.fec + if entry.services: + existing.services = entry.services + if entry.bw_mhz: + existing.bw_mhz = entry.bw_mhz + if entry.classification: + existing.classification = entry.classification + return + self.carriers.append(entry) + + def to_dict(self) -> dict: + return { + "name": self.name, + "band": self.band, + "pol": self.pol, + "lnb_lo_mhz": self.lnb_lo_mhz, + "notes": self.notes, + "created": self.created, + "sweep_params": self.sweep_params, + "carrier_count": len(self.carriers), + "locked_count": sum(1 for c in self.carriers if c.locked), + "carriers": [c.to_dict() for c in self.carriers], + } + + @classmethod + def from_dict(cls, d: dict) -> "CarrierCatalog": + cat = cls( + name=d.get("name", ""), + band=d.get("band", ""), + pol=d.get("pol", ""), + lnb_lo_mhz=d.get("lnb_lo_mhz", 0.0), + notes=d.get("notes", ""), + ) + cat.created = d.get("created", cat.created) + cat.sweep_params = d.get("sweep_params", {}) + for cd in d.get("carriers", []): + cat.carriers.append(CarrierEntry.from_dict(cd)) + return cat + + def save(self, filename: str = None) -> Path: + """ + Save catalog to JSON in CATALOG_DIR. + + If filename is not given, generates one from date/band/pol: + survey-YYYY-MM-DD-{band}-{pol}.json + """ + CATALOG_DIR.mkdir(parents=True, exist_ok=True) + + if filename is None: + date_str = datetime.now().strftime("%Y-%m-%d") + parts = ["survey", date_str] + if self.band: + parts.append(self.band) + if self.pol: + parts.append(self.pol) + filename = "-".join(parts) + ".json" + + path = CATALOG_DIR / filename + with open(path, 'w') as f: + json.dump(self.to_dict(), f, indent=2) + return path + + @classmethod + def load(cls, filename: str) -> "CarrierCatalog": + """Load a catalog from JSON. Accepts filename or full path.""" + path = Path(filename) + if not path.is_absolute(): + path = CATALOG_DIR / filename + with open(path) as f: + data = json.load(f) + return cls.from_dict(data) + + @classmethod + def list_surveys(cls) -> list: + """List saved survey files in CATALOG_DIR, newest first.""" + if not CATALOG_DIR.exists(): + return [] + files = sorted(CATALOG_DIR.glob("survey-*.json"), reverse=True) + results = [] + for f in files: + try: + with open(f) as fh: + data = json.load(fh) + results.append({ + "filename": f.name, + "path": str(f), + "created": data.get("created", ""), + "carrier_count": data.get("carrier_count", 0), + "locked_count": data.get("locked_count", 0), + "band": data.get("band", ""), + "pol": data.get("pol", ""), + }) + except (json.JSONDecodeError, OSError): + results.append({ + "filename": f.name, + "path": str(f), + "created": "", + "carrier_count": -1, + "locked_count": -1, + "band": "", + "pol": "", + }) + return results + + def summary(self) -> str: + """Multi-line text summary of the catalog.""" + lines = [] + lines.append(f"Survey: {self.name or '(unnamed)'}") + lines.append(f"Created: {self.created}") + if self.band or self.pol: + lines.append(f"Band: {self.band} Pol: {self.pol}") + if self.lnb_lo_mhz: + lines.append(f"LNB LO: {self.lnb_lo_mhz} MHz") + lines.append(f"Carriers: {len(self.carriers)} total, " + f"{sum(1 for c in self.carriers if c.locked)} locked") + lines.append("") + for i, c in enumerate(sorted(self.carriers, key=lambda x: x.freq_khz), 1): + lines.append(f" {i:3d}. {c.summary()}") + return "\n".join(lines) + + +class CatalogDiff: + """Compare two catalog snapshots to find changes.""" + + @staticmethod + def diff(old_catalog: CarrierCatalog, + new_catalog: CarrierCatalog) -> dict: + """ + Compare old and new catalogs. + + Returns dict with: + new - carriers in new but not old + missing - carriers in old but not new + changed - carriers at same freq but different SR/power/services + stable - carriers unchanged between scans + """ + old_map = {c.key(): c for c in old_catalog.carriers} + new_map = {c.key(): c for c in new_catalog.carriers} + + old_keys = set(old_map.keys()) + new_keys = set(new_map.keys()) + + result = { + "new": [], + "missing": [], + "changed": [], + "stable": [], + } + + # New carriers + for key in sorted(new_keys - old_keys): + result["new"].append(new_map[key].to_dict()) + + # Missing carriers + for key in sorted(old_keys - new_keys): + result["missing"].append(old_map[key].to_dict()) + + # Compare common carriers + for key in sorted(old_keys & new_keys): + old_c = old_map[key] + new_c = new_map[key] + + changes = _find_changes(old_c, new_c) + if changes: + result["changed"].append({ + "carrier": new_c.to_dict(), + "previous": old_c.to_dict(), + "changes": changes, + }) + else: + result["stable"].append(new_c.to_dict()) + + return result + + @staticmethod + def format_diff(diff_result: dict) -> str: + """Format a diff result as human-readable text.""" + lines = [] + + if diff_result["new"]: + lines.append(f"NEW CARRIERS ({len(diff_result['new'])}):") + for c in diff_result["new"]: + entry = CarrierEntry.from_dict(c) + lines.append(f" + {entry.summary()}") + lines.append("") + + if diff_result["missing"]: + lines.append(f"MISSING CARRIERS ({len(diff_result['missing'])}):") + for c in diff_result["missing"]: + entry = CarrierEntry.from_dict(c) + lines.append(f" - {entry.summary()}") + lines.append("") + + if diff_result["changed"]: + lines.append(f"CHANGED CARRIERS ({len(diff_result['changed'])}):") + for item in diff_result["changed"]: + entry = CarrierEntry.from_dict(item["carrier"]) + lines.append(f" ~ {entry.summary()}") + for change in item["changes"]: + lines.append(f" {change}") + lines.append("") + + stable_count = len(diff_result["stable"]) + lines.append(f"STABLE: {stable_count} carrier(s) unchanged") + + return "\n".join(lines) + + +def _find_changes(old: CarrierEntry, new: CarrierEntry) -> list: + """Compare two carriers at the same frequency, return list of change descriptions.""" + changes = [] + + # Frequency drift (within the 500 kHz key bucket) + if abs(old.freq_khz - new.freq_khz) > 100: + changes.append(f"freq: {old.freq_khz} -> {new.freq_khz} kHz") + + # Symbol rate change + if old.sr_sps and new.sr_sps and old.sr_sps != new.sr_sps: + changes.append(f"SR: {old.sr_sps} -> {new.sr_sps} sps") + + # Power change (>2 dB is significant) + if abs(old.power_db - new.power_db) > 2.0: + changes.append(f"power: {old.power_db:+.1f} -> {new.power_db:+.1f} dB") + + # Lock state change + if old.locked != new.locked: + changes.append(f"lock: {old.locked} -> {new.locked}") + + # Modulation change + if old.modulation and new.modulation and old.modulation != new.modulation: + changes.append(f"mod: {old.modulation} -> {new.modulation}") + + # Service list change + old_svcs = set(old.services) + new_svcs = set(new.services) + if old_svcs != new_svcs: + added = new_svcs - old_svcs + removed = old_svcs - new_svcs + parts = [] + if added: + parts.append(f"+{list(added)}") + if removed: + parts.append(f"-{list(removed)}") + changes.append(f"services: {', '.join(parts)}") + + return changes diff --git a/tools/eeprom_probe.py b/tools/eeprom_probe.py index 5d7bcd2..5845eec 100644 --- a/tools/eeprom_probe.py +++ b/tools/eeprom_probe.py @@ -1,101 +1,101 @@ -#!/usr/bin/env python3 -"""Probe I2C EEPROM address setting on Genpix SkyWalker-1.""" -import usb.core, usb.util, time - -dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) -for cfg in dev: - for intf in cfg: - if dev.is_kernel_driver_active(intf.bInterfaceNumber): - dev.detach_kernel_driver(intf.bInterfaceNumber) - break -try: - dev.set_configuration() -except: - pass - -I2C_READ = 0x84 -I2C_WRITE = 0x83 - -def vin(req, value=0, index=0, length=64): - try: - return dev.ctrl_transfer( - usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, - req, value, index, length, 2000) - except: - return None - -def vout(req, value=0, index=0, data=b''): - try: - return dev.ctrl_transfer( - usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT, - req, value, index, data, 2000) - except Exception as e: - print(f" OUT error: {e}") - return None - -# Known first 8 bytes at offset 0: c2 c0 09 03 02 00 00 40 -# Known bytes at offset 8 (from our read): 03 ff 00 00 02 18 8d 30 -REF_0 = "c2 c0 09 03 02 00 00 40" -REF_8 = "03 ff 00 00 02 18 8d 30" - -def show(label, data): - if data is not None: - h = bytes(data[:8]).hex(' ') - match = "" - if h == REF_0: match = " <-- OFFSET 0" - elif h == REF_8: match = " <-- OFFSET 8" - print(f" {label}: {h}{match}") - else: - print(f" {label}: FAILED") - -print("=== Approach 1: I2C_WRITE data=[addr_h, addr_l], then I2C_READ ===") -vout(I2C_WRITE, 0x51, 0, bytes([0x00, 0x08])) -show("After set 0x0008", vin(I2C_READ, 0x51, 0, 8)) -vout(I2C_WRITE, 0x51, 0, bytes([0x00, 0x00])) -show("After set 0x0000", vin(I2C_READ, 0x51, 0, 8)) - -print("\n=== Approach 2: wValue=addr, wIndex=slave ===") -vout(I2C_WRITE, 0x0008, 0x51) -show("After set 0x0008", vin(I2C_READ, 0x51, 0, 8)) -vout(I2C_WRITE, 0x0000, 0x51) -show("After set 0x0000", vin(I2C_READ, 0x51, 0, 8)) - -print("\n=== Approach 3: wValue=slave, wIndex=addr ===") -vout(I2C_WRITE, 0x51, 0x0008) -show("After set 0x0008", vin(I2C_READ, 0x51, 0, 8)) -vout(I2C_WRITE, 0x51, 0x0000) -show("After set 0x0000", vin(I2C_READ, 0x51, 0, 8)) - -print("\n=== Approach 4: I2C_READ with wIndex=offset ===") -show("wIndex=0x0000", vin(I2C_READ, 0x51, 0x0000, 8)) -show("wIndex=0x0008", vin(I2C_READ, 0x51, 0x0008, 8)) -show("wIndex=0x0040", vin(I2C_READ, 0x51, 0x0040, 8)) - -print("\n=== Approach 5: I2C_READ with (slave<<8|offset) in wValue ===") -show("wValue=0x5100", vin(I2C_READ, 0x5100, 0, 8)) -show("wValue=0x5108", vin(I2C_READ, 0x5108, 0, 8)) - -print("\n=== Approach 6: I2C_READ with wValue=offset (no slave) ===") -show("wValue=0x0000", vin(I2C_READ, 0x0000, 0, 8)) -show("wValue=0x0008", vin(I2C_READ, 0x0008, 0, 8)) -show("wValue=0x0040", vin(I2C_READ, 0x0040, 0, 8)) - -print("\n=== Approach 7: Larger reads to check page boundaries ===") -data = vin(I2C_READ, 0x51, 0, 64) -if data: - show("First 8 of 64", data[:8]) - show("Bytes 8-15", data[8:16]) - show("Bytes 56-63", data[56:64]) - # Check if bytes 8-15 match REF_8 - if bytes(data[8:16]).hex(' ') == REF_8: - print(" ** Bytes 8-15 match expected offset 8 data!") - print(" ** The 64-byte read IS returning sequential EEPROM data!") - -# Reattach -for cfg in dev: - for intf in cfg: - try: - usb.util.release_interface(dev, intf.bInterfaceNumber) - dev.attach_kernel_driver(intf.bInterfaceNumber) - except: - pass +#!/usr/bin/env python3 +"""Probe I2C EEPROM address setting on Genpix SkyWalker-1.""" +import usb.core, usb.util, time + +dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) +for cfg in dev: + for intf in cfg: + if dev.is_kernel_driver_active(intf.bInterfaceNumber): + dev.detach_kernel_driver(intf.bInterfaceNumber) + break +try: + dev.set_configuration() +except: + pass + +I2C_READ = 0x84 +I2C_WRITE = 0x83 + +def vin(req, value=0, index=0, length=64): + try: + return dev.ctrl_transfer( + usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, + req, value, index, length, 2000) + except: + return None + +def vout(req, value=0, index=0, data=b''): + try: + return dev.ctrl_transfer( + usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT, + req, value, index, data, 2000) + except Exception as e: + print(f" OUT error: {e}") + return None + +# Known first 8 bytes at offset 0: c2 c0 09 03 02 00 00 40 +# Known bytes at offset 8 (from our read): 03 ff 00 00 02 18 8d 30 +REF_0 = "c2 c0 09 03 02 00 00 40" +REF_8 = "03 ff 00 00 02 18 8d 30" + +def show(label, data): + if data is not None: + h = bytes(data[:8]).hex(' ') + match = "" + if h == REF_0: match = " <-- OFFSET 0" + elif h == REF_8: match = " <-- OFFSET 8" + print(f" {label}: {h}{match}") + else: + print(f" {label}: FAILED") + +print("=== Approach 1: I2C_WRITE data=[addr_h, addr_l], then I2C_READ ===") +vout(I2C_WRITE, 0x51, 0, bytes([0x00, 0x08])) +show("After set 0x0008", vin(I2C_READ, 0x51, 0, 8)) +vout(I2C_WRITE, 0x51, 0, bytes([0x00, 0x00])) +show("After set 0x0000", vin(I2C_READ, 0x51, 0, 8)) + +print("\n=== Approach 2: wValue=addr, wIndex=slave ===") +vout(I2C_WRITE, 0x0008, 0x51) +show("After set 0x0008", vin(I2C_READ, 0x51, 0, 8)) +vout(I2C_WRITE, 0x0000, 0x51) +show("After set 0x0000", vin(I2C_READ, 0x51, 0, 8)) + +print("\n=== Approach 3: wValue=slave, wIndex=addr ===") +vout(I2C_WRITE, 0x51, 0x0008) +show("After set 0x0008", vin(I2C_READ, 0x51, 0, 8)) +vout(I2C_WRITE, 0x51, 0x0000) +show("After set 0x0000", vin(I2C_READ, 0x51, 0, 8)) + +print("\n=== Approach 4: I2C_READ with wIndex=offset ===") +show("wIndex=0x0000", vin(I2C_READ, 0x51, 0x0000, 8)) +show("wIndex=0x0008", vin(I2C_READ, 0x51, 0x0008, 8)) +show("wIndex=0x0040", vin(I2C_READ, 0x51, 0x0040, 8)) + +print("\n=== Approach 5: I2C_READ with (slave<<8|offset) in wValue ===") +show("wValue=0x5100", vin(I2C_READ, 0x5100, 0, 8)) +show("wValue=0x5108", vin(I2C_READ, 0x5108, 0, 8)) + +print("\n=== Approach 6: I2C_READ with wValue=offset (no slave) ===") +show("wValue=0x0000", vin(I2C_READ, 0x0000, 0, 8)) +show("wValue=0x0008", vin(I2C_READ, 0x0008, 0, 8)) +show("wValue=0x0040", vin(I2C_READ, 0x0040, 0, 8)) + +print("\n=== Approach 7: Larger reads to check page boundaries ===") +data = vin(I2C_READ, 0x51, 0, 64) +if data: + show("First 8 of 64", data[:8]) + show("Bytes 8-15", data[8:16]) + show("Bytes 56-63", data[56:64]) + # Check if bytes 8-15 match REF_8 + if bytes(data[8:16]).hex(' ') == REF_8: + print(" ** Bytes 8-15 match expected offset 8 data!") + print(" ** The 64-byte read IS returning sequential EEPROM data!") + +# Reattach +for cfg in dev: + for intf in cfg: + try: + usb.util.release_interface(dev, intf.bInterfaceNumber) + dev.attach_kernel_driver(intf.bInterfaceNumber) + except: + pass diff --git a/tools/fw_dump.py b/tools/fw_dump.py index 05a45d7..b83c8f2 100644 --- a/tools/fw_dump.py +++ b/tools/fw_dump.py @@ -1,292 +1,292 @@ -#!/usr/bin/env python3 -""" -Genpix SkyWalker-1 firmware probe and dump tool. - -The SkyWalker-1 uses a Cypress FX2 (EZ-USB) microcontroller. -FX2 devices support reading internal RAM (8KB at 0x0000-0x1FFF) -and external RAM via standard vendor requests: - - bRequest=0xA0 (FX2 firmware load/read) - - wValue=address, wIndex=0 - -This tool also queries Genpix-specific vendor commands to gather -device info before attempting a firmware dump. -""" - -import sys -import struct -import argparse -from datetime import datetime - -try: - import usb.core - import usb.util -except ImportError: - print("pyusb required: pip install pyusb") - sys.exit(1) - -VENDOR_ID = 0x09C0 -PRODUCT_ID = 0x0203 - -# Genpix vendor commands (from SkyWalker1Control.h) -CMD_GET_USB_SPEED = 0x07 -CMD_FW_VERSION_READ = 0x0B -CMD_VENDOR_STRING_READ = 0x0C -CMD_PRODUCT_STRING_READ = 0x0D -CMD_RESET_FX2 = 0x13 -CMD_FW_BCD_VERSION_READ = 0x14 -CMD_GET_8PSK_CONFIG = 0x80 -CMD_GET_SIGNAL_STRENGTH = 0x87 -CMD_GET_SIGNAL_LOCK = 0x90 -CMD_GET_SERIAL_NUMBER = 0x93 - -# FX2 standard vendor request for RAM access -FX2_RAM_REQUEST = 0xA0 - -# FX2 memory map -FX2_INTERNAL_RAM_SIZE = 0x2000 # 8KB internal RAM -FX2_EXTERNAL_RAM_SIZE = 0x10000 # Up to 64KB external - -# Config status bits -CONFIG_BITS = { - 0x01: "8PSK Started", - 0x02: "BCM4500 FW Loaded", - 0x04: "Intersil LNB On", - 0x08: "DVB Mode", - 0x10: "22kHz Tone", - 0x20: "18V Selected", - 0x40: "DC Tuned", - 0x80: "Armed (streaming)", -} - - -def find_device(): - dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID) - if dev is None: - print("SkyWalker-1 not found. Is it plugged in?") - sys.exit(1) - return dev - - -def vendor_in(dev, request, value=0, index=0, length=64, timeout=2000): - """Send a vendor IN control transfer (device-to-host).""" - try: - return dev.ctrl_transfer( - usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, - request, value, index, length, timeout - ) - except usb.core.USBError as e: - return None - - -def detach_kernel_driver(dev): - """Detach kernel driver if attached.""" - for cfg in dev: - for intf in cfg: - if dev.is_kernel_driver_active(intf.bInterfaceNumber): - try: - dev.detach_kernel_driver(intf.bInterfaceNumber) - print(f" Detached kernel driver from interface {intf.bInterfaceNumber}") - return intf.bInterfaceNumber - except usb.core.USBError as e: - print(f" Warning: Could not detach kernel driver: {e}") - print(" Try running with sudo, or: sudo modprobe -r dvb_usb_gp8psk") - sys.exit(1) - return None - - -def probe_device_info(dev): - """Query all known Genpix info commands.""" - print("\n=== Genpix SkyWalker-1 Device Info ===\n") - - # Firmware version (6 bytes) - data = vendor_in(dev, CMD_FW_VERSION_READ, length=6) - if data is not None and len(data) == 6: - fw_int = (data[2] << 16) | (data[1] << 8) | data[0] - build_date = f"20{data[5]:02d}-{data[4]:02d}-{data[3]:02d}" - print(f" FW Version: {data[2]}.{data[1]:02d}.{data[0]} (0x{fw_int:06x})") - print(f" FW Build: {build_date}") - else: - print(f" FW Version: (failed: {data})") - - # BCD version - data = vendor_in(dev, CMD_FW_BCD_VERSION_READ, length=2) - if data is not None: - print(f" BCD Version: {bytes(data).hex()}") - - # Vendor string - data = vendor_in(dev, CMD_VENDOR_STRING_READ, length=64) - if data is not None: - s = bytes(data).rstrip(b'\x00').decode('ascii', errors='replace') - print(f" Vendor: {s}") - - # Product string - data = vendor_in(dev, CMD_PRODUCT_STRING_READ, length=64) - if data is not None: - s = bytes(data).rstrip(b'\x00').decode('ascii', errors='replace') - print(f" Product: {s}") - - # USB speed - data = vendor_in(dev, CMD_GET_USB_SPEED, length=1) - if data is not None: - speeds = {0: "Low", 1: "Full (12Mbps)", 2: "High (480Mbps)"} - print(f" USB Speed: {speeds.get(data[0], f'Unknown ({data[0]})')}") - - # Serial number - data = vendor_in(dev, CMD_GET_SERIAL_NUMBER, length=8) - if data is not None: - print(f" Serial: {bytes(data).hex()} ({bytes(data).rstrip(b'\\x00').decode('ascii', errors='replace')})") - - # 8PSK config/status - data = vendor_in(dev, CMD_GET_8PSK_CONFIG, length=1) - if data is not None: - status = data[0] - print(f" Config: 0x{status:02x}") - for bit, desc in CONFIG_BITS.items(): - state = "ON" if status & bit else "off" - print(f" [{state:>3}] {desc}") - - print() - - -def dump_fx2_ram(dev, output_file, start=0x0000, size=FX2_INTERNAL_RAM_SIZE, chunk=64): - """ - Attempt to read FX2 internal RAM using the standard FX2 vendor request 0xA0. - - The Cypress FX2 bootloader/firmware typically supports: - - bRequest = 0xA0 - - wValue = start address - - wIndex = 0 - - Direction = IN (device to host) - """ - print(f"=== Attempting FX2 RAM dump: 0x{start:04X} - 0x{start+size-1:04X} ({size} bytes) ===\n") - - firmware = bytearray() - addr = start - errors = 0 - consecutive_errors = 0 - - while addr < start + size: - remaining = (start + size) - addr - read_len = min(chunk, remaining) - - data = vendor_in(dev, FX2_RAM_REQUEST, value=addr, index=0, length=read_len) - - if data is None: - errors += 1 - consecutive_errors += 1 - firmware.extend(b'\xff' * read_len) - if consecutive_errors >= 5: - print(f"\n Stopped: {consecutive_errors} consecutive read failures at 0x{addr:04X}") - print(" Device may not support FX2 RAM readback (EEPROM firmware)") - break - else: - consecutive_errors = 0 - firmware.extend(data) - - if (addr - start) % 0x400 == 0: - pct = ((addr - start) / size) * 100 - print(f" 0x{addr:04X} [{pct:5.1f}%] {'OK' if data is not None else 'FAIL'}", end='\r') - - addr += read_len - - print(f"\n\n Read {len(firmware)} bytes, {errors} chunk errors") - - if firmware and any(b != 0xFF for b in firmware): - with open(output_file, 'wb') as f: - f.write(firmware) - print(f" Saved to: {output_file}") - - # Quick analysis - non_ff = sum(1 for b in firmware if b != 0xFF) - non_zero = sum(1 for b in firmware if b != 0x00) - print(f" Non-0xFF bytes: {non_ff}/{len(firmware)}") - print(f" Non-0x00 bytes: {non_zero}/{len(firmware)}") - - # Check for FX2 reset vector - if len(firmware) >= 3: - print(f" First 16 bytes: {firmware[:16].hex(' ')}") - if firmware[0] == 0x02: - jump_addr = (firmware[1] << 8) | firmware[2] - print(f" Reset vector: LJMP 0x{jump_addr:04X} (typical FX2 firmware)") - else: - print(" No valid data read — dump appears empty") - - return firmware - - -def scan_vendor_commands(dev, start=0x00, end=0xFF): - """Brute-force scan all vendor IN commands to find undocumented ones.""" - print(f"=== Scanning vendor commands 0x{start:02X}-0x{end:02X} ===\n") - found = [] - for cmd in range(start, end + 1): - data = vendor_in(dev, cmd, length=64, timeout=500) - if data is not None and len(data) > 0: - preview = bytes(data[:16]).hex(' ') - is_known = cmd in ( - CMD_GET_USB_SPEED, CMD_FW_VERSION_READ, CMD_VENDOR_STRING_READ, - CMD_PRODUCT_STRING_READ, CMD_FW_BCD_VERSION_READ, CMD_GET_8PSK_CONFIG, - CMD_GET_SIGNAL_STRENGTH, CMD_GET_SIGNAL_LOCK, CMD_GET_SERIAL_NUMBER, - FX2_RAM_REQUEST, - ) - marker = " [KNOWN]" if is_known else " [NEW!]" - print(f" 0x{cmd:02X}: [{len(data):3d} bytes] {preview}...{marker}") - found.append((cmd, data)) - print(f"\n Found {len(found)} responding commands") - return found - - -def main(): - parser = argparse.ArgumentParser(description="Genpix SkyWalker-1 firmware probe/dump tool") - parser.add_argument('--info', action='store_true', help="Query device info") - parser.add_argument('--dump', metavar='FILE', help="Dump FX2 RAM to file") - parser.add_argument('--scan', action='store_true', help="Scan all vendor commands") - parser.add_argument('--start', type=lambda x: int(x, 0), default=0x0000, - help="RAM dump start address (default: 0x0000)") - parser.add_argument('--size', type=lambda x: int(x, 0), default=FX2_INTERNAL_RAM_SIZE, - help=f"RAM dump size (default: 0x{FX2_INTERNAL_RAM_SIZE:X})") - parser.add_argument('--external', action='store_true', - help="Try to dump external RAM (64KB)") - args = parser.parse_args() - - if not any([args.info, args.dump, args.scan]): - args.info = True - args.scan = True - - print(f"Genpix SkyWalker-1 Firmware Tool") - print(f"{'=' * 40}") - - dev = find_device() - print(f"\nFound device: Bus {dev.bus} Addr {dev.address}") - intf = detach_kernel_driver(dev) - - try: - dev.set_configuration() - except usb.core.USBError: - pass # May already be configured - - try: - if args.info: - probe_device_info(dev) - - if args.scan: - scan_vendor_commands(dev) - print() - - if args.dump: - if args.external: - dump_fx2_ram(dev, args.dump, args.start, FX2_EXTERNAL_RAM_SIZE) - else: - dump_fx2_ram(dev, args.dump, args.start, args.size) - - finally: - if intf is not None: - try: - usb.util.release_interface(dev, intf) - dev.attach_kernel_driver(intf) - print("\nRe-attached kernel driver") - except usb.core.USBError: - print("\nNote: Run 'sudo modprobe dvb_usb_gp8psk' to reload driver") - - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 +""" +Genpix SkyWalker-1 firmware probe and dump tool. + +The SkyWalker-1 uses a Cypress FX2 (EZ-USB) microcontroller. +FX2 devices support reading internal RAM (8KB at 0x0000-0x1FFF) +and external RAM via standard vendor requests: + - bRequest=0xA0 (FX2 firmware load/read) + - wValue=address, wIndex=0 + +This tool also queries Genpix-specific vendor commands to gather +device info before attempting a firmware dump. +""" + +import sys +import struct +import argparse +from datetime import datetime + +try: + import usb.core + import usb.util +except ImportError: + print("pyusb required: pip install pyusb") + sys.exit(1) + +VENDOR_ID = 0x09C0 +PRODUCT_ID = 0x0203 + +# Genpix vendor commands (from SkyWalker1Control.h) +CMD_GET_USB_SPEED = 0x07 +CMD_FW_VERSION_READ = 0x0B +CMD_VENDOR_STRING_READ = 0x0C +CMD_PRODUCT_STRING_READ = 0x0D +CMD_RESET_FX2 = 0x13 +CMD_FW_BCD_VERSION_READ = 0x14 +CMD_GET_8PSK_CONFIG = 0x80 +CMD_GET_SIGNAL_STRENGTH = 0x87 +CMD_GET_SIGNAL_LOCK = 0x90 +CMD_GET_SERIAL_NUMBER = 0x93 + +# FX2 standard vendor request for RAM access +FX2_RAM_REQUEST = 0xA0 + +# FX2 memory map +FX2_INTERNAL_RAM_SIZE = 0x2000 # 8KB internal RAM +FX2_EXTERNAL_RAM_SIZE = 0x10000 # Up to 64KB external + +# Config status bits +CONFIG_BITS = { + 0x01: "8PSK Started", + 0x02: "BCM4500 FW Loaded", + 0x04: "Intersil LNB On", + 0x08: "DVB Mode", + 0x10: "22kHz Tone", + 0x20: "18V Selected", + 0x40: "DC Tuned", + 0x80: "Armed (streaming)", +} + + +def find_device(): + dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID) + if dev is None: + print("SkyWalker-1 not found. Is it plugged in?") + sys.exit(1) + return dev + + +def vendor_in(dev, request, value=0, index=0, length=64, timeout=2000): + """Send a vendor IN control transfer (device-to-host).""" + try: + return dev.ctrl_transfer( + usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, + request, value, index, length, timeout + ) + except usb.core.USBError as e: + return None + + +def detach_kernel_driver(dev): + """Detach kernel driver if attached.""" + for cfg in dev: + for intf in cfg: + if dev.is_kernel_driver_active(intf.bInterfaceNumber): + try: + dev.detach_kernel_driver(intf.bInterfaceNumber) + print(f" Detached kernel driver from interface {intf.bInterfaceNumber}") + return intf.bInterfaceNumber + except usb.core.USBError as e: + print(f" Warning: Could not detach kernel driver: {e}") + print(" Try running with sudo, or: sudo modprobe -r dvb_usb_gp8psk") + sys.exit(1) + return None + + +def probe_device_info(dev): + """Query all known Genpix info commands.""" + print("\n=== Genpix SkyWalker-1 Device Info ===\n") + + # Firmware version (6 bytes) + data = vendor_in(dev, CMD_FW_VERSION_READ, length=6) + if data is not None and len(data) == 6: + fw_int = (data[2] << 16) | (data[1] << 8) | data[0] + build_date = f"20{data[5]:02d}-{data[4]:02d}-{data[3]:02d}" + print(f" FW Version: {data[2]}.{data[1]:02d}.{data[0]} (0x{fw_int:06x})") + print(f" FW Build: {build_date}") + else: + print(f" FW Version: (failed: {data})") + + # BCD version + data = vendor_in(dev, CMD_FW_BCD_VERSION_READ, length=2) + if data is not None: + print(f" BCD Version: {bytes(data).hex()}") + + # Vendor string + data = vendor_in(dev, CMD_VENDOR_STRING_READ, length=64) + if data is not None: + s = bytes(data).rstrip(b'\x00').decode('ascii', errors='replace') + print(f" Vendor: {s}") + + # Product string + data = vendor_in(dev, CMD_PRODUCT_STRING_READ, length=64) + if data is not None: + s = bytes(data).rstrip(b'\x00').decode('ascii', errors='replace') + print(f" Product: {s}") + + # USB speed + data = vendor_in(dev, CMD_GET_USB_SPEED, length=1) + if data is not None: + speeds = {0: "Low", 1: "Full (12Mbps)", 2: "High (480Mbps)"} + print(f" USB Speed: {speeds.get(data[0], f'Unknown ({data[0]})')}") + + # Serial number + data = vendor_in(dev, CMD_GET_SERIAL_NUMBER, length=8) + if data is not None: + print(f" Serial: {bytes(data).hex()} ({bytes(data).rstrip(b'\\x00').decode('ascii', errors='replace')})") + + # 8PSK config/status + data = vendor_in(dev, CMD_GET_8PSK_CONFIG, length=1) + if data is not None: + status = data[0] + print(f" Config: 0x{status:02x}") + for bit, desc in CONFIG_BITS.items(): + state = "ON" if status & bit else "off" + print(f" [{state:>3}] {desc}") + + print() + + +def dump_fx2_ram(dev, output_file, start=0x0000, size=FX2_INTERNAL_RAM_SIZE, chunk=64): + """ + Attempt to read FX2 internal RAM using the standard FX2 vendor request 0xA0. + + The Cypress FX2 bootloader/firmware typically supports: + - bRequest = 0xA0 + - wValue = start address + - wIndex = 0 + - Direction = IN (device to host) + """ + print(f"=== Attempting FX2 RAM dump: 0x{start:04X} - 0x{start+size-1:04X} ({size} bytes) ===\n") + + firmware = bytearray() + addr = start + errors = 0 + consecutive_errors = 0 + + while addr < start + size: + remaining = (start + size) - addr + read_len = min(chunk, remaining) + + data = vendor_in(dev, FX2_RAM_REQUEST, value=addr, index=0, length=read_len) + + if data is None: + errors += 1 + consecutive_errors += 1 + firmware.extend(b'\xff' * read_len) + if consecutive_errors >= 5: + print(f"\n Stopped: {consecutive_errors} consecutive read failures at 0x{addr:04X}") + print(" Device may not support FX2 RAM readback (EEPROM firmware)") + break + else: + consecutive_errors = 0 + firmware.extend(data) + + if (addr - start) % 0x400 == 0: + pct = ((addr - start) / size) * 100 + print(f" 0x{addr:04X} [{pct:5.1f}%] {'OK' if data is not None else 'FAIL'}", end='\r') + + addr += read_len + + print(f"\n\n Read {len(firmware)} bytes, {errors} chunk errors") + + if firmware and any(b != 0xFF for b in firmware): + with open(output_file, 'wb') as f: + f.write(firmware) + print(f" Saved to: {output_file}") + + # Quick analysis + non_ff = sum(1 for b in firmware if b != 0xFF) + non_zero = sum(1 for b in firmware if b != 0x00) + print(f" Non-0xFF bytes: {non_ff}/{len(firmware)}") + print(f" Non-0x00 bytes: {non_zero}/{len(firmware)}") + + # Check for FX2 reset vector + if len(firmware) >= 3: + print(f" First 16 bytes: {firmware[:16].hex(' ')}") + if firmware[0] == 0x02: + jump_addr = (firmware[1] << 8) | firmware[2] + print(f" Reset vector: LJMP 0x{jump_addr:04X} (typical FX2 firmware)") + else: + print(" No valid data read — dump appears empty") + + return firmware + + +def scan_vendor_commands(dev, start=0x00, end=0xFF): + """Brute-force scan all vendor IN commands to find undocumented ones.""" + print(f"=== Scanning vendor commands 0x{start:02X}-0x{end:02X} ===\n") + found = [] + for cmd in range(start, end + 1): + data = vendor_in(dev, cmd, length=64, timeout=500) + if data is not None and len(data) > 0: + preview = bytes(data[:16]).hex(' ') + is_known = cmd in ( + CMD_GET_USB_SPEED, CMD_FW_VERSION_READ, CMD_VENDOR_STRING_READ, + CMD_PRODUCT_STRING_READ, CMD_FW_BCD_VERSION_READ, CMD_GET_8PSK_CONFIG, + CMD_GET_SIGNAL_STRENGTH, CMD_GET_SIGNAL_LOCK, CMD_GET_SERIAL_NUMBER, + FX2_RAM_REQUEST, + ) + marker = " [KNOWN]" if is_known else " [NEW!]" + print(f" 0x{cmd:02X}: [{len(data):3d} bytes] {preview}...{marker}") + found.append((cmd, data)) + print(f"\n Found {len(found)} responding commands") + return found + + +def main(): + parser = argparse.ArgumentParser(description="Genpix SkyWalker-1 firmware probe/dump tool") + parser.add_argument('--info', action='store_true', help="Query device info") + parser.add_argument('--dump', metavar='FILE', help="Dump FX2 RAM to file") + parser.add_argument('--scan', action='store_true', help="Scan all vendor commands") + parser.add_argument('--start', type=lambda x: int(x, 0), default=0x0000, + help="RAM dump start address (default: 0x0000)") + parser.add_argument('--size', type=lambda x: int(x, 0), default=FX2_INTERNAL_RAM_SIZE, + help=f"RAM dump size (default: 0x{FX2_INTERNAL_RAM_SIZE:X})") + parser.add_argument('--external', action='store_true', + help="Try to dump external RAM (64KB)") + args = parser.parse_args() + + if not any([args.info, args.dump, args.scan]): + args.info = True + args.scan = True + + print(f"Genpix SkyWalker-1 Firmware Tool") + print(f"{'=' * 40}") + + dev = find_device() + print(f"\nFound device: Bus {dev.bus} Addr {dev.address}") + intf = detach_kernel_driver(dev) + + try: + dev.set_configuration() + except usb.core.USBError: + pass # May already be configured + + try: + if args.info: + probe_device_info(dev) + + if args.scan: + scan_vendor_commands(dev) + print() + + if args.dump: + if args.external: + dump_fx2_ram(dev, args.dump, args.start, FX2_EXTERNAL_RAM_SIZE) + else: + dump_fx2_ram(dev, args.dump, args.start, args.size) + + finally: + if intf is not None: + try: + usb.util.release_interface(dev, intf) + dev.attach_kernel_driver(intf) + print("\nRe-attached kernel driver") + except usb.core.USBError: + print("\nNote: Run 'sudo modprobe dvb_usb_gp8psk' to reload driver") + + +if __name__ == '__main__': + main() diff --git a/tools/h21cm.py b/tools/h21cm.py index 3eb242d..f2e761b 100644 --- a/tools/h21cm.py +++ b/tools/h21cm.py @@ -1,357 +1,357 @@ -#!/usr/bin/env python3 -""" -Hydrogen 21 cm drift-scan radiometer for the Genpix SkyWalker-1. - -Detects neutral hydrogen emission at 1420.405 MHz — directly in the IF range -with no LNB required. Connect an L-band antenna (patch, helical, or horn) -directly to the F-connector. - -The Milky Way's spiral arms create a velocity-dispersed emission profile -detectable even with the BCM4500's ~346 kHz RBW. Earth's rotation provides -a natural drift-scan across the sky. - -Usage: - python h21cm.py # single sweep, print spectrum - python h21cm.py --drift --duration 3600 # 1-hour drift scan - python h21cm.py --drift --motor-step 5 # step motor between sweeps - python h21cm.py --output data.csv # log to CSV - -The c in 21 cm stands for centimeters. The frequency (1420.405 MHz) comes from -the hyperfine transition in neutral hydrogen — when the electron's spin flips -relative to the proton. This is the most fundamental spectral line in radio -astronomy, and you can detect it with a $30 DVB-S dongle. -""" - -import sys -import os -import argparse -import time -import csv -import math -from datetime import datetime, timezone - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from skywalker_lib import SkyWalker1, agc_to_power_db - - -# Physical constants -H1_FREQ_MHZ = 1420.405751 # Hydrogen 21 cm rest frequency -C_KM_S = 299792.458 # Speed of light - - -def freq_to_velocity(freq_mhz: float) -> float: - """Convert observed frequency to radial velocity via Doppler shift. - - v = c * (f_rest - f_obs) / f_rest - - Positive velocity = receding (redshifted, lower frequency). - Negative velocity = approaching (blueshifted, higher frequency). - """ - return C_KM_S * (H1_FREQ_MHZ - freq_mhz) / H1_FREQ_MHZ - - -def sweep_h1_band(sw: SkyWalker1, center_mhz: float = H1_FREQ_MHZ, - span_mhz: float = 4.0, step_mhz: float = 0.5, - dwell_ms: int = 50, averages: int = 1) -> dict: - """Sweep the hydrogen line band and return power measurements. - - Higher dwell_ms and multiple averages improve SNR for this weak signal. - Default 50ms dwell is 5x longer than typical satellite sweeps. - - Returns dict with frequencies, powers, velocities, and statistics. - """ - start = center_mhz - span_mhz / 2 - stop = center_mhz + span_mhz / 2 - - # Accumulate multiple sweeps for averaging - all_powers = None - for avg in range(averages): - freqs, powers, raw = sw.sweep_spectrum( - start, stop, step_mhz=step_mhz, dwell_ms=dwell_ms, - sr_ksps=1000, mod_index=0, fec_index=5, - ) - if all_powers is None: - all_powers = [0.0] * len(powers) - for i in range(len(powers)): - all_powers[i] += powers[i] - - # Average - avg_powers = [p / averages for p in all_powers] - - # Calculate velocities - velocities = [freq_to_velocity(f) for f in freqs] - - # Baseline: edges of the band should be "empty" (no hydrogen) - edge_count = max(2, len(avg_powers) // 5) - baseline = (sum(avg_powers[:edge_count]) + sum(avg_powers[-edge_count:])) / (2 * edge_count) - - # Excess power above baseline - excess = [p - baseline for p in avg_powers] - - # Find peak excess (the hydrogen line center) - peak_idx = max(range(len(excess)), key=lambda i: excess[i]) - peak_freq = freqs[peak_idx] - peak_excess = excess[peak_idx] - peak_velocity = velocities[peak_idx] - - return { - "timestamp": datetime.now(timezone.utc).isoformat(), - "freqs_mhz": freqs, - "powers_db": avg_powers, - "velocities_km_s": velocities, - "excess_db": excess, - "baseline_db": baseline, - "peak_freq_mhz": peak_freq, - "peak_excess_db": peak_excess, - "peak_velocity_km_s": peak_velocity, - "averages": averages, - "dwell_ms": dwell_ms, - } - - -def sweep_control_band(sw: SkyWalker1, step_mhz: float = 0.5, - dwell_ms: int = 50) -> dict: - """Sweep a control band (1430-1434 MHz) where no hydrogen is expected. - - Comparing the control band to the hydrogen band reveals whether a - detected power bump is real emission or just system noise variation. - """ - freqs, powers, _ = sw.sweep_spectrum( - 1430.0, 1434.0, step_mhz=step_mhz, dwell_ms=dwell_ms, - sr_ksps=1000, mod_index=0, fec_index=5, - ) - mean_power = sum(powers) / len(powers) if powers else 0 - return { - "control_freqs_mhz": freqs, - "control_powers_db": powers, - "control_mean_db": mean_power, - } - - -def print_spectrum(result: dict, show_velocity: bool = True) -> None: - """Print an ASCII spectrum of the hydrogen band.""" - freqs = result["freqs_mhz"] - excess = result["excess_db"] - velocities = result["velocities_km_s"] - baseline = result["baseline_db"] - - # Scale for display - max_excess = max(excess) if excess else 1.0 - min_excess = min(excess) - span = max(max_excess - min_excess, 0.5) - - print(f"\n Hydrogen 21 cm Spectrum") - print(f" Baseline: {baseline:.2f} dB | Peak excess: {result['peak_excess_db']:.2f} dB") - print(f" Peak at {result['peak_freq_mhz']:.3f} MHz ({result['peak_velocity_km_s']:+.1f} km/s)") - print() - - bar_width = 50 - for i in range(len(freqs)): - f = freqs[i] - e = excess[i] - v = velocities[i] - - # Normalize to bar width - filled = int((e - min_excess) / span * bar_width) - filled = max(0, min(filled, bar_width)) - bar = '#' * filled + '-' * (bar_width - filled) - - # Mark the hydrogen rest frequency - marker = " *" if abs(f - H1_FREQ_MHZ) < 0.3 else " " - - if show_velocity: - print(f" {f:8.3f} MHz {v:+7.1f} km/s [{bar}] {e:+.2f} dB{marker}") - else: - print(f" {f:8.3f} MHz [{bar}] {e:+.2f} dB{marker}") - - print() - print(" * = hydrogen rest frequency (1420.405 MHz)") - - -def drift_scan(sw: SkyWalker1, duration_secs: float, interval_secs: float, - step_mhz: float, dwell_ms: int, averages: int, - motor_step: int, output_path: str | None) -> None: - """Run a drift scan: repeated sweeps over time. - - Earth's rotation naturally scans the sky. Each sweep captures the - hydrogen profile at the current sky position. Over hours, you trace - out the galactic plane. - """ - csv_writer = None - csv_file = None - header_written = False - - if output_path: - csv_file = open(output_path, 'w', newline='') - csv_writer = csv.writer(csv_file) - - start_time = time.time() - scan_num = 0 - - try: - while time.time() - start_time < duration_secs: - scan_num += 1 - elapsed = time.time() - start_time - remaining = duration_secs - elapsed - - print(f"\n--- Scan #{scan_num} (elapsed {elapsed:.0f}s, " - f"remaining {remaining:.0f}s) ---") - - # Motor step between scans (for declination scanning) - if motor_step and scan_num > 1: - print(f" Stepping motor {motor_step} steps east...") - sw.motor_drive_east(motor_step) - time.sleep(1.0) - - result = sweep_h1_band(sw, step_mhz=step_mhz, - dwell_ms=dwell_ms, averages=averages) - print_spectrum(result, show_velocity=True) - - # Write CSV - if csv_writer: - if not header_written: - csv_writer.writerow([ - "timestamp", "scan_num", "freq_mhz", "power_db", - "excess_db", "velocity_km_s", "baseline_db", - ]) - header_written = True - - for i in range(len(result["freqs_mhz"])): - csv_writer.writerow([ - result["timestamp"], - scan_num, - f"{result['freqs_mhz'][i]:.3f}", - f"{result['powers_db'][i]:.3f}", - f"{result['excess_db'][i]:.3f}", - f"{result['velocities_km_s'][i]:.1f}", - f"{result['baseline_db']:.3f}", - ]) - csv_file.flush() - - # Wait for next scan - if remaining > interval_secs: - print(f" Next scan in {interval_secs:.0f}s...") - time.sleep(interval_secs) - - except KeyboardInterrupt: - print("\n Drift scan interrupted") - finally: - if csv_file: - csv_file.close() - print(f" Data saved to {output_path}") - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - prog="h21cm.py", - description="Hydrogen 21 cm line radiometer for SkyWalker-1", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -examples: - %(prog)s # single sweep, print spectrum - %(prog)s --averages 8 # 8x averaging for better SNR - %(prog)s --drift --duration 3600 # 1-hour drift scan - %(prog)s --drift --motor-step 5 # step motor between sweeps - %(prog)s --output h21cm-data.csv # log to CSV - %(prog)s --control # include control band comparison - -notes: - - Connect an L-band antenna directly to the F-connector (no LNB) - - LNB power is disabled automatically for direct input - - Hydrogen emission is weak; use --averages 4-16 for best results - - The --dwell option increases per-step integration time (default 50ms) - - Earth rotation provides natural sky drift at ~15 deg/hour -""", - ) - - parser.add_argument('-v', '--verbose', action='store_true', - help="Show raw USB traffic") - parser.add_argument('--center', type=float, default=H1_FREQ_MHZ, - help=f"Center frequency in MHz (default: {H1_FREQ_MHZ})") - parser.add_argument('--span', type=float, default=4.0, - help="Frequency span in MHz (default: 4.0)") - parser.add_argument('--step', type=float, default=0.5, - help="Frequency step in MHz (default: 0.5)") - parser.add_argument('--dwell', type=int, default=50, - help="Dwell time per step in ms (default: 50)") - parser.add_argument('--averages', type=int, default=1, - help="Number of sweeps to average (default: 1)") - parser.add_argument('--output', '-o', type=str, default=None, - help="CSV output file path") - parser.add_argument('--control', action='store_true', - help="Include control band (1430-1434 MHz) for comparison") - parser.add_argument('--no-velocity', action='store_true', - help="Don't show velocity axis in spectrum display") - - drift_group = parser.add_argument_group('drift scan') - drift_group.add_argument('--drift', action='store_true', - help="Enable drift scan mode (repeated sweeps)") - drift_group.add_argument('--duration', type=float, default=3600, - help="Drift scan duration in seconds (default: 3600)") - drift_group.add_argument('--interval', type=float, default=60, - help="Seconds between sweeps (default: 60)") - drift_group.add_argument('--motor-step', type=int, default=0, - help="Motor steps between sweeps (0=no motor, default: 0)") - - return parser - - -def main(): - parser = build_parser() - args = parser.parse_args() - - with SkyWalker1(verbose=args.verbose) as sw: - sw.ensure_booted() - - # Disable LNB power for direct input - sw.start_intersil(on=False) - print("LNB power disabled (direct L-band input mode)") - - if args.drift: - drift_scan(sw, duration_secs=args.duration, - interval_secs=args.interval, - step_mhz=args.step, dwell_ms=args.dwell, - averages=args.averages, motor_step=args.motor_step, - output_path=args.output) - else: - # Single sweep - print(f"\nSweeping {args.center - args.span/2:.1f} - " - f"{args.center + args.span/2:.1f} MHz " - f"(step={args.step} MHz, dwell={args.dwell}ms, " - f"avg={args.averages}x)") - - result = sweep_h1_band(sw, center_mhz=args.center, - span_mhz=args.span, step_mhz=args.step, - dwell_ms=args.dwell, averages=args.averages) - print_spectrum(result, show_velocity=not args.no_velocity) - - if args.control: - print(" Control band (1430-1434 MHz, no hydrogen expected):") - ctrl = sweep_control_band(sw, step_mhz=args.step, dwell_ms=args.dwell) - print(f" Control mean: {ctrl['control_mean_db']:.2f} dB") - print(f" H1 baseline: {result['baseline_db']:.2f} dB") - diff = result["peak_excess_db"] - print(f" H1 peak excess above baseline: {diff:+.2f} dB") - - # Write single sweep to CSV if requested - if args.output: - with open(args.output, 'w', newline='') as f: - writer = csv.writer(f) - writer.writerow([ - "freq_mhz", "power_db", "excess_db", - "velocity_km_s", "baseline_db", - ]) - for i in range(len(result["freqs_mhz"])): - writer.writerow([ - f"{result['freqs_mhz'][i]:.3f}", - f"{result['powers_db'][i]:.3f}", - f"{result['excess_db'][i]:.3f}", - f"{result['velocities_km_s'][i]:.1f}", - f"{result['baseline_db']:.3f}", - ]) - print(f" Data saved to {args.output}") - - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +""" +Hydrogen 21 cm drift-scan radiometer for the Genpix SkyWalker-1. + +Detects neutral hydrogen emission at 1420.405 MHz — directly in the IF range +with no LNB required. Connect an L-band antenna (patch, helical, or horn) +directly to the F-connector. + +The Milky Way's spiral arms create a velocity-dispersed emission profile +detectable even with the BCM4500's ~346 kHz RBW. Earth's rotation provides +a natural drift-scan across the sky. + +Usage: + python h21cm.py # single sweep, print spectrum + python h21cm.py --drift --duration 3600 # 1-hour drift scan + python h21cm.py --drift --motor-step 5 # step motor between sweeps + python h21cm.py --output data.csv # log to CSV + +The c in 21 cm stands for centimeters. The frequency (1420.405 MHz) comes from +the hyperfine transition in neutral hydrogen — when the electron's spin flips +relative to the proton. This is the most fundamental spectral line in radio +astronomy, and you can detect it with a $30 DVB-S dongle. +""" + +import sys +import os +import argparse +import time +import csv +import math +from datetime import datetime, timezone + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from skywalker_lib import SkyWalker1, agc_to_power_db + + +# Physical constants +H1_FREQ_MHZ = 1420.405751 # Hydrogen 21 cm rest frequency +C_KM_S = 299792.458 # Speed of light + + +def freq_to_velocity(freq_mhz: float) -> float: + """Convert observed frequency to radial velocity via Doppler shift. + + v = c * (f_rest - f_obs) / f_rest + + Positive velocity = receding (redshifted, lower frequency). + Negative velocity = approaching (blueshifted, higher frequency). + """ + return C_KM_S * (H1_FREQ_MHZ - freq_mhz) / H1_FREQ_MHZ + + +def sweep_h1_band(sw: SkyWalker1, center_mhz: float = H1_FREQ_MHZ, + span_mhz: float = 4.0, step_mhz: float = 0.5, + dwell_ms: int = 50, averages: int = 1) -> dict: + """Sweep the hydrogen line band and return power measurements. + + Higher dwell_ms and multiple averages improve SNR for this weak signal. + Default 50ms dwell is 5x longer than typical satellite sweeps. + + Returns dict with frequencies, powers, velocities, and statistics. + """ + start = center_mhz - span_mhz / 2 + stop = center_mhz + span_mhz / 2 + + # Accumulate multiple sweeps for averaging + all_powers = None + for avg in range(averages): + freqs, powers, raw = sw.sweep_spectrum( + start, stop, step_mhz=step_mhz, dwell_ms=dwell_ms, + sr_ksps=1000, mod_index=0, fec_index=5, + ) + if all_powers is None: + all_powers = [0.0] * len(powers) + for i in range(len(powers)): + all_powers[i] += powers[i] + + # Average + avg_powers = [p / averages for p in all_powers] + + # Calculate velocities + velocities = [freq_to_velocity(f) for f in freqs] + + # Baseline: edges of the band should be "empty" (no hydrogen) + edge_count = max(2, len(avg_powers) // 5) + baseline = (sum(avg_powers[:edge_count]) + sum(avg_powers[-edge_count:])) / (2 * edge_count) + + # Excess power above baseline + excess = [p - baseline for p in avg_powers] + + # Find peak excess (the hydrogen line center) + peak_idx = max(range(len(excess)), key=lambda i: excess[i]) + peak_freq = freqs[peak_idx] + peak_excess = excess[peak_idx] + peak_velocity = velocities[peak_idx] + + return { + "timestamp": datetime.now(timezone.utc).isoformat(), + "freqs_mhz": freqs, + "powers_db": avg_powers, + "velocities_km_s": velocities, + "excess_db": excess, + "baseline_db": baseline, + "peak_freq_mhz": peak_freq, + "peak_excess_db": peak_excess, + "peak_velocity_km_s": peak_velocity, + "averages": averages, + "dwell_ms": dwell_ms, + } + + +def sweep_control_band(sw: SkyWalker1, step_mhz: float = 0.5, + dwell_ms: int = 50) -> dict: + """Sweep a control band (1430-1434 MHz) where no hydrogen is expected. + + Comparing the control band to the hydrogen band reveals whether a + detected power bump is real emission or just system noise variation. + """ + freqs, powers, _ = sw.sweep_spectrum( + 1430.0, 1434.0, step_mhz=step_mhz, dwell_ms=dwell_ms, + sr_ksps=1000, mod_index=0, fec_index=5, + ) + mean_power = sum(powers) / len(powers) if powers else 0 + return { + "control_freqs_mhz": freqs, + "control_powers_db": powers, + "control_mean_db": mean_power, + } + + +def print_spectrum(result: dict, show_velocity: bool = True) -> None: + """Print an ASCII spectrum of the hydrogen band.""" + freqs = result["freqs_mhz"] + excess = result["excess_db"] + velocities = result["velocities_km_s"] + baseline = result["baseline_db"] + + # Scale for display + max_excess = max(excess) if excess else 1.0 + min_excess = min(excess) + span = max(max_excess - min_excess, 0.5) + + print(f"\n Hydrogen 21 cm Spectrum") + print(f" Baseline: {baseline:.2f} dB | Peak excess: {result['peak_excess_db']:.2f} dB") + print(f" Peak at {result['peak_freq_mhz']:.3f} MHz ({result['peak_velocity_km_s']:+.1f} km/s)") + print() + + bar_width = 50 + for i in range(len(freqs)): + f = freqs[i] + e = excess[i] + v = velocities[i] + + # Normalize to bar width + filled = int((e - min_excess) / span * bar_width) + filled = max(0, min(filled, bar_width)) + bar = '#' * filled + '-' * (bar_width - filled) + + # Mark the hydrogen rest frequency + marker = " *" if abs(f - H1_FREQ_MHZ) < 0.3 else " " + + if show_velocity: + print(f" {f:8.3f} MHz {v:+7.1f} km/s [{bar}] {e:+.2f} dB{marker}") + else: + print(f" {f:8.3f} MHz [{bar}] {e:+.2f} dB{marker}") + + print() + print(" * = hydrogen rest frequency (1420.405 MHz)") + + +def drift_scan(sw: SkyWalker1, duration_secs: float, interval_secs: float, + step_mhz: float, dwell_ms: int, averages: int, + motor_step: int, output_path: str | None) -> None: + """Run a drift scan: repeated sweeps over time. + + Earth's rotation naturally scans the sky. Each sweep captures the + hydrogen profile at the current sky position. Over hours, you trace + out the galactic plane. + """ + csv_writer = None + csv_file = None + header_written = False + + if output_path: + csv_file = open(output_path, 'w', newline='') + csv_writer = csv.writer(csv_file) + + start_time = time.time() + scan_num = 0 + + try: + while time.time() - start_time < duration_secs: + scan_num += 1 + elapsed = time.time() - start_time + remaining = duration_secs - elapsed + + print(f"\n--- Scan #{scan_num} (elapsed {elapsed:.0f}s, " + f"remaining {remaining:.0f}s) ---") + + # Motor step between scans (for declination scanning) + if motor_step and scan_num > 1: + print(f" Stepping motor {motor_step} steps east...") + sw.motor_drive_east(motor_step) + time.sleep(1.0) + + result = sweep_h1_band(sw, step_mhz=step_mhz, + dwell_ms=dwell_ms, averages=averages) + print_spectrum(result, show_velocity=True) + + # Write CSV + if csv_writer: + if not header_written: + csv_writer.writerow([ + "timestamp", "scan_num", "freq_mhz", "power_db", + "excess_db", "velocity_km_s", "baseline_db", + ]) + header_written = True + + for i in range(len(result["freqs_mhz"])): + csv_writer.writerow([ + result["timestamp"], + scan_num, + f"{result['freqs_mhz'][i]:.3f}", + f"{result['powers_db'][i]:.3f}", + f"{result['excess_db'][i]:.3f}", + f"{result['velocities_km_s'][i]:.1f}", + f"{result['baseline_db']:.3f}", + ]) + csv_file.flush() + + # Wait for next scan + if remaining > interval_secs: + print(f" Next scan in {interval_secs:.0f}s...") + time.sleep(interval_secs) + + except KeyboardInterrupt: + print("\n Drift scan interrupted") + finally: + if csv_file: + csv_file.close() + print(f" Data saved to {output_path}") + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="h21cm.py", + description="Hydrogen 21 cm line radiometer for SkyWalker-1", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +examples: + %(prog)s # single sweep, print spectrum + %(prog)s --averages 8 # 8x averaging for better SNR + %(prog)s --drift --duration 3600 # 1-hour drift scan + %(prog)s --drift --motor-step 5 # step motor between sweeps + %(prog)s --output h21cm-data.csv # log to CSV + %(prog)s --control # include control band comparison + +notes: + - Connect an L-band antenna directly to the F-connector (no LNB) + - LNB power is disabled automatically for direct input + - Hydrogen emission is weak; use --averages 4-16 for best results + - The --dwell option increases per-step integration time (default 50ms) + - Earth rotation provides natural sky drift at ~15 deg/hour +""", + ) + + parser.add_argument('-v', '--verbose', action='store_true', + help="Show raw USB traffic") + parser.add_argument('--center', type=float, default=H1_FREQ_MHZ, + help=f"Center frequency in MHz (default: {H1_FREQ_MHZ})") + parser.add_argument('--span', type=float, default=4.0, + help="Frequency span in MHz (default: 4.0)") + parser.add_argument('--step', type=float, default=0.5, + help="Frequency step in MHz (default: 0.5)") + parser.add_argument('--dwell', type=int, default=50, + help="Dwell time per step in ms (default: 50)") + parser.add_argument('--averages', type=int, default=1, + help="Number of sweeps to average (default: 1)") + parser.add_argument('--output', '-o', type=str, default=None, + help="CSV output file path") + parser.add_argument('--control', action='store_true', + help="Include control band (1430-1434 MHz) for comparison") + parser.add_argument('--no-velocity', action='store_true', + help="Don't show velocity axis in spectrum display") + + drift_group = parser.add_argument_group('drift scan') + drift_group.add_argument('--drift', action='store_true', + help="Enable drift scan mode (repeated sweeps)") + drift_group.add_argument('--duration', type=float, default=3600, + help="Drift scan duration in seconds (default: 3600)") + drift_group.add_argument('--interval', type=float, default=60, + help="Seconds between sweeps (default: 60)") + drift_group.add_argument('--motor-step', type=int, default=0, + help="Motor steps between sweeps (0=no motor, default: 0)") + + return parser + + +def main(): + parser = build_parser() + args = parser.parse_args() + + with SkyWalker1(verbose=args.verbose) as sw: + sw.ensure_booted() + + # Disable LNB power for direct input + sw.start_intersil(on=False) + print("LNB power disabled (direct L-band input mode)") + + if args.drift: + drift_scan(sw, duration_secs=args.duration, + interval_secs=args.interval, + step_mhz=args.step, dwell_ms=args.dwell, + averages=args.averages, motor_step=args.motor_step, + output_path=args.output) + else: + # Single sweep + print(f"\nSweeping {args.center - args.span/2:.1f} - " + f"{args.center + args.span/2:.1f} MHz " + f"(step={args.step} MHz, dwell={args.dwell}ms, " + f"avg={args.averages}x)") + + result = sweep_h1_band(sw, center_mhz=args.center, + span_mhz=args.span, step_mhz=args.step, + dwell_ms=args.dwell, averages=args.averages) + print_spectrum(result, show_velocity=not args.no_velocity) + + if args.control: + print(" Control band (1430-1434 MHz, no hydrogen expected):") + ctrl = sweep_control_band(sw, step_mhz=args.step, dwell_ms=args.dwell) + print(f" Control mean: {ctrl['control_mean_db']:.2f} dB") + print(f" H1 baseline: {result['baseline_db']:.2f} dB") + diff = result["peak_excess_db"] + print(f" H1 peak excess above baseline: {diff:+.2f} dB") + + # Write single sweep to CSV if requested + if args.output: + with open(args.output, 'w', newline='') as f: + writer = csv.writer(f) + writer.writerow([ + "freq_mhz", "power_db", "excess_db", + "velocity_km_s", "baseline_db", + ]) + for i in range(len(result["freqs_mhz"])): + writer.writerow([ + f"{result['freqs_mhz'][i]:.3f}", + f"{result['powers_db'][i]:.3f}", + f"{result['excess_db'][i]:.3f}", + f"{result['velocities_km_s'][i]:.1f}", + f"{result['baseline_db']:.3f}", + ]) + print(f" Data saved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/tools/motor.py b/tools/motor.py index b92bdd1..9ddba7a 100755 --- a/tools/motor.py +++ b/tools/motor.py @@ -1,541 +1,541 @@ -#!/usr/bin/env python3 -""" -Genpix SkyWalker-1 DiSEqC 1.2 motor control tool. - -Subcommands: - halt - Stop motor movement - east/west - Drive motor (continuous or stepped) - goto - Go to stored position slot - store - Store current position to slot - gotox - USALS GotoX (automatic orbital positioning) - limit - Set software travel limit - nolimits - Disable software limits - raw - Send raw DiSEqC bytes - interactive - Keyboard-driven jog controller with live signal -""" - -import sys -import os -import argparse -import time -import atexit -import select -import termios -import tty - -# Add tools directory to path for library import -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from skywalker_lib import SkyWalker1, signal_bar - - -# -- Safety timeout for continuous drive -- - -CONTINUOUS_DRIVE_TIMEOUT = 30.0 # seconds - - -# -- Subcommand handlers -- - -def cmd_halt(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Stop motor movement.""" - sw.motor_halt() - print("Motor halted") - - -def cmd_east(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Drive motor east.""" - steps = args.steps - if steps: - sw.motor_drive_east(steps) - print(f"Driving east {steps} step(s)") - else: - sw.motor_drive_east(0) - print(f"Driving east (continuous) -- will auto-halt after {CONTINUOUS_DRIVE_TIMEOUT:.0f}s") - print("Press Ctrl-C to stop") - _wait_with_halt(sw, CONTINUOUS_DRIVE_TIMEOUT) - - -def cmd_west(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Drive motor west.""" - steps = args.steps - if steps: - sw.motor_drive_west(steps) - print(f"Driving west {steps} step(s)") - else: - sw.motor_drive_west(0) - print(f"Driving west (continuous) -- will auto-halt after {CONTINUOUS_DRIVE_TIMEOUT:.0f}s") - print("Press Ctrl-C to stop") - _wait_with_halt(sw, CONTINUOUS_DRIVE_TIMEOUT) - - -def _wait_with_halt(sw: SkyWalker1, timeout: float) -> None: - """Wait for timeout or Ctrl-C, then halt the motor.""" - try: - time.sleep(timeout) - except KeyboardInterrupt: - pass - finally: - sw.motor_halt() - print("\nMotor halted") - - -def cmd_goto(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Go to a stored position slot.""" - slot = args.slot - if slot == 0: - print("Going to reference position (slot 0)") - else: - print(f"Going to stored position {slot}") - sw.motor_goto_position(slot) - - -def cmd_store(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Store the current dish position into a slot.""" - slot = args.slot - sw.motor_store_position(slot) - print(f"Current position stored in slot {slot}") - - -def cmd_gotox(sw: SkyWalker1, args: argparse.Namespace) -> None: - """USALS GotoX: calculate and drive to satellite longitude.""" - sat_lon = args.sat - obs_lon = args.lon - obs_lat = args.lat - - # Import usals_angle for display - from skywalker_lib import usals_angle, usals_encode_angle - - angle = usals_angle(obs_lon, sat_lon, obs_lat) - hh, ll = usals_encode_angle(angle) - direction = "west" if angle < 0 else "east" - - print(f"USALS GotoX") - print(f" Observer: {obs_lon:.2f} lon, {obs_lat:.2f} lat") - print(f" Satellite: {sat_lon:.2f} lon") - print(f" Motor angle: {abs(angle):.2f} deg {direction}") - print(f" DiSEqC: E0 31 6E {hh:02X} {ll:02X}") - - sw.motor_goto_x(obs_lon, sat_lon) - print(" Command sent") - - -def cmd_limit(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Set a software travel limit at the current position.""" - direction = args.direction - sw.motor_set_limit(direction) - print(f"Software {direction} limit set at current position") - - -def cmd_nolimits(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Disable software travel limits.""" - sw.motor_disable_limits() - print("Software limits disabled") - - -def cmd_raw(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Send a raw DiSEqC command.""" - raw_bytes = bytes(int(b, 16) for b in args.bytes) - if len(raw_bytes) < 3 or len(raw_bytes) > 6: - print("DiSEqC message must be 3-6 bytes") - sys.exit(1) - print(f"Sending DiSEqC: {raw_bytes.hex(' ')}") - sw.send_diseqc_message(raw_bytes) - print(" OK") - - -# -- Interactive jog controller -- - -def cmd_interactive(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Keyboard-driven jog controller with live signal monitoring.""" - - # Register atexit halt -- fires on unexpected exit, Ctrl-C leak, etc. - def emergency_halt(): - try: - sw.motor_halt() - except Exception: - pass - - atexit.register(emergency_halt) - - # Save terminal state and switch to raw mode - fd = sys.stdin.fileno() - old_attrs = termios.tcgetattr(fd) - - def restore_terminal(): - termios.tcsetattr(fd, termios.TCSAFLUSH, old_attrs) - # Show cursor, clear line - sys.stdout.write("\033[?25h\n") - sys.stdout.flush() - - atexit.register(restore_terminal) - - tty.setraw(fd) - # Hide cursor for cleaner display - sys.stdout.write("\033[?25l") - sys.stdout.flush() - - _interactive_loop(sw, fd, args.verbose) - - # Cleanup (atexit handles restore, but be explicit for normal exit) - restore_terminal() - emergency_halt() - atexit.unregister(restore_terminal) - atexit.unregister(emergency_halt) - - -def _interactive_loop(sw: SkyWalker1, fd: int, verbose: bool) -> None: - """Main loop for interactive jog mode.""" - - state = { - "driving": None, # None, 'east', or 'west' - "drive_start": 0.0, # time.time() when drive began - "store_mode": False, # True = next digit stores position - "running": True, - } - - POLL_INTERVAL = 0.5 # seconds between signal refreshes (~2 Hz) - last_refresh = 0.0 - - _draw_header() - - while state["running"]: - now = time.time() - - # Auto-halt safety: stop after CONTINUOUS_DRIVE_TIMEOUT - if state["driving"] and (now - state["drive_start"]) >= CONTINUOUS_DRIVE_TIMEOUT: - sw.motor_halt() - _status_line(f"AUTO-HALT: {CONTINUOUS_DRIVE_TIMEOUT:.0f}s safety limit reached") - state["driving"] = None - - # Refresh signal display at ~2 Hz - if now - last_refresh >= POLL_INTERVAL: - try: - sig = sw.signal_monitor() - _draw_signal(sig, state) - except Exception: - _draw_signal(None, state) - last_refresh = now - - # Non-blocking key read - if select.select([fd], [], [], 0.05)[0]: - ch = os.read(fd, 8) - _handle_key(sw, ch, state) - - -def _handle_key(sw: SkyWalker1, ch: bytes, state: dict) -> None: - """Process a keypress in interactive mode.""" - - # Escape sequences for arrow keys - if ch == b'\x1b[D' or ch == b'\x1b[C': - direction = 'west' if ch == b'\x1b[D' else 'east' - if state["driving"] != direction: - if direction == 'east': - sw.motor_drive_east(0) - else: - sw.motor_drive_west(0) - state["driving"] = direction - state["drive_start"] = time.time() - _status_line(f"Driving {direction}...") - return - - # Space = halt - if ch == b' ': - sw.motor_halt() - state["driving"] = None - _status_line("Halted") - return - - # q = quit - if ch in (b'q', b'Q', b'\x03'): # q, Q, or Ctrl-C - sw.motor_halt() - state["driving"] = None - state["running"] = False - _status_line("Quitting...") - return - - # s = enter store mode (next digit saves position) - if ch == b's' or ch == b'S': - state["store_mode"] = True - _status_line("Store mode: press 1-9 to save position") - return - - # g = prompt for USALS GotoX - if ch == b'g' or ch == b'G': - _gotox_prompt(sw, state) - return - - # Digits 1-9: goto or store - if len(ch) == 1 and ord(ch) in range(ord('1'), ord('9') + 1): - slot = ord(ch) - ord('0') - if state["store_mode"]: - sw.motor_store_position(slot) - _status_line(f"Position stored in slot {slot}") - state["store_mode"] = False - else: - sw.motor_goto_position(slot) - state["driving"] = None - _status_line(f"Going to position {slot}") - return - - # 0 = goto reference - if ch == b'0': - if state["store_mode"]: - _status_line("Slot 0 is reference -- not storable") - state["store_mode"] = False - else: - sw.motor_goto_position(0) - state["driving"] = None - _status_line("Going to reference (slot 0)") - return - - # Unknown key -- clear store mode - if state["store_mode"]: - state["store_mode"] = False - _status_line("Store cancelled") - - -def _gotox_prompt(sw: SkyWalker1, state: dict) -> None: - """ - Prompt for USALS GotoX parameters in raw terminal mode. - - Reads satellite longitude and observer longitude character-by-character - since we're in raw mode and can't use input(). - """ - _status_line("GotoX: enter satellite longitude (e.g. -97.5): ") - sat_str = _raw_readline() - if sat_str is None: - _status_line("GotoX cancelled") - return - - _status_line(f"Sat {sat_str} -- enter observer longitude: ") - obs_str = _raw_readline() - if obs_str is None: - _status_line("GotoX cancelled") - return - - try: - sat_lon = float(sat_str) - obs_lon = float(obs_str) - except ValueError: - _status_line("Invalid coordinates") - return - - from skywalker_lib import usals_angle - angle = usals_angle(obs_lon, sat_lon) - direction = "W" if angle < 0 else "E" - - sw.motor_goto_x(obs_lon, sat_lon) - state["driving"] = None - _status_line(f"GotoX: sat {sat_lon} obs {obs_lon} -> {abs(angle):.1f} deg {direction}") - - -def _raw_readline() -> str | None: - """Read a line of text in raw terminal mode, echoing characters.""" - fd = sys.stdin.fileno() - buf = [] - while True: - if select.select([fd], [], [], 30.0)[0]: - ch = os.read(fd, 1) - if ch == b'\r' or ch == b'\n': - sys.stdout.write("\r\n") - sys.stdout.flush() - return ''.join(buf) - if ch == b'\x03' or ch == b'\x1b': # Ctrl-C or Escape - return None - if ch == b'\x7f' or ch == b'\x08': # Backspace - if buf: - buf.pop() - sys.stdout.write("\b \b") - sys.stdout.flush() - continue - # Accept digits, minus, period - c = ch.decode('ascii', errors='ignore') - if c in '0123456789.-': - buf.append(c) - sys.stdout.write(c) - sys.stdout.flush() - else: - # Timeout waiting for input - return None - - -# -- Display helpers -- - -def _draw_header() -> None: - """Print the interactive mode header (once at startup).""" - sys.stdout.write("\r\n") - sys.stdout.write(" SkyWalker-1 Motor Control\r\n") - sys.stdout.write(" ========================\r\n") - sys.stdout.write(" Left/Right : jog west/east (continuous)\r\n") - sys.stdout.write(" Space : halt\r\n") - sys.stdout.write(" 1-9 : goto stored position\r\n") - sys.stdout.write(" s + 1-9 : store to position slot\r\n") - sys.stdout.write(" g : USALS GotoX prompt\r\n") - sys.stdout.write(" q : quit\r\n") - sys.stdout.write("\r\n") - sys.stdout.flush() - - -def _draw_signal(sig: dict | None, state: dict) -> None: - """Update the signal monitor line at the bottom of the display.""" - # Save cursor, move to signal display area - sys.stdout.write("\033[s") # save cursor - - if sig is None: - sys.stdout.write("\r\n Signal: -- no data --\033[K") - else: - snr_db = sig["snr_db"] - agc1 = sig["agc1"] - locked = sig["locked"] - power_db = sig["power_db"] - pct = sig["snr_pct"] - - lock_str = "LOCK" if locked else "----" - bar = signal_bar(pct, width=25) - - drive_str = "" - if state["driving"]: - elapsed = time.time() - state["drive_start"] - remaining = CONTINUOUS_DRIVE_TIMEOUT - elapsed - drive_str = f" [{state['driving'].upper()} {remaining:.0f}s]" - - line = (f"\r [{lock_str}] SNR {snr_db:5.1f} dB " - f"AGC {agc1:5d} " - f"Pwr {power_db:5.1f} dB " - f"{bar}{drive_str}") - sys.stdout.write(f"\n{line}\033[K") - - sys.stdout.write("\033[u") # restore cursor - sys.stdout.flush() - - -def _status_line(msg: str) -> None: - """Write a status message on a dedicated line.""" - sys.stdout.write(f"\r > {msg}\033[K\r\n") - sys.stdout.flush() - - -# -- CLI -- - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - prog="motor.py", - description="Genpix SkyWalker-1 DiSEqC 1.2 motor control", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -examples: - %(prog)s halt - %(prog)s east --steps 10 - %(prog)s west - %(prog)s goto 3 - %(prog)s store 3 - %(prog)s gotox --sat -97.5 --lon -96.8 - %(prog)s gotox --sat -97.5 --lon -96.8 --lat 32.7 - %(prog)s limit east - %(prog)s nolimits - %(prog)s raw E0 31 6B 01 - %(prog)s interactive - -interactive mode controls: - Arrow left/right jog west/east (continuous drive) - Space halt motor - 1-9 go to stored position - s + 1-9 store position to slot - g USALS GotoX prompt - q quit - -safety: - Continuous drive auto-halts after 30 seconds. - Motor is halted on exit (including unexpected termination). -""") - parser.add_argument('-v', '--verbose', action='store_true', - help="Show raw USB traffic") - - sub = parser.add_subparsers(dest='command') - - # halt - sub.add_parser('halt', help="Stop motor movement") - - # east - p_east = sub.add_parser('east', help="Drive motor east") - p_east.add_argument('--steps', type=int, default=0, - help="Number of steps (0=continuous, 1-127)") - - # west - p_west = sub.add_parser('west', help="Drive motor west") - p_west.add_argument('--steps', type=int, default=0, - help="Number of steps (0=continuous, 1-127)") - - # goto - p_goto = sub.add_parser('goto', help="Go to stored position") - p_goto.add_argument('slot', type=int, - help="Position slot (0=reference, 1-255)") - - # store - p_store = sub.add_parser('store', help="Store current position") - p_store.add_argument('slot', type=int, - help="Position slot to store to (1-255)") - - # gotox - p_gotox = sub.add_parser('gotox', help="USALS GotoX (automatic positioning)") - p_gotox.add_argument('--sat', type=float, required=True, - help="Satellite longitude (negative=west, e.g. -97.5)") - p_gotox.add_argument('--lon', type=float, required=True, - help="Observer longitude (negative=west)") - p_gotox.add_argument('--lat', type=float, default=0.0, - help="Observer latitude (default: 0.0)") - - # limit - p_limit = sub.add_parser('limit', help="Set software travel limit") - p_limit.add_argument('direction', choices=['east', 'west'], - help="Limit direction") - - # nolimits - sub.add_parser('nolimits', help="Disable software travel limits") - - # raw - p_raw = sub.add_parser('raw', help="Send raw DiSEqC bytes") - p_raw.add_argument('bytes', nargs='+', metavar='HH', - help="Hex bytes (e.g. E0 31 6B 01)") - - # interactive - sub.add_parser('interactive', help="Keyboard-driven jog controller") - - return parser - - -def main(): - parser = build_parser() - args = parser.parse_args() - - if not args.command: - parser.print_help() - sys.exit(0) - - dispatch = { - 'halt': cmd_halt, - 'east': cmd_east, - 'west': cmd_west, - 'goto': cmd_goto, - 'store': cmd_store, - 'gotox': cmd_gotox, - 'limit': cmd_limit, - 'nolimits': cmd_nolimits, - 'raw': cmd_raw, - 'interactive': cmd_interactive, - } - - handler = dispatch.get(args.command) - if handler is None: - parser.print_help() - sys.exit(1) - - with SkyWalker1(verbose=args.verbose) as sw: - # Boot demodulator and enable LNB power for DiSEqC - sw.ensure_booted() - sw.start_intersil(on=True) - handler(sw, args) - - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 +""" +Genpix SkyWalker-1 DiSEqC 1.2 motor control tool. + +Subcommands: + halt - Stop motor movement + east/west - Drive motor (continuous or stepped) + goto - Go to stored position slot + store - Store current position to slot + gotox - USALS GotoX (automatic orbital positioning) + limit - Set software travel limit + nolimits - Disable software limits + raw - Send raw DiSEqC bytes + interactive - Keyboard-driven jog controller with live signal +""" + +import sys +import os +import argparse +import time +import atexit +import select +import termios +import tty + +# Add tools directory to path for library import +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from skywalker_lib import SkyWalker1, signal_bar + + +# -- Safety timeout for continuous drive -- + +CONTINUOUS_DRIVE_TIMEOUT = 30.0 # seconds + + +# -- Subcommand handlers -- + +def cmd_halt(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Stop motor movement.""" + sw.motor_halt() + print("Motor halted") + + +def cmd_east(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Drive motor east.""" + steps = args.steps + if steps: + sw.motor_drive_east(steps) + print(f"Driving east {steps} step(s)") + else: + sw.motor_drive_east(0) + print(f"Driving east (continuous) -- will auto-halt after {CONTINUOUS_DRIVE_TIMEOUT:.0f}s") + print("Press Ctrl-C to stop") + _wait_with_halt(sw, CONTINUOUS_DRIVE_TIMEOUT) + + +def cmd_west(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Drive motor west.""" + steps = args.steps + if steps: + sw.motor_drive_west(steps) + print(f"Driving west {steps} step(s)") + else: + sw.motor_drive_west(0) + print(f"Driving west (continuous) -- will auto-halt after {CONTINUOUS_DRIVE_TIMEOUT:.0f}s") + print("Press Ctrl-C to stop") + _wait_with_halt(sw, CONTINUOUS_DRIVE_TIMEOUT) + + +def _wait_with_halt(sw: SkyWalker1, timeout: float) -> None: + """Wait for timeout or Ctrl-C, then halt the motor.""" + try: + time.sleep(timeout) + except KeyboardInterrupt: + pass + finally: + sw.motor_halt() + print("\nMotor halted") + + +def cmd_goto(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Go to a stored position slot.""" + slot = args.slot + if slot == 0: + print("Going to reference position (slot 0)") + else: + print(f"Going to stored position {slot}") + sw.motor_goto_position(slot) + + +def cmd_store(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Store the current dish position into a slot.""" + slot = args.slot + sw.motor_store_position(slot) + print(f"Current position stored in slot {slot}") + + +def cmd_gotox(sw: SkyWalker1, args: argparse.Namespace) -> None: + """USALS GotoX: calculate and drive to satellite longitude.""" + sat_lon = args.sat + obs_lon = args.lon + obs_lat = args.lat + + # Import usals_angle for display + from skywalker_lib import usals_angle, usals_encode_angle + + angle = usals_angle(obs_lon, sat_lon, obs_lat) + hh, ll = usals_encode_angle(angle) + direction = "west" if angle < 0 else "east" + + print(f"USALS GotoX") + print(f" Observer: {obs_lon:.2f} lon, {obs_lat:.2f} lat") + print(f" Satellite: {sat_lon:.2f} lon") + print(f" Motor angle: {abs(angle):.2f} deg {direction}") + print(f" DiSEqC: E0 31 6E {hh:02X} {ll:02X}") + + sw.motor_goto_x(obs_lon, sat_lon) + print(" Command sent") + + +def cmd_limit(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Set a software travel limit at the current position.""" + direction = args.direction + sw.motor_set_limit(direction) + print(f"Software {direction} limit set at current position") + + +def cmd_nolimits(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Disable software travel limits.""" + sw.motor_disable_limits() + print("Software limits disabled") + + +def cmd_raw(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Send a raw DiSEqC command.""" + raw_bytes = bytes(int(b, 16) for b in args.bytes) + if len(raw_bytes) < 3 or len(raw_bytes) > 6: + print("DiSEqC message must be 3-6 bytes") + sys.exit(1) + print(f"Sending DiSEqC: {raw_bytes.hex(' ')}") + sw.send_diseqc_message(raw_bytes) + print(" OK") + + +# -- Interactive jog controller -- + +def cmd_interactive(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Keyboard-driven jog controller with live signal monitoring.""" + + # Register atexit halt -- fires on unexpected exit, Ctrl-C leak, etc. + def emergency_halt(): + try: + sw.motor_halt() + except Exception: + pass + + atexit.register(emergency_halt) + + # Save terminal state and switch to raw mode + fd = sys.stdin.fileno() + old_attrs = termios.tcgetattr(fd) + + def restore_terminal(): + termios.tcsetattr(fd, termios.TCSAFLUSH, old_attrs) + # Show cursor, clear line + sys.stdout.write("\033[?25h\n") + sys.stdout.flush() + + atexit.register(restore_terminal) + + tty.setraw(fd) + # Hide cursor for cleaner display + sys.stdout.write("\033[?25l") + sys.stdout.flush() + + _interactive_loop(sw, fd, args.verbose) + + # Cleanup (atexit handles restore, but be explicit for normal exit) + restore_terminal() + emergency_halt() + atexit.unregister(restore_terminal) + atexit.unregister(emergency_halt) + + +def _interactive_loop(sw: SkyWalker1, fd: int, verbose: bool) -> None: + """Main loop for interactive jog mode.""" + + state = { + "driving": None, # None, 'east', or 'west' + "drive_start": 0.0, # time.time() when drive began + "store_mode": False, # True = next digit stores position + "running": True, + } + + POLL_INTERVAL = 0.5 # seconds between signal refreshes (~2 Hz) + last_refresh = 0.0 + + _draw_header() + + while state["running"]: + now = time.time() + + # Auto-halt safety: stop after CONTINUOUS_DRIVE_TIMEOUT + if state["driving"] and (now - state["drive_start"]) >= CONTINUOUS_DRIVE_TIMEOUT: + sw.motor_halt() + _status_line(f"AUTO-HALT: {CONTINUOUS_DRIVE_TIMEOUT:.0f}s safety limit reached") + state["driving"] = None + + # Refresh signal display at ~2 Hz + if now - last_refresh >= POLL_INTERVAL: + try: + sig = sw.signal_monitor() + _draw_signal(sig, state) + except Exception: + _draw_signal(None, state) + last_refresh = now + + # Non-blocking key read + if select.select([fd], [], [], 0.05)[0]: + ch = os.read(fd, 8) + _handle_key(sw, ch, state) + + +def _handle_key(sw: SkyWalker1, ch: bytes, state: dict) -> None: + """Process a keypress in interactive mode.""" + + # Escape sequences for arrow keys + if ch == b'\x1b[D' or ch == b'\x1b[C': + direction = 'west' if ch == b'\x1b[D' else 'east' + if state["driving"] != direction: + if direction == 'east': + sw.motor_drive_east(0) + else: + sw.motor_drive_west(0) + state["driving"] = direction + state["drive_start"] = time.time() + _status_line(f"Driving {direction}...") + return + + # Space = halt + if ch == b' ': + sw.motor_halt() + state["driving"] = None + _status_line("Halted") + return + + # q = quit + if ch in (b'q', b'Q', b'\x03'): # q, Q, or Ctrl-C + sw.motor_halt() + state["driving"] = None + state["running"] = False + _status_line("Quitting...") + return + + # s = enter store mode (next digit saves position) + if ch == b's' or ch == b'S': + state["store_mode"] = True + _status_line("Store mode: press 1-9 to save position") + return + + # g = prompt for USALS GotoX + if ch == b'g' or ch == b'G': + _gotox_prompt(sw, state) + return + + # Digits 1-9: goto or store + if len(ch) == 1 and ord(ch) in range(ord('1'), ord('9') + 1): + slot = ord(ch) - ord('0') + if state["store_mode"]: + sw.motor_store_position(slot) + _status_line(f"Position stored in slot {slot}") + state["store_mode"] = False + else: + sw.motor_goto_position(slot) + state["driving"] = None + _status_line(f"Going to position {slot}") + return + + # 0 = goto reference + if ch == b'0': + if state["store_mode"]: + _status_line("Slot 0 is reference -- not storable") + state["store_mode"] = False + else: + sw.motor_goto_position(0) + state["driving"] = None + _status_line("Going to reference (slot 0)") + return + + # Unknown key -- clear store mode + if state["store_mode"]: + state["store_mode"] = False + _status_line("Store cancelled") + + +def _gotox_prompt(sw: SkyWalker1, state: dict) -> None: + """ + Prompt for USALS GotoX parameters in raw terminal mode. + + Reads satellite longitude and observer longitude character-by-character + since we're in raw mode and can't use input(). + """ + _status_line("GotoX: enter satellite longitude (e.g. -97.5): ") + sat_str = _raw_readline() + if sat_str is None: + _status_line("GotoX cancelled") + return + + _status_line(f"Sat {sat_str} -- enter observer longitude: ") + obs_str = _raw_readline() + if obs_str is None: + _status_line("GotoX cancelled") + return + + try: + sat_lon = float(sat_str) + obs_lon = float(obs_str) + except ValueError: + _status_line("Invalid coordinates") + return + + from skywalker_lib import usals_angle + angle = usals_angle(obs_lon, sat_lon) + direction = "W" if angle < 0 else "E" + + sw.motor_goto_x(obs_lon, sat_lon) + state["driving"] = None + _status_line(f"GotoX: sat {sat_lon} obs {obs_lon} -> {abs(angle):.1f} deg {direction}") + + +def _raw_readline() -> str | None: + """Read a line of text in raw terminal mode, echoing characters.""" + fd = sys.stdin.fileno() + buf = [] + while True: + if select.select([fd], [], [], 30.0)[0]: + ch = os.read(fd, 1) + if ch == b'\r' or ch == b'\n': + sys.stdout.write("\r\n") + sys.stdout.flush() + return ''.join(buf) + if ch == b'\x03' or ch == b'\x1b': # Ctrl-C or Escape + return None + if ch == b'\x7f' or ch == b'\x08': # Backspace + if buf: + buf.pop() + sys.stdout.write("\b \b") + sys.stdout.flush() + continue + # Accept digits, minus, period + c = ch.decode('ascii', errors='ignore') + if c in '0123456789.-': + buf.append(c) + sys.stdout.write(c) + sys.stdout.flush() + else: + # Timeout waiting for input + return None + + +# -- Display helpers -- + +def _draw_header() -> None: + """Print the interactive mode header (once at startup).""" + sys.stdout.write("\r\n") + sys.stdout.write(" SkyWalker-1 Motor Control\r\n") + sys.stdout.write(" ========================\r\n") + sys.stdout.write(" Left/Right : jog west/east (continuous)\r\n") + sys.stdout.write(" Space : halt\r\n") + sys.stdout.write(" 1-9 : goto stored position\r\n") + sys.stdout.write(" s + 1-9 : store to position slot\r\n") + sys.stdout.write(" g : USALS GotoX prompt\r\n") + sys.stdout.write(" q : quit\r\n") + sys.stdout.write("\r\n") + sys.stdout.flush() + + +def _draw_signal(sig: dict | None, state: dict) -> None: + """Update the signal monitor line at the bottom of the display.""" + # Save cursor, move to signal display area + sys.stdout.write("\033[s") # save cursor + + if sig is None: + sys.stdout.write("\r\n Signal: -- no data --\033[K") + else: + snr_db = sig["snr_db"] + agc1 = sig["agc1"] + locked = sig["locked"] + power_db = sig["power_db"] + pct = sig["snr_pct"] + + lock_str = "LOCK" if locked else "----" + bar = signal_bar(pct, width=25) + + drive_str = "" + if state["driving"]: + elapsed = time.time() - state["drive_start"] + remaining = CONTINUOUS_DRIVE_TIMEOUT - elapsed + drive_str = f" [{state['driving'].upper()} {remaining:.0f}s]" + + line = (f"\r [{lock_str}] SNR {snr_db:5.1f} dB " + f"AGC {agc1:5d} " + f"Pwr {power_db:5.1f} dB " + f"{bar}{drive_str}") + sys.stdout.write(f"\n{line}\033[K") + + sys.stdout.write("\033[u") # restore cursor + sys.stdout.flush() + + +def _status_line(msg: str) -> None: + """Write a status message on a dedicated line.""" + sys.stdout.write(f"\r > {msg}\033[K\r\n") + sys.stdout.flush() + + +# -- CLI -- + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="motor.py", + description="Genpix SkyWalker-1 DiSEqC 1.2 motor control", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +examples: + %(prog)s halt + %(prog)s east --steps 10 + %(prog)s west + %(prog)s goto 3 + %(prog)s store 3 + %(prog)s gotox --sat -97.5 --lon -96.8 + %(prog)s gotox --sat -97.5 --lon -96.8 --lat 32.7 + %(prog)s limit east + %(prog)s nolimits + %(prog)s raw E0 31 6B 01 + %(prog)s interactive + +interactive mode controls: + Arrow left/right jog west/east (continuous drive) + Space halt motor + 1-9 go to stored position + s + 1-9 store position to slot + g USALS GotoX prompt + q quit + +safety: + Continuous drive auto-halts after 30 seconds. + Motor is halted on exit (including unexpected termination). +""") + parser.add_argument('-v', '--verbose', action='store_true', + help="Show raw USB traffic") + + sub = parser.add_subparsers(dest='command') + + # halt + sub.add_parser('halt', help="Stop motor movement") + + # east + p_east = sub.add_parser('east', help="Drive motor east") + p_east.add_argument('--steps', type=int, default=0, + help="Number of steps (0=continuous, 1-127)") + + # west + p_west = sub.add_parser('west', help="Drive motor west") + p_west.add_argument('--steps', type=int, default=0, + help="Number of steps (0=continuous, 1-127)") + + # goto + p_goto = sub.add_parser('goto', help="Go to stored position") + p_goto.add_argument('slot', type=int, + help="Position slot (0=reference, 1-255)") + + # store + p_store = sub.add_parser('store', help="Store current position") + p_store.add_argument('slot', type=int, + help="Position slot to store to (1-255)") + + # gotox + p_gotox = sub.add_parser('gotox', help="USALS GotoX (automatic positioning)") + p_gotox.add_argument('--sat', type=float, required=True, + help="Satellite longitude (negative=west, e.g. -97.5)") + p_gotox.add_argument('--lon', type=float, required=True, + help="Observer longitude (negative=west)") + p_gotox.add_argument('--lat', type=float, default=0.0, + help="Observer latitude (default: 0.0)") + + # limit + p_limit = sub.add_parser('limit', help="Set software travel limit") + p_limit.add_argument('direction', choices=['east', 'west'], + help="Limit direction") + + # nolimits + sub.add_parser('nolimits', help="Disable software travel limits") + + # raw + p_raw = sub.add_parser('raw', help="Send raw DiSEqC bytes") + p_raw.add_argument('bytes', nargs='+', metavar='HH', + help="Hex bytes (e.g. E0 31 6B 01)") + + # interactive + sub.add_parser('interactive', help="Keyboard-driven jog controller") + + return parser + + +def main(): + parser = build_parser() + args = parser.parse_args() + + if not args.command: + parser.print_help() + sys.exit(0) + + dispatch = { + 'halt': cmd_halt, + 'east': cmd_east, + 'west': cmd_west, + 'goto': cmd_goto, + 'store': cmd_store, + 'gotox': cmd_gotox, + 'limit': cmd_limit, + 'nolimits': cmd_nolimits, + 'raw': cmd_raw, + 'interactive': cmd_interactive, + } + + handler = dispatch.get(args.command) + if handler is None: + parser.print_help() + sys.exit(1) + + with SkyWalker1(verbose=args.verbose) as sw: + # Boot demodulator and enable LNB power for DiSEqC + sw.ensure_booted() + sw.start_intersil(on=True) + handler(sw, args) + + +if __name__ == '__main__': + main() diff --git a/tools/qo100.py b/tools/qo100.py index 0249688..fd3f5bc 100755 --- a/tools/qo100.py +++ b/tools/qo100.py @@ -1,773 +1,773 @@ -#!/usr/bin/env python3 -""" -QO-100 (Es'hail-2) DATV reception tool for the Genpix SkyWalker-1. - -Provides frequency calculation, band plan display, wideband transponder -scanning, tuning, and live video piping for QO-100 amateur television -signals received via the SkyWalker-1 DVB-S demodulator. - -QO-100 wideband transponder: 10491-10499 MHz (DVB-S QPSK, various SRs) -Narrowband transponder: 10489.5-10490 MHz (SSB/CW, not demodulable) -Engineering beacon: 10489.75 MHz (CW) - -The BCM4500 demodulator has a minimum symbol rate of 256 ksps. QO-100 -DATV signals typically range from 333 ksps to 2000 ksps, well within -the hardware capability. Signals below 256 ksps are detectable as -energy via spectrum sweep but cannot be locked/demodulated. -""" - -import sys -import os -import argparse -import time -import signal as signal_mod -import subprocess - -# Add tools directory to path for library import -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from skywalker_lib import ( - SkyWalker1, MODULATIONS, FEC_RATES, MOD_FEC_GROUP, - rf_to_if, if_to_rf, detect_peaks, signal_bar, -) - - -# --- QO-100 constants --- - -QO100_WB_START_MHZ = 10491.0 # wideband transponder start -QO100_WB_STOP_MHZ = 10499.0 # wideband transponder stop -QO100_BEACON_MHZ = 10489.75 # engineering beacon -QO100_ORBITAL_POS = 25.9 # degrees East (Es'hail-2) -QO100_NB_START_MHZ = 10489.5 # narrowband transponder start -QO100_NB_STOP_MHZ = 10490.0 # narrowband transponder stop -BCM4500_MIN_SR_KSPS = 256 # hardware minimum - -# Common modified LNB local oscillators used for QO-100 reception -COMMON_QO100_LOS = { - 9361: "Modified PLL LNB (TCXO, popular)", - 9000: "Round LO", - 9100: "Round LO", - 9200: "Round LO", - 9300: "Round LO", - 9750: "Standard universal (low band)", -} - -# Known DATV stations / frequencies on QO-100 wideband transponder -QO100_KNOWN_STATIONS = [ - {"call": "BATC", "freq_mhz": 10491.5, "sr_ksps": 1500, "mod": "qpsk", "fec": "3/4", "note": "British ATV Club beacon"}, - {"call": "Various", "freq_mhz": 10492.0, "sr_ksps": 1000, "mod": "qpsk", "fec": "1/2", "note": "Common DATV frequency"}, - {"call": "Various", "freq_mhz": 10493.0, "sr_ksps": 500, "mod": "qpsk", "fec": "1/2", "note": "Low-power DATV"}, - {"call": "Various", "freq_mhz": 10494.0, "sr_ksps": 333, "mod": "qpsk", "fec": "1/2", "note": "Minimum viable DVB-S"}, - {"call": "Beacon", "freq_mhz": 10489.75, "sr_ksps": 0, "mod": "cw", "fec": "-", "note": "Engineering beacon (CW)"}, -] - - -# --- Helper functions --- - -def validate_qo100_lo(lnb_lo_mhz: int) -> dict: - """ - Check whether a given LNB LO places the QO-100 wideband transponder - within the SkyWalker-1 IF range (950-2150 MHz). - - Returns dict with: - valid - bool, True if entire WB transponder fits in IF range - if_range - (start_if, stop_if) tuple in MHz - warnings - list of warning strings - """ - start_if = rf_to_if(QO100_WB_START_MHZ, lnb_lo_mhz) - stop_if = rf_to_if(QO100_WB_STOP_MHZ, lnb_lo_mhz) - warnings = [] - - if start_if < 950: - warnings.append(f"WB start IF {start_if:.0f} MHz is below 950 MHz minimum") - if stop_if > 2150: - warnings.append(f"WB stop IF {stop_if:.0f} MHz is above 2150 MHz maximum") - if start_if < 0 or stop_if < 0: - warnings.append(f"Negative IF -- LNB LO {lnb_lo_mhz} MHz is above the RF frequency") - - # Check if LO is a known value - if lnb_lo_mhz not in COMMON_QO100_LOS: - nearby = [lo for lo in COMMON_QO100_LOS if abs(lo - lnb_lo_mhz) <= 100] - if not nearby: - warnings.append(f"LO {lnb_lo_mhz} MHz is not a common QO-100 value") - - valid = (950 <= start_if) and (stop_if <= 2150) - - return { - "valid": valid, - "if_range": (start_if, stop_if), - "warnings": warnings, - } - - -def qo100_if_range(lnb_lo_mhz: float) -> tuple: - """Return (start_if, stop_if) in MHz for the QO-100 WB transponder.""" - return ( - rf_to_if(QO100_WB_START_MHZ, lnb_lo_mhz), - rf_to_if(QO100_WB_STOP_MHZ, lnb_lo_mhz), - ) - - -def qo100_band_plan(lnb_lo_mhz: float) -> list: - """ - Return the QO-100 known station list augmented with IF frequencies - and lockability status for the given LNB LO. - - Each entry is a dict with keys: - call, freq_mhz (RF), if_mhz, sr_ksps, mod, fec, note, lockable - """ - plan = [] - for station in QO100_KNOWN_STATIONS: - if_mhz = rf_to_if(station["freq_mhz"], lnb_lo_mhz) - lockable = ( - station["sr_ksps"] >= BCM4500_MIN_SR_KSPS - and station["mod"] in MODULATIONS - and 950 <= if_mhz <= 2150 - ) - plan.append({ - "call": station["call"], - "freq_mhz": station["freq_mhz"], - "if_mhz": if_mhz, - "sr_ksps": station["sr_ksps"], - "mod": station["mod"], - "fec": station["fec"], - "note": station["note"], - "lockable": lockable, - }) - return plan - - -def _resolve_fec(mod_name: str, fec_name: str) -> int: - """Look up the FEC index for a given modulation and FEC rate string.""" - fec_group = MOD_FEC_GROUP.get(mod_name) - if fec_group is None: - print(f"Unknown modulation: {mod_name}") - sys.exit(1) - fec_table = FEC_RATES[fec_group] - if fec_name not in fec_table: - print(f"Invalid FEC '{fec_name}' for {mod_name}") - print(f"Valid: {', '.join(fec_table.keys())}") - sys.exit(1) - return fec_table[fec_name] - - -def _print_lo_info(lnb_lo: float, verbose: bool = False) -> None: - """Print LNB LO validation summary.""" - lo_desc = COMMON_QO100_LOS.get(int(lnb_lo), "custom") - print(f" LNB LO: {lnb_lo:.0f} MHz ({lo_desc})") - - check = validate_qo100_lo(lnb_lo) - start_if, stop_if = check["if_range"] - print(f" WB IF range: {start_if:.1f} - {stop_if:.1f} MHz") - - if not check["valid"]: - print(f" WARNING: QO-100 WB transponder does not fit in IF range!") - for w in check["warnings"]: - print(f" WARNING: {w}") - - if verbose: - beacon_if = rf_to_if(QO100_BEACON_MHZ, lnb_lo) - nb_start_if = rf_to_if(QO100_NB_START_MHZ, lnb_lo) - nb_stop_if = rf_to_if(QO100_NB_STOP_MHZ, lnb_lo) - print(f" Beacon IF: {beacon_if:.2f} MHz (CW, not demodulable)") - print(f" NB IF range: {nb_start_if:.1f} - {nb_stop_if:.1f} MHz (SSB/CW)") - - -# --- Subcommand handlers --- - -def cmd_calc(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Show IF frequencies for a given LNB LO.""" - lnb_lo = args.lnb_lo - - print(f"QO-100 IF Frequency Calculator") - print(f"{'=' * 60}") - print(f" Satellite: Es'hail-2 (QO-100) at {QO100_ORBITAL_POS} deg E") - _print_lo_info(lnb_lo, verbose=True) - - print(f"\n {'RF (MHz)':>12} {'IF (MHz)':>10} {'Description'}") - print(f" {'─' * 50}") - - entries = [ - (QO100_NB_START_MHZ, "Narrowband start"), - (QO100_BEACON_MHZ, "Engineering beacon (CW)"), - (QO100_NB_STOP_MHZ, "Narrowband stop"), - (QO100_WB_START_MHZ, "Wideband start"), - (QO100_WB_STOP_MHZ, "Wideband stop"), - ] - - for rf, desc in entries: - if_mhz = rf_to_if(rf, lnb_lo) - in_range = 950 <= if_mhz <= 2150 - marker = "" if in_range else " [OUT OF RANGE]" - print(f" {rf:12.2f} {if_mhz:10.2f} {desc}{marker}") - - # Common LO comparison table - print(f"\n Common LNB LO comparison:") - print(f" {'LO (MHz)':>10} {'WB Start IF':>12} {'WB Stop IF':>12} {'Description'}") - print(f" {'─' * 60}") - for lo, desc in sorted(COMMON_QO100_LOS.items()): - s_if = rf_to_if(QO100_WB_START_MHZ, lo) - e_if = rf_to_if(QO100_WB_STOP_MHZ, lo) - fits = 950 <= s_if and e_if <= 2150 - status = "" if fits else " [!]" - current = " <--" if lo == int(lnb_lo) else "" - print(f" {lo:10d} {s_if:12.1f} {e_if:12.1f} {desc}{status}{current}") - - -def cmd_band_plan(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Show known QO-100 stations with IF conversion for this LNB LO.""" - lnb_lo = args.lnb_lo - - print(f"QO-100 Wideband Transponder Band Plan") - print(f"{'=' * 60}") - _print_lo_info(lnb_lo, verbose=args.verbose) - print() - - plan = qo100_band_plan(lnb_lo) - - # Table header - hdr = (f" {'Call':8s} {'RF MHz':>9s} {'IF MHz':>9s} " - f"{'SR ksps':>8s} {'Mod':5s} {'FEC':4s} {'Lock':4s} Note") - print(hdr) - print(f" {'─' * 76}") - - for entry in plan: - sr_str = f"{entry['sr_ksps']:>5d}" if entry["sr_ksps"] > 0 else " n/a" - if entry["lockable"]: - lock_str = " yes" - elif entry["sr_ksps"] == 0: - lock_str = " --" - elif entry["sr_ksps"] < BCM4500_MIN_SR_KSPS: - lock_str = " no" - elif entry["mod"] not in MODULATIONS: - lock_str = " no" - else: - lock_str = " no" - - in_range = 950 <= entry["if_mhz"] <= 2150 - if_str = f"{entry['if_mhz']:9.2f}" if in_range else f"{entry['if_mhz']:7.2f} !" - - print(f" {entry['call']:8s} {entry['freq_mhz']:9.2f} {if_str} " - f"{sr_str:>8s} {entry['mod']:5s} {entry['fec']:4s} {lock_str} " - f"{entry['note']}") - - # Legend - print(f"\n Lock column: yes = lockable by BCM4500 (SR >= {BCM4500_MIN_SR_KSPS} ksps, " - f"supported mod, IF in range)") - print(f" no = detectable as energy but not demodulable") - print(f" -- = not a digital signal (CW/SSB)") - - -def cmd_scan(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Scan the QO-100 wideband transponder for active carriers.""" - lnb_lo = args.lnb_lo - step_mhz = args.step - dwell_ms = args.dwell - - # Validate LO - check = validate_qo100_lo(lnb_lo) - if not check["valid"]: - print(f"ERROR: QO-100 WB transponder does not fit in IF range with LO {lnb_lo} MHz") - for w in check["warnings"]: - print(f" {w}") - sys.exit(1) - - start_if, stop_if = check["if_range"] - - # QO-100 optimized sweep parameters - # Low symbol rates need longer dwell, finer steps, lower measurement SR - sr_ksps = 1000 # lower SR for better sensitivity to narrow signals - - steps = int((stop_if - start_if) / step_mhz) + 1 - est_time = steps * (dwell_ms + 5) / 1000.0 - - print(f"QO-100 Wideband Transponder Scan") - print(f"{'=' * 60}") - _print_lo_info(lnb_lo, verbose=args.verbose) - print(f" RF range: {QO100_WB_START_MHZ:.1f} - {QO100_WB_STOP_MHZ:.1f} MHz") - print(f" IF range: {start_if:.1f} - {stop_if:.1f} MHz") - print(f" Step: {step_mhz} MHz ({steps} points)") - print(f" Dwell: {dwell_ms} ms") - print(f" Meas SR: {sr_ksps} ksps") - print(f" Est. time: {est_time:.1f}s") - print() - - sw.ensure_booted() - - # Sweep the wideband transponder IF range - print("[1/3] Sweeping wideband transponder...") - - def progress(freq, step_num, total, result): - pct = (step_num + 1) / total * 100 - rf = if_to_rf(freq, lnb_lo) - sys.stdout.write(f"\r [{pct:5.1f}%] IF {freq:.1f} MHz RF {rf:.1f} MHz" - f" pwr={result['power_db']:.1f} dB" - f" AGC={result['agc1']}") - sys.stdout.flush() - - freqs, powers, results = sw.sweep_spectrum( - start_if, stop_if, step_mhz, dwell_ms, sr_ksps, - callback=progress if not args.verbose else None - ) - sys.stdout.write("\r" + " " * 70 + "\r") - print(f" {len(freqs)} points measured") - - # Peak detection - print(f"\n[2/3] Peak detection (threshold {args.threshold:.0f} dB)...") - peaks = detect_peaks(freqs, powers, threshold_db=args.threshold) - - if not peaks: - print(" No carriers detected above noise floor.") - print(" Check dish alignment, LNB LO, and that the transponder is active.") - return - - print(f" {len(peaks)} carrier(s) detected:") - print() - print(f" {'IF MHz':>8s} {'RF MHz':>10s} {'Power dB':>9s} Nearest known station") - print(f" {'─' * 55}") - - for freq_if, pwr, idx in peaks: - freq_rf = if_to_rf(freq_if, lnb_lo) - - # Match to nearest known station - nearest = None - nearest_dist = 999 - for station in QO100_KNOWN_STATIONS: - dist = abs(station["freq_mhz"] - freq_rf) - if dist < nearest_dist: - nearest_dist = dist - nearest = station - - match_str = "" - if nearest and nearest_dist < 1.0: - lockable = nearest["sr_ksps"] >= BCM4500_MIN_SR_KSPS - lock_note = "" if lockable else " [below min SR]" - match_str = (f"{nearest['call']} ({nearest['sr_ksps']} ksps " - f"{nearest['mod']} {nearest['fec']}){lock_note}") - elif nearest and nearest_dist < 2.0: - match_str = f"near {nearest['call']} ({nearest_dist:.1f} MHz off)" - - print(f" {freq_if:8.1f} {freq_rf:10.2f} {pwr:9.1f} {match_str}") - - # Try locking each peak that could be a DATV signal - print(f"\n[3/3] Attempting lock on detected carriers...") - locked_count = 0 - - for freq_if, pwr, idx in peaks: - freq_rf = if_to_rf(freq_if, lnb_lo) - if_khz = int(freq_if * 1000) - - # Try common QO-100 symbol rates, highest first - trial_srs = [1500, 1000, 500, 333, 256] - - for sr in trial_srs: - if sr < BCM4500_MIN_SR_KSPS: - continue - - sr_sps = sr * 1000 - mod_index, _ = MODULATIONS["qpsk"] - fec_group = MOD_FEC_GROUP["qpsk"] - fec_index = FEC_RATES[fec_group]["auto"] - - if args.verbose: - print(f" Trying {freq_rf:.2f} MHz SR {sr} ksps...", end="", flush=True) - - sw.tune(sr_sps, if_khz, mod_index, fec_index) - time.sleep(0.3) - - if sw.get_signal_lock(): - sig = sw.get_signal_strength() - print(f" LOCKED {freq_rf:.2f} MHz SR {sr} ksps " - f"SNR {sig['snr_db']:.1f} dB {signal_bar(sig['snr_pct'], width=20)}") - locked_count += 1 - break - elif args.verbose: - print(f" no lock") - - print(f"\n Scan complete: {len(peaks)} carriers detected, {locked_count} locked") - - -def cmd_tune(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Tune to a specific QO-100 frequency.""" - lnb_lo = args.lnb_lo - freq_rf = args.freq - sr_ksps = args.sr - mod_name = args.mod - fec_name = args.fec - - # Validate - if sr_ksps < BCM4500_MIN_SR_KSPS: - print(f"ERROR: Symbol rate {sr_ksps} ksps is below BCM4500 minimum ({BCM4500_MIN_SR_KSPS} ksps)") - sys.exit(1) - - if mod_name not in MODULATIONS: - print(f"Unknown modulation: {mod_name}") - print(f"Valid: {', '.join(MODULATIONS.keys())}") - sys.exit(1) - - mod_index, mod_desc = MODULATIONS[mod_name] - fec_index = _resolve_fec(mod_name, fec_name) - - if_mhz = rf_to_if(freq_rf, lnb_lo) - if_khz = int(if_mhz * 1000) - sr_sps = sr_ksps * 1000 - - if if_khz < 950000 or if_khz > 2150000: - print(f"ERROR: IF frequency {if_mhz:.1f} MHz is outside 950-2150 MHz range") - print(f" RF: {freq_rf} MHz, LNB LO: {lnb_lo} MHz") - sys.exit(1) - - print(f"QO-100 Tune") - print(f"{'=' * 60}") - _print_lo_info(lnb_lo, verbose=args.verbose) - print(f" RF Frequency: {freq_rf} MHz") - print(f" IF Frequency: {if_mhz:.2f} MHz ({if_khz} kHz)") - print(f" Symbol Rate: {sr_ksps} ksps ({sr_sps} sps)") - print(f" Modulation: {mod_desc}") - print(f" FEC: {fec_name} (index {fec_index})") - print() - - sw.ensure_booted() - - # Tune - print("Sending tune command...", end="", flush=True) - sw.tune(sr_sps, if_khz, mod_index, fec_index) - print(" done") - - # Wait for lock - timeout = args.timeout - print(f"Waiting for lock (timeout {timeout}s)...", end="", flush=True) - deadline = time.time() + timeout - locked = False - dots = 0 - - while time.time() < deadline: - if sw.get_signal_lock(): - locked = True - break - print(".", end="", flush=True) - dots += 1 - time.sleep(0.5) - - print() - - if locked: - sig = sw.get_signal_strength() - print(f"\n LOCKED") - print(f" SNR: {sig['snr_db']:.1f} dB (raw 0x{sig['snr_raw']:04X})") - print(f" Quality: {signal_bar(sig['snr_pct'])}") - else: - print(f"\n NO LOCK after {timeout}s") - print(f" Possible causes:") - print(f" - No signal at {freq_rf} MHz (station may be off-air)") - print(f" - Wrong symbol rate (try scanning first)") - print(f" - Dish not aligned to {QO100_ORBITAL_POS} deg E") - print(f" - LNB LO mismatch (expected {lnb_lo} MHz)") - - -def cmd_watch(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Tune to a QO-100 frequency and pipe the transport stream to a video player.""" - lnb_lo = args.lnb_lo - freq_rf = args.freq - sr_ksps = args.sr - mod_name = args.mod - fec_name = args.fec - player_cmd = args.player - - # Validate - if sr_ksps < BCM4500_MIN_SR_KSPS: - print(f"ERROR: Symbol rate {sr_ksps} ksps is below BCM4500 minimum ({BCM4500_MIN_SR_KSPS} ksps)") - sys.exit(1) - - if mod_name not in MODULATIONS: - print(f"Unknown modulation: {mod_name}") - print(f"Valid: {', '.join(MODULATIONS.keys())}") - sys.exit(1) - - mod_index, mod_desc = MODULATIONS[mod_name] - fec_index = _resolve_fec(mod_name, fec_name) - - if_mhz = rf_to_if(freq_rf, lnb_lo) - if_khz = int(if_mhz * 1000) - sr_sps = sr_ksps * 1000 - - if if_khz < 950000 or if_khz > 2150000: - print(f"ERROR: IF frequency {if_mhz:.1f} MHz is outside 950-2150 MHz range") - print(f" RF: {freq_rf} MHz, LNB LO: {lnb_lo} MHz") - sys.exit(1) - - # Status messages go to stderr so stdout is clean for piping - status = sys.stderr - - status.write(f"QO-100 Watch\n") - status.write(f"{'=' * 60}\n") - status.write(f" RF Frequency: {freq_rf} MHz\n") - status.write(f" IF Frequency: {if_mhz:.2f} MHz\n") - status.write(f" Symbol Rate: {sr_ksps} ksps\n") - status.write(f" Modulation: {mod_desc}\n") - status.write(f" FEC: {fec_name}\n") - if player_cmd: - status.write(f" Player: {player_cmd}\n") - else: - status.write(f" Output: stdout (pipe to player)\n") - status.write(f"\n") - status.flush() - - sw.ensure_booted() - - # Tune and wait for lock - status.write("Tuning...\n") - status.flush() - sw.tune(sr_sps, if_khz, mod_index, fec_index) - - timeout = args.timeout - deadline = time.time() + timeout - locked = False - - while time.time() < deadline: - if sw.get_signal_lock(): - locked = True - break - time.sleep(0.3) - - if not locked: - status.write(f"NO LOCK after {timeout}s -- aborting\n") - status.flush() - sys.exit(1) - - sig = sw.get_signal_strength() - status.write(f"LOCKED SNR {sig['snr_db']:.1f} dB {signal_bar(sig['snr_pct'], width=20)}\n") - status.flush() - - # Open player subprocess or use stdout - player_proc = None - output_fd = None - - if player_cmd: - try: - player_proc = subprocess.Popen( - player_cmd, shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - output_fd = player_proc.stdin - status.write(f"Player started (PID {player_proc.pid})\n") - status.flush() - except OSError as e: - status.write(f"Failed to start player: {e}\n") - status.flush() - sys.exit(1) - else: - output_fd = sys.stdout.buffer - - # Stream - sw.arm_transfer(on=True) - status.write("Streaming...\n") - status.flush() - - total_bytes = 0 - start_time = time.time() - last_status = start_time - running = True - - def stop_handler(signum, frame): - nonlocal running - running = False - - signal_mod.signal(signal_mod.SIGINT, stop_handler) - signal_mod.signal(signal_mod.SIGTERM, stop_handler) - - try: - while running: - # Check if player is still alive - if player_proc and player_proc.poll() is not None: - status.write(f"\nPlayer exited (code {player_proc.returncode})\n") - status.flush() - break - - chunk = sw.read_stream(timeout=2000) - if chunk: - try: - output_fd.write(chunk) - output_fd.flush() - total_bytes += len(chunk) - except BrokenPipeError: - status.write("\nPipe closed\n") - status.flush() - break - - now = time.time() - if now - last_status >= 2.0: - elapsed = now - start_time - bitrate = (total_bytes * 8) / elapsed if elapsed > 0 else 0 - if bitrate >= 1e6: - rate_str = f"{bitrate / 1e6:.2f} Mbps" - else: - rate_str = f"{bitrate / 1e3:.1f} kbps" - - # Quick signal check - still_locked = sw.get_signal_lock() - lock_str = "LOCK" if still_locked else "----" - - status.write(f"\r [{lock_str}] {total_bytes:,} bytes " - f"{rate_str} ({elapsed:.0f}s) ") - status.flush() - last_status = now - - finally: - sw.arm_transfer(on=False) - if player_proc: - player_proc.terminate() - player_proc.wait(timeout=5) - status.write(f"\n Stopped. Total: {total_bytes:,} bytes\n") - status.flush() - - -# --- CLI --- - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - prog="qo100.py", - description="QO-100 (Es'hail-2) DATV reception tool for the SkyWalker-1", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -examples: - %(prog)s calc --lnb-lo 9750 - %(prog)s calc --lnb-lo 9361 - %(prog)s band-plan --lnb-lo 9750 - %(prog)s scan --lnb-lo 9750 - %(prog)s scan --lnb-lo 9361 --step 0.25 --dwell 100 - %(prog)s tune --lnb-lo 9750 --freq 10491.5 --sr 1500 - %(prog)s tune --lnb-lo 9750 --freq 10491.5 --sr 1500 --fec 3/4 - %(prog)s watch --lnb-lo 9750 --freq 10491.5 --sr 1500 --player "ffplay -f mpegts -i pipe:0" - %(prog)s watch --lnb-lo 9750 --freq 10491.5 --sr 1500 --player "mpv -" - %(prog)s watch --lnb-lo 9750 --freq 10491.5 --sr 1500 | vlc - - -QO-100 wideband transponder: 10491-10499 MHz (DVB-S QPSK, various SRs) -BCM4500 minimum symbol rate: 256 ksps -Common LNB LOs: 9750 (universal), 9361 (TCXO PLL, popular for QO-100) - -The --lnb-lo parameter is required for all commands. It must match your -LNB's actual local oscillator frequency for correct IF calculation. -""") - parser.add_argument('-v', '--verbose', action='store_true', - help="Verbose output (USB traffic, extra detail)") - - sub = parser.add_subparsers(dest='command') - - # calc - p_calc = sub.add_parser('calc', - help="Show IF frequencies for a given LNB LO", - formatter_class=argparse.RawDescriptionHelpFormatter) - p_calc.add_argument('--lnb-lo', type=float, required=True, - help="LNB local oscillator frequency in MHz") - - # band-plan - p_bp = sub.add_parser('band-plan', - help="Show known QO-100 stations with IF conversion", - formatter_class=argparse.RawDescriptionHelpFormatter) - p_bp.add_argument('--lnb-lo', type=float, required=True, - help="LNB local oscillator frequency in MHz") - - # scan - p_scan = sub.add_parser('scan', - help="Scan wideband transponder for active carriers", - formatter_class=argparse.RawDescriptionHelpFormatter) - p_scan.add_argument('--lnb-lo', type=float, required=True, - help="LNB local oscillator frequency in MHz") - p_scan.add_argument('--step', type=float, default=0.5, - help="Frequency step in MHz (default: 0.5, finer for low-SR)") - p_scan.add_argument('--dwell', type=int, default=75, - help="Dwell time per step in ms (default: 75, longer for sensitivity)") - p_scan.add_argument('--threshold', type=float, default=3.0, - help="Peak detection threshold in dB (default: 3.0)") - - # tune - p_tune = sub.add_parser('tune', - help="Tune to a specific QO-100 frequency", - formatter_class=argparse.RawDescriptionHelpFormatter) - p_tune.add_argument('--lnb-lo', type=float, required=True, - help="LNB local oscillator frequency in MHz") - p_tune.add_argument('--freq', type=float, required=True, - help="RF frequency in MHz (e.g. 10491.5)") - p_tune.add_argument('--sr', type=int, required=True, - help="Symbol rate in ksps (e.g. 1500)") - p_tune.add_argument('--mod', default='qpsk', - choices=list(MODULATIONS.keys()), - help="Modulation type (default: qpsk)") - p_tune.add_argument('--fec', default='auto', - help="FEC rate (default: auto)") - p_tune.add_argument('--timeout', type=float, default=10, - help="Lock timeout in seconds (default: 10)") - - # watch - p_watch = sub.add_parser('watch', - help="Tune and pipe transport stream to video player", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -Watch pipes the raw MPEG-2 transport stream to a video player or stdout. -Status output goes to stderr so the TS data on stdout stays clean. - -Player examples: - --player "ffplay -f mpegts -i pipe:0" - --player "mpv --demuxer=lavf -" - --player "vlc --demux ts -" - -Without --player, the TS stream is written to stdout for shell piping: - qo100.py watch --lnb-lo 9750 --freq 10491.5 --sr 1500 | vlc - -""") - p_watch.add_argument('--lnb-lo', type=float, required=True, - help="LNB local oscillator frequency in MHz") - p_watch.add_argument('--freq', type=float, required=True, - help="RF frequency in MHz (e.g. 10491.5)") - p_watch.add_argument('--sr', type=int, required=True, - help="Symbol rate in ksps (e.g. 1500)") - p_watch.add_argument('--mod', default='qpsk', - choices=list(MODULATIONS.keys()), - help="Modulation type (default: qpsk)") - p_watch.add_argument('--fec', default='auto', - help="FEC rate (default: auto)") - p_watch.add_argument('--player', default=None, - help="Player command (e.g. 'ffplay -f mpegts -i pipe:0')") - p_watch.add_argument('--timeout', type=float, default=15, - help="Lock timeout in seconds (default: 15)") - - return parser - - -def main(): - parser = build_parser() - args = parser.parse_args() - - if not args.command: - parser.print_help() - sys.exit(0) - - # calc and band-plan don't need the device - if args.command in ('calc', 'band-plan'): - dispatch = { - 'calc': cmd_calc, - 'band-plan': cmd_band_plan, - } - handler = dispatch[args.command] - handler(None, args) - return - - dispatch = { - 'scan': cmd_scan, - 'tune': cmd_tune, - 'watch': cmd_watch, - } - - handler = dispatch.get(args.command) - if handler is None: - parser.print_help() - sys.exit(1) - - with SkyWalker1(verbose=args.verbose) as sw: - handler(sw, args) - - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 +""" +QO-100 (Es'hail-2) DATV reception tool for the Genpix SkyWalker-1. + +Provides frequency calculation, band plan display, wideband transponder +scanning, tuning, and live video piping for QO-100 amateur television +signals received via the SkyWalker-1 DVB-S demodulator. + +QO-100 wideband transponder: 10491-10499 MHz (DVB-S QPSK, various SRs) +Narrowband transponder: 10489.5-10490 MHz (SSB/CW, not demodulable) +Engineering beacon: 10489.75 MHz (CW) + +The BCM4500 demodulator has a minimum symbol rate of 256 ksps. QO-100 +DATV signals typically range from 333 ksps to 2000 ksps, well within +the hardware capability. Signals below 256 ksps are detectable as +energy via spectrum sweep but cannot be locked/demodulated. +""" + +import sys +import os +import argparse +import time +import signal as signal_mod +import subprocess + +# Add tools directory to path for library import +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from skywalker_lib import ( + SkyWalker1, MODULATIONS, FEC_RATES, MOD_FEC_GROUP, + rf_to_if, if_to_rf, detect_peaks, signal_bar, +) + + +# --- QO-100 constants --- + +QO100_WB_START_MHZ = 10491.0 # wideband transponder start +QO100_WB_STOP_MHZ = 10499.0 # wideband transponder stop +QO100_BEACON_MHZ = 10489.75 # engineering beacon +QO100_ORBITAL_POS = 25.9 # degrees East (Es'hail-2) +QO100_NB_START_MHZ = 10489.5 # narrowband transponder start +QO100_NB_STOP_MHZ = 10490.0 # narrowband transponder stop +BCM4500_MIN_SR_KSPS = 256 # hardware minimum + +# Common modified LNB local oscillators used for QO-100 reception +COMMON_QO100_LOS = { + 9361: "Modified PLL LNB (TCXO, popular)", + 9000: "Round LO", + 9100: "Round LO", + 9200: "Round LO", + 9300: "Round LO", + 9750: "Standard universal (low band)", +} + +# Known DATV stations / frequencies on QO-100 wideband transponder +QO100_KNOWN_STATIONS = [ + {"call": "BATC", "freq_mhz": 10491.5, "sr_ksps": 1500, "mod": "qpsk", "fec": "3/4", "note": "British ATV Club beacon"}, + {"call": "Various", "freq_mhz": 10492.0, "sr_ksps": 1000, "mod": "qpsk", "fec": "1/2", "note": "Common DATV frequency"}, + {"call": "Various", "freq_mhz": 10493.0, "sr_ksps": 500, "mod": "qpsk", "fec": "1/2", "note": "Low-power DATV"}, + {"call": "Various", "freq_mhz": 10494.0, "sr_ksps": 333, "mod": "qpsk", "fec": "1/2", "note": "Minimum viable DVB-S"}, + {"call": "Beacon", "freq_mhz": 10489.75, "sr_ksps": 0, "mod": "cw", "fec": "-", "note": "Engineering beacon (CW)"}, +] + + +# --- Helper functions --- + +def validate_qo100_lo(lnb_lo_mhz: int) -> dict: + """ + Check whether a given LNB LO places the QO-100 wideband transponder + within the SkyWalker-1 IF range (950-2150 MHz). + + Returns dict with: + valid - bool, True if entire WB transponder fits in IF range + if_range - (start_if, stop_if) tuple in MHz + warnings - list of warning strings + """ + start_if = rf_to_if(QO100_WB_START_MHZ, lnb_lo_mhz) + stop_if = rf_to_if(QO100_WB_STOP_MHZ, lnb_lo_mhz) + warnings = [] + + if start_if < 950: + warnings.append(f"WB start IF {start_if:.0f} MHz is below 950 MHz minimum") + if stop_if > 2150: + warnings.append(f"WB stop IF {stop_if:.0f} MHz is above 2150 MHz maximum") + if start_if < 0 or stop_if < 0: + warnings.append(f"Negative IF -- LNB LO {lnb_lo_mhz} MHz is above the RF frequency") + + # Check if LO is a known value + if lnb_lo_mhz not in COMMON_QO100_LOS: + nearby = [lo for lo in COMMON_QO100_LOS if abs(lo - lnb_lo_mhz) <= 100] + if not nearby: + warnings.append(f"LO {lnb_lo_mhz} MHz is not a common QO-100 value") + + valid = (950 <= start_if) and (stop_if <= 2150) + + return { + "valid": valid, + "if_range": (start_if, stop_if), + "warnings": warnings, + } + + +def qo100_if_range(lnb_lo_mhz: float) -> tuple: + """Return (start_if, stop_if) in MHz for the QO-100 WB transponder.""" + return ( + rf_to_if(QO100_WB_START_MHZ, lnb_lo_mhz), + rf_to_if(QO100_WB_STOP_MHZ, lnb_lo_mhz), + ) + + +def qo100_band_plan(lnb_lo_mhz: float) -> list: + """ + Return the QO-100 known station list augmented with IF frequencies + and lockability status for the given LNB LO. + + Each entry is a dict with keys: + call, freq_mhz (RF), if_mhz, sr_ksps, mod, fec, note, lockable + """ + plan = [] + for station in QO100_KNOWN_STATIONS: + if_mhz = rf_to_if(station["freq_mhz"], lnb_lo_mhz) + lockable = ( + station["sr_ksps"] >= BCM4500_MIN_SR_KSPS + and station["mod"] in MODULATIONS + and 950 <= if_mhz <= 2150 + ) + plan.append({ + "call": station["call"], + "freq_mhz": station["freq_mhz"], + "if_mhz": if_mhz, + "sr_ksps": station["sr_ksps"], + "mod": station["mod"], + "fec": station["fec"], + "note": station["note"], + "lockable": lockable, + }) + return plan + + +def _resolve_fec(mod_name: str, fec_name: str) -> int: + """Look up the FEC index for a given modulation and FEC rate string.""" + fec_group = MOD_FEC_GROUP.get(mod_name) + if fec_group is None: + print(f"Unknown modulation: {mod_name}") + sys.exit(1) + fec_table = FEC_RATES[fec_group] + if fec_name not in fec_table: + print(f"Invalid FEC '{fec_name}' for {mod_name}") + print(f"Valid: {', '.join(fec_table.keys())}") + sys.exit(1) + return fec_table[fec_name] + + +def _print_lo_info(lnb_lo: float, verbose: bool = False) -> None: + """Print LNB LO validation summary.""" + lo_desc = COMMON_QO100_LOS.get(int(lnb_lo), "custom") + print(f" LNB LO: {lnb_lo:.0f} MHz ({lo_desc})") + + check = validate_qo100_lo(lnb_lo) + start_if, stop_if = check["if_range"] + print(f" WB IF range: {start_if:.1f} - {stop_if:.1f} MHz") + + if not check["valid"]: + print(f" WARNING: QO-100 WB transponder does not fit in IF range!") + for w in check["warnings"]: + print(f" WARNING: {w}") + + if verbose: + beacon_if = rf_to_if(QO100_BEACON_MHZ, lnb_lo) + nb_start_if = rf_to_if(QO100_NB_START_MHZ, lnb_lo) + nb_stop_if = rf_to_if(QO100_NB_STOP_MHZ, lnb_lo) + print(f" Beacon IF: {beacon_if:.2f} MHz (CW, not demodulable)") + print(f" NB IF range: {nb_start_if:.1f} - {nb_stop_if:.1f} MHz (SSB/CW)") + + +# --- Subcommand handlers --- + +def cmd_calc(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Show IF frequencies for a given LNB LO.""" + lnb_lo = args.lnb_lo + + print(f"QO-100 IF Frequency Calculator") + print(f"{'=' * 60}") + print(f" Satellite: Es'hail-2 (QO-100) at {QO100_ORBITAL_POS} deg E") + _print_lo_info(lnb_lo, verbose=True) + + print(f"\n {'RF (MHz)':>12} {'IF (MHz)':>10} {'Description'}") + print(f" {'─' * 50}") + + entries = [ + (QO100_NB_START_MHZ, "Narrowband start"), + (QO100_BEACON_MHZ, "Engineering beacon (CW)"), + (QO100_NB_STOP_MHZ, "Narrowband stop"), + (QO100_WB_START_MHZ, "Wideband start"), + (QO100_WB_STOP_MHZ, "Wideband stop"), + ] + + for rf, desc in entries: + if_mhz = rf_to_if(rf, lnb_lo) + in_range = 950 <= if_mhz <= 2150 + marker = "" if in_range else " [OUT OF RANGE]" + print(f" {rf:12.2f} {if_mhz:10.2f} {desc}{marker}") + + # Common LO comparison table + print(f"\n Common LNB LO comparison:") + print(f" {'LO (MHz)':>10} {'WB Start IF':>12} {'WB Stop IF':>12} {'Description'}") + print(f" {'─' * 60}") + for lo, desc in sorted(COMMON_QO100_LOS.items()): + s_if = rf_to_if(QO100_WB_START_MHZ, lo) + e_if = rf_to_if(QO100_WB_STOP_MHZ, lo) + fits = 950 <= s_if and e_if <= 2150 + status = "" if fits else " [!]" + current = " <--" if lo == int(lnb_lo) else "" + print(f" {lo:10d} {s_if:12.1f} {e_if:12.1f} {desc}{status}{current}") + + +def cmd_band_plan(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Show known QO-100 stations with IF conversion for this LNB LO.""" + lnb_lo = args.lnb_lo + + print(f"QO-100 Wideband Transponder Band Plan") + print(f"{'=' * 60}") + _print_lo_info(lnb_lo, verbose=args.verbose) + print() + + plan = qo100_band_plan(lnb_lo) + + # Table header + hdr = (f" {'Call':8s} {'RF MHz':>9s} {'IF MHz':>9s} " + f"{'SR ksps':>8s} {'Mod':5s} {'FEC':4s} {'Lock':4s} Note") + print(hdr) + print(f" {'─' * 76}") + + for entry in plan: + sr_str = f"{entry['sr_ksps']:>5d}" if entry["sr_ksps"] > 0 else " n/a" + if entry["lockable"]: + lock_str = " yes" + elif entry["sr_ksps"] == 0: + lock_str = " --" + elif entry["sr_ksps"] < BCM4500_MIN_SR_KSPS: + lock_str = " no" + elif entry["mod"] not in MODULATIONS: + lock_str = " no" + else: + lock_str = " no" + + in_range = 950 <= entry["if_mhz"] <= 2150 + if_str = f"{entry['if_mhz']:9.2f}" if in_range else f"{entry['if_mhz']:7.2f} !" + + print(f" {entry['call']:8s} {entry['freq_mhz']:9.2f} {if_str} " + f"{sr_str:>8s} {entry['mod']:5s} {entry['fec']:4s} {lock_str} " + f"{entry['note']}") + + # Legend + print(f"\n Lock column: yes = lockable by BCM4500 (SR >= {BCM4500_MIN_SR_KSPS} ksps, " + f"supported mod, IF in range)") + print(f" no = detectable as energy but not demodulable") + print(f" -- = not a digital signal (CW/SSB)") + + +def cmd_scan(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Scan the QO-100 wideband transponder for active carriers.""" + lnb_lo = args.lnb_lo + step_mhz = args.step + dwell_ms = args.dwell + + # Validate LO + check = validate_qo100_lo(lnb_lo) + if not check["valid"]: + print(f"ERROR: QO-100 WB transponder does not fit in IF range with LO {lnb_lo} MHz") + for w in check["warnings"]: + print(f" {w}") + sys.exit(1) + + start_if, stop_if = check["if_range"] + + # QO-100 optimized sweep parameters + # Low symbol rates need longer dwell, finer steps, lower measurement SR + sr_ksps = 1000 # lower SR for better sensitivity to narrow signals + + steps = int((stop_if - start_if) / step_mhz) + 1 + est_time = steps * (dwell_ms + 5) / 1000.0 + + print(f"QO-100 Wideband Transponder Scan") + print(f"{'=' * 60}") + _print_lo_info(lnb_lo, verbose=args.verbose) + print(f" RF range: {QO100_WB_START_MHZ:.1f} - {QO100_WB_STOP_MHZ:.1f} MHz") + print(f" IF range: {start_if:.1f} - {stop_if:.1f} MHz") + print(f" Step: {step_mhz} MHz ({steps} points)") + print(f" Dwell: {dwell_ms} ms") + print(f" Meas SR: {sr_ksps} ksps") + print(f" Est. time: {est_time:.1f}s") + print() + + sw.ensure_booted() + + # Sweep the wideband transponder IF range + print("[1/3] Sweeping wideband transponder...") + + def progress(freq, step_num, total, result): + pct = (step_num + 1) / total * 100 + rf = if_to_rf(freq, lnb_lo) + sys.stdout.write(f"\r [{pct:5.1f}%] IF {freq:.1f} MHz RF {rf:.1f} MHz" + f" pwr={result['power_db']:.1f} dB" + f" AGC={result['agc1']}") + sys.stdout.flush() + + freqs, powers, results = sw.sweep_spectrum( + start_if, stop_if, step_mhz, dwell_ms, sr_ksps, + callback=progress if not args.verbose else None + ) + sys.stdout.write("\r" + " " * 70 + "\r") + print(f" {len(freqs)} points measured") + + # Peak detection + print(f"\n[2/3] Peak detection (threshold {args.threshold:.0f} dB)...") + peaks = detect_peaks(freqs, powers, threshold_db=args.threshold) + + if not peaks: + print(" No carriers detected above noise floor.") + print(" Check dish alignment, LNB LO, and that the transponder is active.") + return + + print(f" {len(peaks)} carrier(s) detected:") + print() + print(f" {'IF MHz':>8s} {'RF MHz':>10s} {'Power dB':>9s} Nearest known station") + print(f" {'─' * 55}") + + for freq_if, pwr, idx in peaks: + freq_rf = if_to_rf(freq_if, lnb_lo) + + # Match to nearest known station + nearest = None + nearest_dist = 999 + for station in QO100_KNOWN_STATIONS: + dist = abs(station["freq_mhz"] - freq_rf) + if dist < nearest_dist: + nearest_dist = dist + nearest = station + + match_str = "" + if nearest and nearest_dist < 1.0: + lockable = nearest["sr_ksps"] >= BCM4500_MIN_SR_KSPS + lock_note = "" if lockable else " [below min SR]" + match_str = (f"{nearest['call']} ({nearest['sr_ksps']} ksps " + f"{nearest['mod']} {nearest['fec']}){lock_note}") + elif nearest and nearest_dist < 2.0: + match_str = f"near {nearest['call']} ({nearest_dist:.1f} MHz off)" + + print(f" {freq_if:8.1f} {freq_rf:10.2f} {pwr:9.1f} {match_str}") + + # Try locking each peak that could be a DATV signal + print(f"\n[3/3] Attempting lock on detected carriers...") + locked_count = 0 + + for freq_if, pwr, idx in peaks: + freq_rf = if_to_rf(freq_if, lnb_lo) + if_khz = int(freq_if * 1000) + + # Try common QO-100 symbol rates, highest first + trial_srs = [1500, 1000, 500, 333, 256] + + for sr in trial_srs: + if sr < BCM4500_MIN_SR_KSPS: + continue + + sr_sps = sr * 1000 + mod_index, _ = MODULATIONS["qpsk"] + fec_group = MOD_FEC_GROUP["qpsk"] + fec_index = FEC_RATES[fec_group]["auto"] + + if args.verbose: + print(f" Trying {freq_rf:.2f} MHz SR {sr} ksps...", end="", flush=True) + + sw.tune(sr_sps, if_khz, mod_index, fec_index) + time.sleep(0.3) + + if sw.get_signal_lock(): + sig = sw.get_signal_strength() + print(f" LOCKED {freq_rf:.2f} MHz SR {sr} ksps " + f"SNR {sig['snr_db']:.1f} dB {signal_bar(sig['snr_pct'], width=20)}") + locked_count += 1 + break + elif args.verbose: + print(f" no lock") + + print(f"\n Scan complete: {len(peaks)} carriers detected, {locked_count} locked") + + +def cmd_tune(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Tune to a specific QO-100 frequency.""" + lnb_lo = args.lnb_lo + freq_rf = args.freq + sr_ksps = args.sr + mod_name = args.mod + fec_name = args.fec + + # Validate + if sr_ksps < BCM4500_MIN_SR_KSPS: + print(f"ERROR: Symbol rate {sr_ksps} ksps is below BCM4500 minimum ({BCM4500_MIN_SR_KSPS} ksps)") + sys.exit(1) + + if mod_name not in MODULATIONS: + print(f"Unknown modulation: {mod_name}") + print(f"Valid: {', '.join(MODULATIONS.keys())}") + sys.exit(1) + + mod_index, mod_desc = MODULATIONS[mod_name] + fec_index = _resolve_fec(mod_name, fec_name) + + if_mhz = rf_to_if(freq_rf, lnb_lo) + if_khz = int(if_mhz * 1000) + sr_sps = sr_ksps * 1000 + + if if_khz < 950000 or if_khz > 2150000: + print(f"ERROR: IF frequency {if_mhz:.1f} MHz is outside 950-2150 MHz range") + print(f" RF: {freq_rf} MHz, LNB LO: {lnb_lo} MHz") + sys.exit(1) + + print(f"QO-100 Tune") + print(f"{'=' * 60}") + _print_lo_info(lnb_lo, verbose=args.verbose) + print(f" RF Frequency: {freq_rf} MHz") + print(f" IF Frequency: {if_mhz:.2f} MHz ({if_khz} kHz)") + print(f" Symbol Rate: {sr_ksps} ksps ({sr_sps} sps)") + print(f" Modulation: {mod_desc}") + print(f" FEC: {fec_name} (index {fec_index})") + print() + + sw.ensure_booted() + + # Tune + print("Sending tune command...", end="", flush=True) + sw.tune(sr_sps, if_khz, mod_index, fec_index) + print(" done") + + # Wait for lock + timeout = args.timeout + print(f"Waiting for lock (timeout {timeout}s)...", end="", flush=True) + deadline = time.time() + timeout + locked = False + dots = 0 + + while time.time() < deadline: + if sw.get_signal_lock(): + locked = True + break + print(".", end="", flush=True) + dots += 1 + time.sleep(0.5) + + print() + + if locked: + sig = sw.get_signal_strength() + print(f"\n LOCKED") + print(f" SNR: {sig['snr_db']:.1f} dB (raw 0x{sig['snr_raw']:04X})") + print(f" Quality: {signal_bar(sig['snr_pct'])}") + else: + print(f"\n NO LOCK after {timeout}s") + print(f" Possible causes:") + print(f" - No signal at {freq_rf} MHz (station may be off-air)") + print(f" - Wrong symbol rate (try scanning first)") + print(f" - Dish not aligned to {QO100_ORBITAL_POS} deg E") + print(f" - LNB LO mismatch (expected {lnb_lo} MHz)") + + +def cmd_watch(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Tune to a QO-100 frequency and pipe the transport stream to a video player.""" + lnb_lo = args.lnb_lo + freq_rf = args.freq + sr_ksps = args.sr + mod_name = args.mod + fec_name = args.fec + player_cmd = args.player + + # Validate + if sr_ksps < BCM4500_MIN_SR_KSPS: + print(f"ERROR: Symbol rate {sr_ksps} ksps is below BCM4500 minimum ({BCM4500_MIN_SR_KSPS} ksps)") + sys.exit(1) + + if mod_name not in MODULATIONS: + print(f"Unknown modulation: {mod_name}") + print(f"Valid: {', '.join(MODULATIONS.keys())}") + sys.exit(1) + + mod_index, mod_desc = MODULATIONS[mod_name] + fec_index = _resolve_fec(mod_name, fec_name) + + if_mhz = rf_to_if(freq_rf, lnb_lo) + if_khz = int(if_mhz * 1000) + sr_sps = sr_ksps * 1000 + + if if_khz < 950000 or if_khz > 2150000: + print(f"ERROR: IF frequency {if_mhz:.1f} MHz is outside 950-2150 MHz range") + print(f" RF: {freq_rf} MHz, LNB LO: {lnb_lo} MHz") + sys.exit(1) + + # Status messages go to stderr so stdout is clean for piping + status = sys.stderr + + status.write(f"QO-100 Watch\n") + status.write(f"{'=' * 60}\n") + status.write(f" RF Frequency: {freq_rf} MHz\n") + status.write(f" IF Frequency: {if_mhz:.2f} MHz\n") + status.write(f" Symbol Rate: {sr_ksps} ksps\n") + status.write(f" Modulation: {mod_desc}\n") + status.write(f" FEC: {fec_name}\n") + if player_cmd: + status.write(f" Player: {player_cmd}\n") + else: + status.write(f" Output: stdout (pipe to player)\n") + status.write(f"\n") + status.flush() + + sw.ensure_booted() + + # Tune and wait for lock + status.write("Tuning...\n") + status.flush() + sw.tune(sr_sps, if_khz, mod_index, fec_index) + + timeout = args.timeout + deadline = time.time() + timeout + locked = False + + while time.time() < deadline: + if sw.get_signal_lock(): + locked = True + break + time.sleep(0.3) + + if not locked: + status.write(f"NO LOCK after {timeout}s -- aborting\n") + status.flush() + sys.exit(1) + + sig = sw.get_signal_strength() + status.write(f"LOCKED SNR {sig['snr_db']:.1f} dB {signal_bar(sig['snr_pct'], width=20)}\n") + status.flush() + + # Open player subprocess or use stdout + player_proc = None + output_fd = None + + if player_cmd: + try: + player_proc = subprocess.Popen( + player_cmd, shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + output_fd = player_proc.stdin + status.write(f"Player started (PID {player_proc.pid})\n") + status.flush() + except OSError as e: + status.write(f"Failed to start player: {e}\n") + status.flush() + sys.exit(1) + else: + output_fd = sys.stdout.buffer + + # Stream + sw.arm_transfer(on=True) + status.write("Streaming...\n") + status.flush() + + total_bytes = 0 + start_time = time.time() + last_status = start_time + running = True + + def stop_handler(signum, frame): + nonlocal running + running = False + + signal_mod.signal(signal_mod.SIGINT, stop_handler) + signal_mod.signal(signal_mod.SIGTERM, stop_handler) + + try: + while running: + # Check if player is still alive + if player_proc and player_proc.poll() is not None: + status.write(f"\nPlayer exited (code {player_proc.returncode})\n") + status.flush() + break + + chunk = sw.read_stream(timeout=2000) + if chunk: + try: + output_fd.write(chunk) + output_fd.flush() + total_bytes += len(chunk) + except BrokenPipeError: + status.write("\nPipe closed\n") + status.flush() + break + + now = time.time() + if now - last_status >= 2.0: + elapsed = now - start_time + bitrate = (total_bytes * 8) / elapsed if elapsed > 0 else 0 + if bitrate >= 1e6: + rate_str = f"{bitrate / 1e6:.2f} Mbps" + else: + rate_str = f"{bitrate / 1e3:.1f} kbps" + + # Quick signal check + still_locked = sw.get_signal_lock() + lock_str = "LOCK" if still_locked else "----" + + status.write(f"\r [{lock_str}] {total_bytes:,} bytes " + f"{rate_str} ({elapsed:.0f}s) ") + status.flush() + last_status = now + + finally: + sw.arm_transfer(on=False) + if player_proc: + player_proc.terminate() + player_proc.wait(timeout=5) + status.write(f"\n Stopped. Total: {total_bytes:,} bytes\n") + status.flush() + + +# --- CLI --- + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="qo100.py", + description="QO-100 (Es'hail-2) DATV reception tool for the SkyWalker-1", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +examples: + %(prog)s calc --lnb-lo 9750 + %(prog)s calc --lnb-lo 9361 + %(prog)s band-plan --lnb-lo 9750 + %(prog)s scan --lnb-lo 9750 + %(prog)s scan --lnb-lo 9361 --step 0.25 --dwell 100 + %(prog)s tune --lnb-lo 9750 --freq 10491.5 --sr 1500 + %(prog)s tune --lnb-lo 9750 --freq 10491.5 --sr 1500 --fec 3/4 + %(prog)s watch --lnb-lo 9750 --freq 10491.5 --sr 1500 --player "ffplay -f mpegts -i pipe:0" + %(prog)s watch --lnb-lo 9750 --freq 10491.5 --sr 1500 --player "mpv -" + %(prog)s watch --lnb-lo 9750 --freq 10491.5 --sr 1500 | vlc - + +QO-100 wideband transponder: 10491-10499 MHz (DVB-S QPSK, various SRs) +BCM4500 minimum symbol rate: 256 ksps +Common LNB LOs: 9750 (universal), 9361 (TCXO PLL, popular for QO-100) + +The --lnb-lo parameter is required for all commands. It must match your +LNB's actual local oscillator frequency for correct IF calculation. +""") + parser.add_argument('-v', '--verbose', action='store_true', + help="Verbose output (USB traffic, extra detail)") + + sub = parser.add_subparsers(dest='command') + + # calc + p_calc = sub.add_parser('calc', + help="Show IF frequencies for a given LNB LO", + formatter_class=argparse.RawDescriptionHelpFormatter) + p_calc.add_argument('--lnb-lo', type=float, required=True, + help="LNB local oscillator frequency in MHz") + + # band-plan + p_bp = sub.add_parser('band-plan', + help="Show known QO-100 stations with IF conversion", + formatter_class=argparse.RawDescriptionHelpFormatter) + p_bp.add_argument('--lnb-lo', type=float, required=True, + help="LNB local oscillator frequency in MHz") + + # scan + p_scan = sub.add_parser('scan', + help="Scan wideband transponder for active carriers", + formatter_class=argparse.RawDescriptionHelpFormatter) + p_scan.add_argument('--lnb-lo', type=float, required=True, + help="LNB local oscillator frequency in MHz") + p_scan.add_argument('--step', type=float, default=0.5, + help="Frequency step in MHz (default: 0.5, finer for low-SR)") + p_scan.add_argument('--dwell', type=int, default=75, + help="Dwell time per step in ms (default: 75, longer for sensitivity)") + p_scan.add_argument('--threshold', type=float, default=3.0, + help="Peak detection threshold in dB (default: 3.0)") + + # tune + p_tune = sub.add_parser('tune', + help="Tune to a specific QO-100 frequency", + formatter_class=argparse.RawDescriptionHelpFormatter) + p_tune.add_argument('--lnb-lo', type=float, required=True, + help="LNB local oscillator frequency in MHz") + p_tune.add_argument('--freq', type=float, required=True, + help="RF frequency in MHz (e.g. 10491.5)") + p_tune.add_argument('--sr', type=int, required=True, + help="Symbol rate in ksps (e.g. 1500)") + p_tune.add_argument('--mod', default='qpsk', + choices=list(MODULATIONS.keys()), + help="Modulation type (default: qpsk)") + p_tune.add_argument('--fec', default='auto', + help="FEC rate (default: auto)") + p_tune.add_argument('--timeout', type=float, default=10, + help="Lock timeout in seconds (default: 10)") + + # watch + p_watch = sub.add_parser('watch', + help="Tune and pipe transport stream to video player", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +Watch pipes the raw MPEG-2 transport stream to a video player or stdout. +Status output goes to stderr so the TS data on stdout stays clean. + +Player examples: + --player "ffplay -f mpegts -i pipe:0" + --player "mpv --demuxer=lavf -" + --player "vlc --demux ts -" + +Without --player, the TS stream is written to stdout for shell piping: + qo100.py watch --lnb-lo 9750 --freq 10491.5 --sr 1500 | vlc - +""") + p_watch.add_argument('--lnb-lo', type=float, required=True, + help="LNB local oscillator frequency in MHz") + p_watch.add_argument('--freq', type=float, required=True, + help="RF frequency in MHz (e.g. 10491.5)") + p_watch.add_argument('--sr', type=int, required=True, + help="Symbol rate in ksps (e.g. 1500)") + p_watch.add_argument('--mod', default='qpsk', + choices=list(MODULATIONS.keys()), + help="Modulation type (default: qpsk)") + p_watch.add_argument('--fec', default='auto', + help="FEC rate (default: auto)") + p_watch.add_argument('--player', default=None, + help="Player command (e.g. 'ffplay -f mpegts -i pipe:0')") + p_watch.add_argument('--timeout', type=float, default=15, + help="Lock timeout in seconds (default: 15)") + + return parser + + +def main(): + parser = build_parser() + args = parser.parse_args() + + if not args.command: + parser.print_help() + sys.exit(0) + + # calc and band-plan don't need the device + if args.command in ('calc', 'band-plan'): + dispatch = { + 'calc': cmd_calc, + 'band-plan': cmd_band_plan, + } + handler = dispatch[args.command] + handler(None, args) + return + + dispatch = { + 'scan': cmd_scan, + 'tune': cmd_tune, + 'watch': cmd_watch, + } + + handler = dispatch.get(args.command) + if handler is None: + parser.print_help() + sys.exit(1) + + with SkyWalker1(verbose=args.verbose) as sw: + handler(sw, args) + + +if __name__ == '__main__': + main() diff --git a/tools/signal_analysis.py b/tools/signal_analysis.py index edb8ae1..569dfe1 100644 --- a/tools/signal_analysis.py +++ b/tools/signal_analysis.py @@ -1,239 +1,239 @@ -#!/usr/bin/env python3 -""" -Enhanced signal analysis for carrier detection and characterization. - -Goes beyond the basic detect_peaks() in skywalker_lib by using robust -noise floor estimation (median + MAD), peak width measurement at -3dB, -peak merging within estimated carrier bandwidth, and carrier classification. -""" - -import math -import statistics - - -def adaptive_noise_floor(powers: list) -> tuple: - """ - Robust noise floor estimation using median + MAD. - - The median is insensitive to strong carriers in the sweep, and the - Median Absolute Deviation (MAD) provides a robust spread measure - that won't be pulled by a few dominant peaks. - - Returns (noise_floor_db, mad_db). - """ - if not powers: - return (0.0, 0.0) - - median_power = statistics.median(powers) - deviations = [abs(p - median_power) for p in powers] - mad = statistics.median(deviations) if deviations else 0.0 - - # The noise floor sits at the median -- most bins are noise in a - # typical satellite IF sweep. MAD gives us an idea of how "bumpy" - # the noise is, useful for setting adaptive thresholds. - return (median_power, mad) - - -def detect_peaks_enhanced(freqs: list, powers: list, - threshold_db: float = 6.0) -> list: - """ - Enhanced peak detection with width estimation and merging. - - Returns list of dicts, each containing: - freq - center frequency in MHz - power - peak power in dB (relative) - index - index into freqs/powers arrays - width_mhz - estimated carrier bandwidth at -3dB - prominence_db - peak power above noise floor - - Steps: - 1. Compute adaptive noise floor (median + MAD) - 2. Find local maxima above noise_floor + threshold_db - 3. Estimate -3dB width around each peak - 4. Merge peaks whose -3dB extents overlap (same carrier) - """ - if len(powers) < 3 or len(freqs) != len(powers): - return [] - - noise_floor, mad = adaptive_noise_floor(powers) - # Effective threshold: user threshold, but never below 3x MAD to - # avoid chasing noise ripples. - effective_threshold = max(threshold_db, 3.0 * mad) if mad > 0 else threshold_db - min_power = noise_floor + effective_threshold - - # Step 1: find raw local maxima - raw_peaks = [] - for i in range(1, len(powers) - 1): - if powers[i] > powers[i - 1] and powers[i] > powers[i + 1]: - if powers[i] >= min_power: - raw_peaks.append(i) - - # Also check endpoints if they are strong - if len(powers) >= 2: - if powers[0] > powers[1] and powers[0] >= min_power: - raw_peaks.insert(0, 0) - if powers[-1] > powers[-2] and powers[-1] >= min_power: - raw_peaks.append(len(powers) - 1) - - if not raw_peaks: - return [] - - # Step 2: measure width and build peak dicts - peaks = [] - for idx in raw_peaks: - bw = estimate_carrier_bw(freqs, powers, idx) - prominence = powers[idx] - noise_floor - peaks.append({ - "freq": freqs[idx], - "power": powers[idx], - "index": idx, - "width_mhz": bw, - "prominence_db": prominence, - }) - - # Step 3: merge overlapping peaks (keep the stronger one) - merged = _merge_peaks(peaks) - return merged - - -def _merge_peaks(peaks: list) -> list: - """ - Merge peaks whose -3dB extents overlap. - - When two peaks are closer together than the sum of their half-widths - they likely belong to the same carrier. Keep the stronger peak and - take the wider bandwidth. - """ - if len(peaks) <= 1: - return peaks - - # Sort by frequency - peaks = sorted(peaks, key=lambda p: p["freq"]) - merged = [peaks[0]] - - for peak in peaks[1:]: - prev = merged[-1] - # Half-widths - prev_upper = prev["freq"] + prev["width_mhz"] / 2 - peak_lower = peak["freq"] - peak["width_mhz"] / 2 - - if peak_lower <= prev_upper: - # Overlap: keep the stronger peak, widen the bandwidth - if peak["power"] > prev["power"]: - wider = max(prev["width_mhz"], peak["width_mhz"], - (peak["freq"] + peak["width_mhz"] / 2) - - (prev["freq"] - prev["width_mhz"] / 2)) - peak["width_mhz"] = wider - merged[-1] = peak - else: - wider = max(prev["width_mhz"], peak["width_mhz"], - (peak["freq"] + peak["width_mhz"] / 2) - - (prev["freq"] - prev["width_mhz"] / 2)) - prev["width_mhz"] = wider - else: - merged.append(peak) - - return merged - - -def estimate_carrier_bw(freqs: list, powers: list, - peak_idx: int) -> float: - """ - Estimate carrier bandwidth by walking from peak until power drops - 3 dB below the peak value (the -3dB bandwidth). - - Walks left and right from the peak index, interpolating between - adjacent frequency bins when the -3dB crossing falls between them. - - Returns estimated bandwidth in MHz. Minimum return is one frequency - step width (avoids zero-width artifacts on single-bin peaks). - """ - if peak_idx < 0 or peak_idx >= len(powers): - return 0.0 - - peak_power = powers[peak_idx] - cutoff = peak_power - 3.0 - - # Minimum step size for fallback - if len(freqs) >= 2: - step = abs(freqs[1] - freqs[0]) - else: - return 0.0 - - # Walk left - left_freq = freqs[peak_idx] - for i in range(peak_idx - 1, -1, -1): - if powers[i] <= cutoff: - # Interpolate between bin i and bin i+1 - if powers[i + 1] != powers[i]: - frac = (cutoff - powers[i]) / (powers[i + 1] - powers[i]) - else: - frac = 0.5 - left_freq = freqs[i] + frac * (freqs[i + 1] - freqs[i]) - break - left_freq = freqs[i] - - # Walk right - right_freq = freqs[peak_idx] - for i in range(peak_idx + 1, len(powers)): - if powers[i] <= cutoff: - if powers[i - 1] != powers[i]: - frac = (cutoff - powers[i]) / (powers[i - 1] - powers[i]) - else: - frac = 0.5 - right_freq = freqs[i] - frac * (freqs[i] - freqs[i - 1]) - break - right_freq = freqs[i] - - bw = right_freq - left_freq - return max(bw, step) - - -def classify_carrier(bw_mhz: float, power_db: float) -> dict: - """ - Classify a detected carrier based on bandwidth and power. - - Uses empirical ranges for DVB-S symbol rates: SR (Msps) is roughly - BW (MHz) / 1.35 for QPSK with roll-off 0.35. - - Returns dict with: - estimated_sr_range - (min_sps, max_sps) tuple - likely_modulation - list of plausible modulation names - signal_quality - 'strong', 'moderate', or 'weak' - """ - # Roll-off factor for DVB-S is typically 0.35, so BW ~ SR * 1.35. - # Allow some tolerance on both sides. - sr_center = bw_mhz / 1.35 # Msps - sr_min = int(max(256_000, (bw_mhz / 1.5) * 1_000_000)) - sr_max = int(min(30_000_000, (bw_mhz / 1.2) * 1_000_000)) - if sr_max < sr_min: - sr_max = sr_min - - # Guess modulation based on bandwidth - likely_mods = [] - if bw_mhz < 3.0: - # Narrow carrier: low-SR data channels, SCPC, DCII split - likely_mods = ["qpsk", "dcii-i", "dcii-q", "dss"] - elif bw_mhz < 10.0: - # Medium: typical SCPC, small MCPC - likely_mods = ["qpsk", "turbo-qpsk", "dcii-combo"] - elif bw_mhz < 20.0: - # Wide: MCPC transponders - likely_mods = ["qpsk", "turbo-qpsk", "turbo-8psk"] - else: - # Very wide: full transponder, high-SR - likely_mods = ["qpsk", "turbo-qpsk", "turbo-8psk", "dcii-combo"] - - # Signal quality heuristic (relative power, device-dependent) - if power_db > -10.0: - quality = "strong" - elif power_db > -25.0: - quality = "moderate" - else: - quality = "weak" - - return { - "estimated_sr_range": (sr_min, sr_max), - "likely_modulation": likely_mods, - "signal_quality": quality, - } +#!/usr/bin/env python3 +""" +Enhanced signal analysis for carrier detection and characterization. + +Goes beyond the basic detect_peaks() in skywalker_lib by using robust +noise floor estimation (median + MAD), peak width measurement at -3dB, +peak merging within estimated carrier bandwidth, and carrier classification. +""" + +import math +import statistics + + +def adaptive_noise_floor(powers: list) -> tuple: + """ + Robust noise floor estimation using median + MAD. + + The median is insensitive to strong carriers in the sweep, and the + Median Absolute Deviation (MAD) provides a robust spread measure + that won't be pulled by a few dominant peaks. + + Returns (noise_floor_db, mad_db). + """ + if not powers: + return (0.0, 0.0) + + median_power = statistics.median(powers) + deviations = [abs(p - median_power) for p in powers] + mad = statistics.median(deviations) if deviations else 0.0 + + # The noise floor sits at the median -- most bins are noise in a + # typical satellite IF sweep. MAD gives us an idea of how "bumpy" + # the noise is, useful for setting adaptive thresholds. + return (median_power, mad) + + +def detect_peaks_enhanced(freqs: list, powers: list, + threshold_db: float = 6.0) -> list: + """ + Enhanced peak detection with width estimation and merging. + + Returns list of dicts, each containing: + freq - center frequency in MHz + power - peak power in dB (relative) + index - index into freqs/powers arrays + width_mhz - estimated carrier bandwidth at -3dB + prominence_db - peak power above noise floor + + Steps: + 1. Compute adaptive noise floor (median + MAD) + 2. Find local maxima above noise_floor + threshold_db + 3. Estimate -3dB width around each peak + 4. Merge peaks whose -3dB extents overlap (same carrier) + """ + if len(powers) < 3 or len(freqs) != len(powers): + return [] + + noise_floor, mad = adaptive_noise_floor(powers) + # Effective threshold: user threshold, but never below 3x MAD to + # avoid chasing noise ripples. + effective_threshold = max(threshold_db, 3.0 * mad) if mad > 0 else threshold_db + min_power = noise_floor + effective_threshold + + # Step 1: find raw local maxima + raw_peaks = [] + for i in range(1, len(powers) - 1): + if powers[i] > powers[i - 1] and powers[i] > powers[i + 1]: + if powers[i] >= min_power: + raw_peaks.append(i) + + # Also check endpoints if they are strong + if len(powers) >= 2: + if powers[0] > powers[1] and powers[0] >= min_power: + raw_peaks.insert(0, 0) + if powers[-1] > powers[-2] and powers[-1] >= min_power: + raw_peaks.append(len(powers) - 1) + + if not raw_peaks: + return [] + + # Step 2: measure width and build peak dicts + peaks = [] + for idx in raw_peaks: + bw = estimate_carrier_bw(freqs, powers, idx) + prominence = powers[idx] - noise_floor + peaks.append({ + "freq": freqs[idx], + "power": powers[idx], + "index": idx, + "width_mhz": bw, + "prominence_db": prominence, + }) + + # Step 3: merge overlapping peaks (keep the stronger one) + merged = _merge_peaks(peaks) + return merged + + +def _merge_peaks(peaks: list) -> list: + """ + Merge peaks whose -3dB extents overlap. + + When two peaks are closer together than the sum of their half-widths + they likely belong to the same carrier. Keep the stronger peak and + take the wider bandwidth. + """ + if len(peaks) <= 1: + return peaks + + # Sort by frequency + peaks = sorted(peaks, key=lambda p: p["freq"]) + merged = [peaks[0]] + + for peak in peaks[1:]: + prev = merged[-1] + # Half-widths + prev_upper = prev["freq"] + prev["width_mhz"] / 2 + peak_lower = peak["freq"] - peak["width_mhz"] / 2 + + if peak_lower <= prev_upper: + # Overlap: keep the stronger peak, widen the bandwidth + if peak["power"] > prev["power"]: + wider = max(prev["width_mhz"], peak["width_mhz"], + (peak["freq"] + peak["width_mhz"] / 2) - + (prev["freq"] - prev["width_mhz"] / 2)) + peak["width_mhz"] = wider + merged[-1] = peak + else: + wider = max(prev["width_mhz"], peak["width_mhz"], + (peak["freq"] + peak["width_mhz"] / 2) - + (prev["freq"] - prev["width_mhz"] / 2)) + prev["width_mhz"] = wider + else: + merged.append(peak) + + return merged + + +def estimate_carrier_bw(freqs: list, powers: list, + peak_idx: int) -> float: + """ + Estimate carrier bandwidth by walking from peak until power drops + 3 dB below the peak value (the -3dB bandwidth). + + Walks left and right from the peak index, interpolating between + adjacent frequency bins when the -3dB crossing falls between them. + + Returns estimated bandwidth in MHz. Minimum return is one frequency + step width (avoids zero-width artifacts on single-bin peaks). + """ + if peak_idx < 0 or peak_idx >= len(powers): + return 0.0 + + peak_power = powers[peak_idx] + cutoff = peak_power - 3.0 + + # Minimum step size for fallback + if len(freqs) >= 2: + step = abs(freqs[1] - freqs[0]) + else: + return 0.0 + + # Walk left + left_freq = freqs[peak_idx] + for i in range(peak_idx - 1, -1, -1): + if powers[i] <= cutoff: + # Interpolate between bin i and bin i+1 + if powers[i + 1] != powers[i]: + frac = (cutoff - powers[i]) / (powers[i + 1] - powers[i]) + else: + frac = 0.5 + left_freq = freqs[i] + frac * (freqs[i + 1] - freqs[i]) + break + left_freq = freqs[i] + + # Walk right + right_freq = freqs[peak_idx] + for i in range(peak_idx + 1, len(powers)): + if powers[i] <= cutoff: + if powers[i - 1] != powers[i]: + frac = (cutoff - powers[i]) / (powers[i - 1] - powers[i]) + else: + frac = 0.5 + right_freq = freqs[i] - frac * (freqs[i] - freqs[i - 1]) + break + right_freq = freqs[i] + + bw = right_freq - left_freq + return max(bw, step) + + +def classify_carrier(bw_mhz: float, power_db: float) -> dict: + """ + Classify a detected carrier based on bandwidth and power. + + Uses empirical ranges for DVB-S symbol rates: SR (Msps) is roughly + BW (MHz) / 1.35 for QPSK with roll-off 0.35. + + Returns dict with: + estimated_sr_range - (min_sps, max_sps) tuple + likely_modulation - list of plausible modulation names + signal_quality - 'strong', 'moderate', or 'weak' + """ + # Roll-off factor for DVB-S is typically 0.35, so BW ~ SR * 1.35. + # Allow some tolerance on both sides. + sr_center = bw_mhz / 1.35 # Msps + sr_min = int(max(256_000, (bw_mhz / 1.5) * 1_000_000)) + sr_max = int(min(30_000_000, (bw_mhz / 1.2) * 1_000_000)) + if sr_max < sr_min: + sr_max = sr_min + + # Guess modulation based on bandwidth + likely_mods = [] + if bw_mhz < 3.0: + # Narrow carrier: low-SR data channels, SCPC, DCII split + likely_mods = ["qpsk", "dcii-i", "dcii-q", "dss"] + elif bw_mhz < 10.0: + # Medium: typical SCPC, small MCPC + likely_mods = ["qpsk", "turbo-qpsk", "dcii-combo"] + elif bw_mhz < 20.0: + # Wide: MCPC transponders + likely_mods = ["qpsk", "turbo-qpsk", "turbo-8psk"] + else: + # Very wide: full transponder, high-SR + likely_mods = ["qpsk", "turbo-qpsk", "turbo-8psk", "dcii-combo"] + + # Signal quality heuristic (relative power, device-dependent) + if power_db > -10.0: + quality = "strong" + elif power_db > -25.0: + quality = "moderate" + else: + quality = "weak" + + return { + "estimated_sr_range": (sr_min, sr_max), + "likely_modulation": likely_mods, + "signal_quality": quality, + } diff --git a/tools/skywalker.py b/tools/skywalker.py index e7f35e4..6451905 100755 --- a/tools/skywalker.py +++ b/tools/skywalker.py @@ -1,1086 +1,1086 @@ -#!/usr/bin/env python3 -""" -Genpix SkyWalker-1 multi-mode RF tool. - -Modes: - spectrum - Sweep spectrum analyzer (950-2150 MHz IF range) - scan - Automated transponder scanner (sweep + blind scan) - monitor - Real-time signal strength at a single frequency - lband - L-band direct input analyzer (no LNB) - track - Carrier/beacon tracker with logging -""" - -import sys -import os -import argparse -import time -import signal -import csv -import json -import struct -import math -from datetime import datetime - -# Add tools directory to path for library import -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from skywalker_lib import ( - SkyWalker1, MODULATIONS, FEC_RATES, MOD_FEC_GROUP, - LNB_LO_LOW, LNB_LO_HIGH, LBAND_ALLOCATIONS, - CMD_BLIND_SCAN, - snr_raw_to_db, snr_raw_to_pct, agc_to_power_db, - detect_peaks, if_to_rf, signal_bar, format_config_bits, -) - - -# --- Terminal rendering --- - -# ANSI color codes for waterfall display -WATERFALL_COLORS = [ - "\033[38;5;17m", # dark blue (weakest) - "\033[38;5;19m", - "\033[38;5;21m", - "\033[38;5;27m", - "\033[38;5;33m", - "\033[38;5;39m", # cyan - "\033[38;5;46m", # green - "\033[38;5;82m", - "\033[38;5;118m", - "\033[38;5;154m", # yellow-green - "\033[38;5;190m", - "\033[38;5;226m", # yellow - "\033[38;5;214m", - "\033[38;5;208m", # orange - "\033[38;5;196m", # red (strongest) - "\033[38;5;160m", -] -ANSI_RESET = "\033[0m" - -# Unicode block characters for bar charts -BARS_H = " ▏▎▍▌▋▊▉█" - -# Sparkline characters -SPARKS = "▁▂▃▄▅▆▇█" - - -def power_color(power_db: float, floor: float = -40.0, ceil: float = 0.0) -> str: - """Map a power_db value to an ANSI color escape.""" - ratio = (power_db - floor) / (ceil - floor) - ratio = max(0.0, min(1.0, ratio)) - idx = int(ratio * (len(WATERFALL_COLORS) - 1)) - return WATERFALL_COLORS[idx] - - -def ascii_bar_h(value: float, max_val: float, width: int = 50) -> str: - """Render a horizontal bar using Unicode block characters.""" - if max_val == 0: - return "" - ratio = max(0.0, min(1.0, value / max_val)) - full_blocks = int(ratio * width) - remainder = (ratio * width) - full_blocks - partial_idx = int(remainder * (len(BARS_H) - 1)) - - bar = "█" * full_blocks - if full_blocks < width: - bar += BARS_H[partial_idx] - bar += " " * (width - full_blocks - 1) - return bar - - -def sparkline(values: list, width: int = 60) -> str: - """Render a sparkline from a list of values.""" - if not values: - return "" - # Take last 'width' values - vals = values[-width:] - mn = min(vals) - mx = max(vals) - rng = mx - mn if mx != mn else 1.0 - return "".join( - SPARKS[min(len(SPARKS) - 1, int((v - mn) / rng * (len(SPARKS) - 1)))] - for v in vals - ) - - -def clear_line(): - """Clear the current terminal line.""" - sys.stdout.write("\r\033[K") - - -# --- Mode: spectrum --- - -def cmd_spectrum(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Sweep 950-2150 MHz (or custom range), display power-vs-frequency.""" - start = args.start - stop = args.stop - step = args.step - dwell = args.dwell - sr_ksps = args.sr - lnb_lo = args.lnb_lo - num_sweeps = args.sweeps - - steps = int((stop - start) / step) + 1 - est_time = steps * (dwell + 2) / 1000.0 # dwell + USB overhead - - print(f"SkyWalker-1 Spectrum Analyzer") - print(f"{'=' * 60}") - print(f" IF Range: {start}-{stop} MHz (step {step} MHz)") - if lnb_lo > 0: - print(f" RF Range: {start + lnb_lo:.0f}-{stop + lnb_lo:.0f} MHz (LNB LO {lnb_lo} MHz)") - else: - print(f" Direct input (no LNB offset)") - print(f" Steps: {steps}") - print(f" Dwell: {dwell} ms") - print(f" Symbol rate: {sr_ksps} ksps") - print(f" Est. sweep: {est_time:.1f}s") - if num_sweeps > 1: - print(f" Sweeps: {num_sweeps}") - print() - - sw.ensure_booted() - - csv_writer = None - csv_file = None - if args.csv: - csv_file = open(args.csv, 'w', newline='') - csv_writer = csv.writer(csv_file) - csv_writer.writerow(["sweep", "if_mhz", "rf_mhz", "snr_raw", "snr_db", - "agc1", "agc2", "power_db", "locked"]) - - all_sweeps = [] - - for sweep_num in range(num_sweeps): - if num_sweeps > 1: - print(f"\n--- Sweep {sweep_num + 1}/{num_sweeps} ---") - - def progress(freq, step_num, total, result): - pct = (step_num + 1) / total * 100 - rf = if_to_rf(freq, lnb_lo) - clear_line() - sys.stdout.write(f" [{pct:5.1f}%] {freq:.0f} MHz IF" - f" ({rf:.0f} MHz RF)" - f" SNR={result['snr_db']:.1f} dB" - f" AGC={result['agc1']}") - sys.stdout.flush() - - t0 = time.time() - freqs, powers, results = sw.sweep_spectrum( - start, stop, step, dwell, sr_ksps, - callback=progress if not args.waterfall else None - ) - elapsed = time.time() - t0 - clear_line() - print(f" Sweep complete: {len(freqs)} points in {elapsed:.1f}s") - - all_sweeps.append((freqs, powers, results)) - - # Write CSV - if csv_writer: - for i, (f, p, r) in enumerate(zip(freqs, powers, results)): - rf = if_to_rf(f, lnb_lo) - csv_writer.writerow([ - sweep_num, f"{f:.1f}", f"{rf:.1f}", - r["snr_raw"], f"{r['snr_db']:.2f}", - r["agc1"], r["agc2"], - f"{p:.2f}", int(r["locked"]) - ]) - - # Terminal bar chart display - if not args.waterfall: - print() - p_min = min(powers) if powers else -40 - p_max = max(powers) if powers else 0 - p_range = p_max - p_min if p_max != p_min else 1 - - for i, (f, p) in enumerate(zip(freqs, powers)): - rf = if_to_rf(f, lnb_lo) - bar = ascii_bar_h(p - p_min, p_range, width=40) - label = f"{rf:7.0f}" if lnb_lo > 0 else f"{f:7.0f}" - locked = results[i]["locked"] - lock_mark = " *" if locked else "" - print(f" {label} |{bar}| {p:6.1f} dB{lock_mark}") - - # Waterfall display - if args.waterfall: - p_min = min(powers) if powers else -40 - p_max = max(powers) if powers else 0 - line = "" - for p in powers: - color = power_color(p, p_min, p_max) - line += f"{color}█{ANSI_RESET}" - ts = datetime.now().strftime("%H:%M:%S") - print(f" {ts} {line}") - - # Peak detection - if not args.waterfall and freqs: - peaks = detect_peaks(freqs, powers, threshold_db=args.threshold if hasattr(args, 'threshold') else 3.0) - if peaks: - print(f"\n Peaks ({len(peaks)} found):") - for freq, pwr, idx in peaks: - rf = if_to_rf(freq, lnb_lo) - locked = results[idx]["locked"] - lock_str = " LOCKED" if locked else "" - label = f"{rf:.0f} MHz RF" if lnb_lo > 0 else f"{freq:.0f} MHz" - print(f" {label} {pwr:.1f} dB{lock_str}") - - if csv_file: - csv_file.close() - print(f"\n CSV saved: {args.csv}") - - # Matplotlib plot - if args.plot: - _plot_spectrum(freqs, powers, lnb_lo, all_sweeps) - - -def _plot_spectrum(freqs, powers, lnb_lo, all_sweeps=None): - """Show matplotlib spectrum plot.""" - try: - import matplotlib.pyplot as plt - except ImportError: - print(" matplotlib required for --plot: pip install matplotlib") - return - - rf_freqs = [if_to_rf(f, lnb_lo) for f in freqs] - x_label = "Frequency (MHz RF)" if lnb_lo > 0 else "Frequency (MHz IF)" - - fig, ax = plt.subplots(figsize=(14, 6)) - ax.plot(rf_freqs, powers, '-', linewidth=0.8, color='#2196F3') - ax.fill_between(rf_freqs, min(powers), powers, alpha=0.15, color='#2196F3') - ax.set_xlabel(x_label) - ax.set_ylabel("Power (dB, relative)") - ax.set_title("SkyWalker-1 Spectrum") - ax.grid(True, alpha=0.3) - - # Mark peaks - peaks = detect_peaks(freqs, powers) - if peaks: - peak_x = [if_to_rf(f, lnb_lo) for f, _, _ in peaks] - peak_y = [p for _, p, _ in peaks] - ax.scatter(peak_x, peak_y, color='#F44336', zorder=5, s=30) - for px, py in zip(peak_x, peak_y): - ax.annotate(f"{px:.0f}", (px, py), textcoords="offset points", - xytext=(0, 8), ha='center', fontsize=7, color='#F44336') - - plt.tight_layout() - plt.show() - - -# --- Mode: scan --- - -def cmd_scan(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Automated transponder scanner: coarse sweep, peak detect, blind scan.""" - start = args.start - stop = args.stop - lnb_lo = args.lnb_lo - threshold = args.threshold - sr_min = args.sr_min * 1000 - sr_max = args.sr_max * 1000 - sr_step = args.sr_step * 1000 - - print(f"SkyWalker-1 Transponder Scanner") - print(f"{'=' * 60}") - print(f" IF Range: {start}-{stop} MHz") - if lnb_lo > 0: - print(f" RF Range: {start + lnb_lo:.0f}-{stop + lnb_lo:.0f} MHz") - print(f" Threshold: {threshold} dB above noise floor") - print(f" SR Range: {args.sr_min}-{args.sr_max} ksps (step {args.sr_step})") - print() - - sw.ensure_booted() - - # Configure LNB - if args.pol: - sw.set_lnb_voltage(args.pol.upper() in ("H", "L")) - if args.band: - sw.set_22khz_tone(args.band == "high") - - # Phase 1: Coarse spectrum sweep - print("[Phase 1] Coarse spectrum sweep...") - coarse_step = 10 # MHz - freqs, powers, results = sw.sweep_spectrum( - start, stop, coarse_step, dwell_ms=15, sr_ksps=20000 - ) - print(f" {len(freqs)} points measured") - - # Phase 2: Find peaks - print(f"\n[Phase 2] Peak detection (threshold {threshold} dB)...") - peaks = detect_peaks(freqs, powers, threshold_db=threshold) - if not peaks: - print(" No peaks found above threshold.") - print(" Try lowering --threshold or checking dish alignment.") - return - - print(f" {len(peaks)} candidate peaks:") - for freq, pwr, idx in peaks: - rf = if_to_rf(freq, lnb_lo) - print(f" {rf:.0f} MHz {pwr:.1f} dB") - - # Phase 2.5: Fine sweep around each peak - print(f"\n[Phase 2.5] Fine sweep around peaks...") - refined_peaks = [] - for freq, pwr, idx in peaks: - fine_start = max(start, freq - 15) - fine_stop = min(stop, freq + 15) - fine_freqs, fine_powers, fine_results = sw.sweep_spectrum( - fine_start, fine_stop, step_mhz=2.0, dwell_ms=20, sr_ksps=20000 - ) - if fine_powers: - best_idx = fine_powers.index(max(fine_powers)) - refined_peaks.append(( - fine_freqs[best_idx], - fine_powers[best_idx], - fine_results[best_idx] - )) - rf = if_to_rf(fine_freqs[best_idx], lnb_lo) - print(f" {rf:.0f} MHz {fine_powers[best_idx]:.1f} dB (refined)") - - # Phase 3: Blind scan at each refined peak - print(f"\n[Phase 3] Blind scan at {len(refined_peaks)} peaks...") - found = [] - - for freq, pwr, result in refined_peaks: - freq_khz_int = int(freq * 1000) - rf = if_to_rf(freq, lnb_lo) - print(f" Scanning {rf:.0f} MHz (IF {freq:.0f})...", end="", flush=True) - - # Build blind scan EP0 payload - payload = struct.pack('= 8 and resp[0] != 0: - found_freq = struct.unpack_from(' None: - """Real-time signal monitor / dish alignment aid.""" - freq_mhz = args.freq - sr_ksps = args.sr - lnb_lo = args.lnb_lo - rate = args.rate - poll_interval = 1.0 / rate - - # Calculate IF frequency - if lnb_lo > 0: - if_mhz = freq_mhz - lnb_lo - else: - if_mhz = freq_mhz - - if_khz = int(if_mhz * 1000) - sr_sps = sr_ksps * 1000 - - print(f"SkyWalker-1 Signal Monitor") - print(f"{'=' * 60}") - if lnb_lo > 0: - print(f" Frequency: {freq_mhz} MHz (IF {if_mhz:.0f} MHz, LNB LO {lnb_lo} MHz)") - else: - print(f" Frequency: {if_mhz} MHz (direct input)") - print(f" Symbol rate: {sr_ksps} ksps") - print(f" Poll rate: {rate} Hz") - if args.audio: - print(f" Audio: ON") - print(f"\n Press Ctrl-C to stop\n") - - sw.ensure_booted() - - # Configure LNB - if args.pol: - sw.set_lnb_voltage(args.pol.upper() in ("H", "L")) - if args.band: - sw.set_22khz_tone(args.band == "high") - - # Initial tune - sw.tune(sr_sps, if_khz, 0, 5) # QPSK, auto-FEC - time.sleep(0.5) - - history = [] - peak_snr = 0.0 - peak_power = -99.0 - running = True - - def stop(signum, frame): - nonlocal running - running = False - - signal.signal(signal.SIGINT, stop) - signal.signal(signal.SIGTERM, stop) - - while running: - t0 = time.time() - - sig = sw.signal_monitor() - snr_db = sig["snr_db"] - power = sig["power_db"] - locked = sig["locked"] - agc1 = sig["agc1"] - - history.append(snr_db) - if args.peak_hold: - peak_snr = max(peak_snr, snr_db) - peak_power = max(peak_power, power) - - # Build display - lock_str = "LOCK" if locked else "----" - bar = signal_bar(sig["snr_pct"], width=35) - - clear_line() - line = f" [{lock_str}] SNR {snr_db:5.1f} dB AGC {agc1:5d} {bar}" - if args.peak_hold: - line += f" peak {peak_snr:.1f} dB" - sys.stdout.write(line) - - # Sparkline history - if len(history) > 1: - spark = sparkline(history, width=min(40, len(history))) - sys.stdout.write(f"\n History: {spark}") - sys.stdout.write("\033[F") # cursor up - - sys.stdout.flush() - - # Audio feedback - if args.audio: - _beep_proportional(snr_db) - - # Pace the polling - elapsed = time.time() - t0 - sleep_time = poll_interval - elapsed - if sleep_time > 0: - time.sleep(sleep_time) - - print(f"\n\n Stopped. {len(history)} samples collected.") - if args.peak_hold: - print(f" Peak SNR: {peak_snr:.1f} dB") - - if args.plot: - _plot_monitor_history(history, rate) - - -def _beep_proportional(snr_db: float): - """Emit a pitch-proportional beep for dish alignment.""" - # Map SNR 0-15 dB to frequency 200-2000 Hz - freq = int(200 + min(15, max(0, snr_db)) / 15 * 1800) - duration_ms = 50 - try: - sys.stdout.write(f"\033[10;{freq}]\033[11;{duration_ms}]\a") - sys.stdout.flush() - except Exception: - pass # Terminal doesn't support bell frequency control - - -def _plot_monitor_history(history, rate): - """Plot signal monitor history with matplotlib.""" - try: - import matplotlib.pyplot as plt - except ImportError: - print(" matplotlib required for --plot: pip install matplotlib") - return - - t = [i / rate for i in range(len(history))] - fig, ax = plt.subplots(figsize=(12, 5)) - ax.plot(t, history, '-', linewidth=0.8, color='#4CAF50') - ax.set_xlabel("Time (s)") - ax.set_ylabel("SNR (dB)") - ax.set_title("Signal Monitor History") - ax.grid(True, alpha=0.3) - plt.tight_layout() - plt.show() - - -# --- Mode: lband --- - -def cmd_lband(sw: SkyWalker1, args: argparse.Namespace) -> None: - """L-band direct input spectrum analyzer with allocation annotations.""" - start = args.start - stop = args.stop - step = args.step - dwell = args.dwell - - # Narrow to 23cm if requested - if args.ham_23cm: - start = 1240 - stop = 1300 - step = 0.5 - - steps = int((stop - start) / step) + 1 - est_time = steps * (dwell + 2) / 1000.0 - - print(f"SkyWalker-1 L-Band Analyzer") - print(f"{'=' * 60}") - print(f" Range: {start}-{stop} MHz (direct input, no LNB)") - print(f" Steps: {steps} (step {step} MHz, dwell {dwell} ms)") - print(f" Est. sweep: {est_time:.1f}s") - print() - print(" NOTE: Can detect carrier PRESENCE at any frequency even if") - print(" modulation is incompatible with the BCM4500 demodulator.") - print() - - sw.ensure_booted() - - # Disable LNB power for direct input - sw.start_intersil(on=False) - time.sleep(0.1) - - # Show band info - if args.band_info: - print(f" L-Band Allocations in range:") - for lo, hi, name in LBAND_ALLOCATIONS: - if lo < stop and hi > start: - overlap_lo = max(lo, start) - overlap_hi = min(hi, stop) - print(f" {overlap_lo:7.0f}-{overlap_hi:<7.0f} MHz {name}") - print() - - # Sweep - def progress(freq, step_num, total, result): - pct = (step_num + 1) / total * 100 - clear_line() - sys.stdout.write(f" [{pct:5.1f}%] {freq:.1f} MHz" - f" SNR={result['snr_db']:.1f} dB" - f" AGC={result['agc1']}") - sys.stdout.flush() - - t0 = time.time() - freqs, powers, results = sw.sweep_spectrum( - start, stop, step, dwell, sr_ksps=20000, - callback=progress if not args.waterfall else None - ) - elapsed = time.time() - t0 - clear_line() - print(f" Sweep complete: {len(freqs)} points in {elapsed:.1f}s\n") - - # CSV output - csv_writer = None - csv_file = None - if args.csv: - csv_file = open(args.csv, 'w', newline='') - csv_writer = csv.writer(csv_file) - csv_writer.writerow(["freq_mhz", "snr_raw", "snr_db", "agc1", "agc2", - "power_db", "locked", "allocation"]) - for i, (f, p, r) in enumerate(zip(freqs, powers, results)): - alloc = _freq_allocation(f) - csv_writer.writerow([ - f"{f:.1f}", r["snr_raw"], f"{r['snr_db']:.2f}", - r["agc1"], r["agc2"], f"{p:.2f}", int(r["locked"]), - alloc - ]) - csv_file.close() - print(f" CSV saved: {args.csv}") - - # Terminal display with allocation annotations - if not args.waterfall: - p_min = min(powers) if powers else -40 - p_max = max(powers) if powers else 0 - p_range = p_max - p_min if p_max != p_min else 1 - last_alloc = "" - - for i, (f, p) in enumerate(zip(freqs, powers)): - bar = ascii_bar_h(p - p_min, p_range, width=35) - locked = results[i]["locked"] - lock_mark = " *" if locked else "" - alloc = _freq_allocation(f) - - # Print allocation header when it changes - if alloc != last_alloc and alloc: - print(f" {'─' * 55}") - print(f" ┌ {alloc}") - last_alloc = alloc - elif alloc != last_alloc: - last_alloc = alloc - - print(f" {f:7.1f} |{bar}| {p:6.1f} dB{lock_mark}") - - # Waterfall - if args.waterfall: - p_min = min(powers) if powers else -40 - p_max = max(powers) if powers else 0 - line = "" - for p in powers: - color = power_color(p, p_min, p_max) - line += f"{color}█{ANSI_RESET}" - ts = datetime.now().strftime("%H:%M:%S") - print(f" {ts} {line}") - - # Peaks - peaks = detect_peaks(freqs, powers, threshold_db=3.0) - if peaks: - print(f"\n Peaks ({len(peaks)} found):") - for freq, pwr, idx in peaks: - alloc = _freq_allocation(freq) - alloc_str = f" [{alloc}]" if alloc else "" - locked = results[idx]["locked"] - lock_str = " LOCKED" if locked else "" - print(f" {freq:.1f} MHz {pwr:.1f} dB{lock_str}{alloc_str}") - - if args.plot: - _plot_lband(freqs, powers) - - -def _freq_allocation(freq_mhz: float) -> str: - """Return the allocation name for a given frequency, or empty string.""" - for lo, hi, name in LBAND_ALLOCATIONS: - if lo <= freq_mhz <= hi: - return name - return "" - - -def _plot_lband(freqs, powers): - """L-band spectrum plot with allocation shading.""" - try: - import matplotlib.pyplot as plt - except ImportError: - print(" matplotlib required for --plot: pip install matplotlib") - return - - fig, ax = plt.subplots(figsize=(14, 6)) - ax.plot(freqs, powers, '-', linewidth=0.8, color='#FF9800') - ax.fill_between(freqs, min(powers), powers, alpha=0.1, color='#FF9800') - - # Shade allocations - colors = ['#E3F2FD', '#FFF3E0', '#E8F5E9', '#F3E5F5', - '#FBE9E7', '#E0F7FA', '#ECEFF1'] - for i, (lo, hi, name) in enumerate(LBAND_ALLOCATIONS): - if lo < max(freqs) and hi > min(freqs): - c = colors[i % len(colors)] - ax.axvspan(lo, hi, alpha=0.3, color=c, label=name) - mid = (lo + hi) / 2 - ax.text(mid, max(powers) * 0.95, name, - ha='center', fontsize=6, rotation=45) - - ax.set_xlabel("Frequency (MHz)") - ax.set_ylabel("Power (dB, relative)") - ax.set_title("SkyWalker-1 L-Band Spectrum") - ax.grid(True, alpha=0.3) - plt.tight_layout() - plt.show() - - -# --- Mode: track --- - -def cmd_track(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Lock to a frequency and log power/lock/status over time.""" - freq_mhz = args.freq - sr_ksps = args.sr - lnb_lo = args.lnb_lo - rate = args.rate - duration = args.duration - poll_interval = 1.0 / rate - - if lnb_lo > 0: - if_mhz = freq_mhz - lnb_lo - else: - if_mhz = freq_mhz - - if_khz = int(if_mhz * 1000) - sr_sps = sr_ksps * 1000 - - print(f"SkyWalker-1 Carrier Tracker") - print(f"{'=' * 60}") - if lnb_lo > 0: - print(f" Frequency: {freq_mhz} MHz (IF {if_mhz:.0f} MHz)") - else: - print(f" Frequency: {if_mhz} MHz (direct)") - print(f" Symbol rate: {sr_ksps} ksps") - print(f" Poll rate: {rate} Hz") - if duration: - print(f" Duration: {duration}s") - if args.log: - print(f" Log file: {args.log}") - print(f"\n Press Ctrl-C to stop\n") - - sw.ensure_booted() - - if args.pol: - sw.set_lnb_voltage(args.pol.upper() in ("H", "L")) - if args.band: - sw.set_22khz_tone(args.band == "high") - - # Tune once - sw.tune(sr_sps, if_khz, 0, 5) - time.sleep(0.5) - - # Setup logging - log_file = None - log_writer = None - if args.log: - log_file = open(args.log, 'w', newline='') - log_writer = csv.writer(log_file) - log_writer.writerow(["timestamp", "elapsed_s", "snr_db", "agc1", "agc2", - "power_db", "locked", "lock_reg", "status_reg"]) - - jsonl_file = None - if args.json_lines: - jsonl_file = open(args.json_lines, 'w') - - history_snr = [] - history_power = [] - was_locked = None - sample_count = 0 - start_time = time.time() - running = True - - def stop_handler(signum, frame): - nonlocal running - running = False - - signal.signal(signal.SIGINT, stop_handler) - signal.signal(signal.SIGTERM, stop_handler) - - # Drift tracking window - drift_window = 5 # MHz each side - drift_history = [] - - while running: - if duration and (time.time() - start_time) >= duration: - break - - t0 = time.time() - elapsed = t0 - start_time - - sig = sw.signal_monitor() - snr_db = sig["snr_db"] - power = sig["power_db"] - locked = sig["locked"] - - history_snr.append(snr_db) - history_power.append(power) - sample_count += 1 - - # Lock state transitions - if was_locked is not None and locked != was_locked: - ts = datetime.now().strftime("%H:%M:%S.%f")[:-3] - if locked: - print(f"\n [{ts}] >>> LOCK ACQUIRED SNR {snr_db:.1f} dB") - else: - print(f"\n [{ts}] <<< LOCK LOST") - was_locked = locked - - # Drift tracking - if args.drift_track and sample_count % (rate * 5) == 0: - # Every 5 seconds, do a narrow sweep to find peak - drift_start = max(950, if_mhz - drift_window) - drift_stop = min(2150, if_mhz + drift_window) - drift_freqs, drift_powers, _ = sw.sweep_spectrum( - drift_start, drift_stop, step_mhz=1.0, dwell_ms=5, sr_ksps=sr_ksps - ) - if drift_powers: - peak_idx = drift_powers.index(max(drift_powers)) - peak_freq = drift_freqs[peak_idx] - drift_hz = (peak_freq - if_mhz) * 1000 - drift_history.append((elapsed, drift_hz)) - if abs(drift_hz) > 100: - print(f"\n DRIFT: {drift_hz:+.0f} kHz from center") - # Re-tune to original - sw.tune(sr_sps, if_khz, 0, 5) - time.sleep(0.1) - - # Display - lock_str = "LOCK" if locked else "----" - bar = signal_bar(sig["snr_pct"], width=30) - clear_line() - sys.stdout.write(f" [{lock_str}] {elapsed:7.1f}s SNR {snr_db:5.1f} dB {bar}" - f" #{sample_count}") - sys.stdout.flush() - - # Log - ts_iso = datetime.now().isoformat() - if log_writer: - log_writer.writerow([ - ts_iso, f"{elapsed:.3f}", f"{snr_db:.2f}", - sig["agc1"], sig["agc2"], f"{power:.2f}", - int(locked), sig["lock"], sig["status"] - ]) - - if jsonl_file: - record = { - "ts": ts_iso, - "elapsed": round(elapsed, 3), - "snr_db": round(snr_db, 2), - "agc1": sig["agc1"], - "agc2": sig["agc2"], - "power_db": round(power, 2), - "locked": locked, - } - jsonl_file.write(json.dumps(record) + "\n") - - # Pace - sleep_time = poll_interval - (time.time() - t0) - if sleep_time > 0: - time.sleep(sleep_time) - - total_time = time.time() - start_time - print(f"\n\n Stopped. {sample_count} samples in {total_time:.1f}s") - - if log_file: - log_file.close() - print(f" Log saved: {args.log}") - if jsonl_file: - jsonl_file.close() - print(f" JSON-lines saved: {args.json_lines}") - - if args.plot: - _plot_track(history_snr, history_power, rate, drift_history) - - -def _plot_track(history_snr, history_power, rate, drift_history=None): - """Plot tracking history.""" - try: - import matplotlib.pyplot as plt - except ImportError: - print(" matplotlib required for --plot: pip install matplotlib") - return - - t = [i / rate for i in range(len(history_snr))] - - fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True) - - axes[0].plot(t, history_snr, '-', linewidth=0.6, color='#4CAF50') - axes[0].set_ylabel("SNR (dB)") - axes[0].set_title("Carrier Tracking") - axes[0].grid(True, alpha=0.3) - - axes[1].plot(t, history_power, '-', linewidth=0.6, color='#2196F3') - axes[1].set_ylabel("Power (dB)") - axes[1].set_xlabel("Time (s)") - axes[1].grid(True, alpha=0.3) - - if drift_history: - ax_drift = axes[0].twinx() - dt = [d[0] for d in drift_history] - dv = [d[1] for d in drift_history] - ax_drift.plot(dt, dv, 'o-', color='#F44336', markersize=3, linewidth=0.8) - ax_drift.set_ylabel("Drift (kHz)", color='#F44336') - - plt.tight_layout() - plt.show() - - -# --- CLI --- - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - prog="skywalker.py", - description="Genpix SkyWalker-1 multi-mode RF tool", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -examples: - %(prog)s spectrum --start 950 --stop 2150 --step 5 - %(prog)s spectrum --lnb-lo 9750 --start 950 --stop 2150 --plot - %(prog)s scan --lnb-lo 9750 --pol H --band high - %(prog)s monitor 12520 27500 --lnb-lo 9750 --pol H --rate 10 - %(prog)s lband --band-info - %(prog)s lband --23cm --plot - %(prog)s track 12520 27500 --lnb-lo 9750 --log signal.csv --duration 60 - -RF coverage (with different LNB configurations): - No LNB (direct): 950-2150 MHz (L-band: ham 23cm, GPS, GOES, HRPT) - Ku LNB (9750 LO): 10700-11900 MHz (satellite TV low band) - Ku LNB (10600 LO): 11550-12750 MHz (satellite TV high band) - Custom (9000 LO): 9950-11150 MHz (QO-100 DATV @ ~1491 MHz IF) -""") - parser.add_argument('-v', '--verbose', action='store_true', - help="Show raw USB traffic") - - sub = parser.add_subparsers(dest='command') - - # spectrum - p_spec = sub.add_parser('spectrum', help="Sweep spectrum analyzer", - formatter_class=argparse.RawDescriptionHelpFormatter) - p_spec.add_argument('--start', type=float, default=950, - help="Start IF frequency in MHz (default: 950)") - p_spec.add_argument('--stop', type=float, default=2150, - help="Stop IF frequency in MHz (default: 2150)") - p_spec.add_argument('--step', type=float, default=5, - help="Step size in MHz (default: 5)") - p_spec.add_argument('--dwell', type=int, default=10, - help="Dwell time per step in ms (default: 10)") - p_spec.add_argument('--lnb-lo', type=float, default=0, - help="LNB LO frequency in MHz (0=direct input)") - p_spec.add_argument('--sr', type=int, default=20000, - help="Symbol rate for measurement in ksps (default: 20000)") - p_spec.add_argument('--waterfall', action='store_true', - help="Waterfall display (time x frequency x power)") - p_spec.add_argument('--sweeps', type=int, default=1, - help="Number of sweeps (default: 1)") - p_spec.add_argument('--threshold', type=float, default=3.0, - help="Peak detection threshold in dB (default: 3)") - p_spec.add_argument('--plot', action='store_true', - help="Show matplotlib plot") - p_spec.add_argument('--csv', metavar='FILE', - help="Save sweep data to CSV") - - # scan - p_scan = sub.add_parser('scan', help="Automated transponder scanner", - formatter_class=argparse.RawDescriptionHelpFormatter) - p_scan.add_argument('--start', type=float, default=950, - help="Start IF frequency in MHz (default: 950)") - p_scan.add_argument('--stop', type=float, default=2150, - help="Stop IF frequency in MHz (default: 2150)") - p_scan.add_argument('--threshold', type=float, default=3, - help="Peak detection threshold in dB (default: 3)") - p_scan.add_argument('--sr-min', type=int, default=1000, - help="Minimum symbol rate in ksps (default: 1000)") - p_scan.add_argument('--sr-max', type=int, default=30000, - help="Maximum symbol rate in ksps (default: 30000)") - p_scan.add_argument('--sr-step', type=int, default=500, - help="Symbol rate step in ksps (default: 500)") - p_scan.add_argument('--lnb-lo', type=float, default=9750, - help="LNB LO frequency in MHz (default: 9750)") - p_scan.add_argument('--pol', choices=['H', 'V', 'L', 'R'], - help="Polarization (sets LNB voltage)") - p_scan.add_argument('--band', choices=['low', 'high'], - help="LNB band (sets 22 kHz tone)") - p_scan.add_argument('--json', action='store_true', - help="Output results as JSON") - p_scan.add_argument('--csv', metavar='FILE', - help="Save results to CSV") - - # monitor - p_mon = sub.add_parser('monitor', help="Real-time signal monitor / dish alignment", - formatter_class=argparse.RawDescriptionHelpFormatter) - p_mon.add_argument('freq', type=float, - help="Frequency in MHz (RF if --lnb-lo set, IF otherwise)") - p_mon.add_argument('sr', type=int, - help="Symbol rate in ksps") - p_mon.add_argument('--pol', choices=['H', 'V', 'L', 'R'], - help="Polarization (sets LNB voltage)") - p_mon.add_argument('--band', choices=['low', 'high'], - help="LNB band (sets 22 kHz tone)") - p_mon.add_argument('--lnb-lo', type=float, default=0, - help="LNB LO frequency in MHz (0=direct input)") - p_mon.add_argument('--rate', type=float, default=10, - help="Poll rate in Hz (default: 10, max ~50)") - p_mon.add_argument('--audio', action='store_true', - help="Pitch-proportional beep for hands-free alignment") - p_mon.add_argument('--peak-hold', action='store_true', - help="Track and display maximum signal seen") - p_mon.add_argument('--history', type=int, default=60, - help="Sparkline history length in samples (default: 60)") - p_mon.add_argument('--plot', action='store_true', - help="Show matplotlib plot after stopping") - - # lband - p_lband = sub.add_parser('lband', help="L-band direct input analyzer", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -L-band mode uses direct input (no LNB) to monitor 950-2150 MHz. -Can detect carrier PRESENCE at any frequency even if modulation -is incompatible with the BCM4500 demodulator. - -Known allocations in range: - 1240-1300 MHz Amateur 23cm - 1525-1559 MHz Inmarsat downlink - 1559-1610 MHz GNSS (GPS L1, Galileo E1) - 1610-1626 MHz Iridium downlink - 1670-1710 MHz MetSat (GOES LRIT, NOAA HRPT) - 1710-1785 MHz LTE/AWS uplink - 1920-2025 MHz UMTS uplink -""") - p_lband.add_argument('--start', type=float, default=950, - help="Start frequency in MHz (default: 950)") - p_lband.add_argument('--stop', type=float, default=2150, - help="Stop frequency in MHz (default: 2150)") - p_lband.add_argument('--step', type=float, default=2, - help="Step size in MHz (default: 2)") - p_lband.add_argument('--dwell', type=int, default=20, - help="Dwell time per step in ms (default: 20)") - p_lband.add_argument('--23cm', dest='ham_23cm', action='store_true', - help="Narrow to 1240-1300 MHz with 500 kHz steps") - p_lband.add_argument('--band-info', action='store_true', - help="Print L-band allocation table") - p_lband.add_argument('--waterfall', action='store_true', - help="Waterfall display") - p_lband.add_argument('--plot', action='store_true', - help="Show matplotlib plot") - p_lband.add_argument('--csv', metavar='FILE', - help="Save sweep data to CSV") - - # track - p_track = sub.add_parser('track', help="Carrier/beacon tracker with logging", - formatter_class=argparse.RawDescriptionHelpFormatter) - p_track.add_argument('freq', type=float, - help="Frequency in MHz (RF if --lnb-lo set, IF otherwise)") - p_track.add_argument('sr', type=int, - help="Symbol rate in ksps") - p_track.add_argument('--pol', choices=['H', 'V', 'L', 'R'], - help="Polarization (sets LNB voltage)") - p_track.add_argument('--band', choices=['low', 'high'], - help="LNB band (sets 22 kHz tone)") - p_track.add_argument('--lnb-lo', type=float, default=0, - help="LNB LO frequency in MHz (0=direct input)") - p_track.add_argument('--rate', type=float, default=1, - help="Poll rate in Hz (default: 1)") - p_track.add_argument('--duration', type=float, default=None, - help="Tracking duration in seconds (default: until Ctrl-C)") - p_track.add_argument('--log', metavar='FILE', - help="Log CSV: timestamp, snr, agc, power, lock, status") - p_track.add_argument('--drift-track', action='store_true', - help="Periodically sweep narrow window to measure frequency drift") - p_track.add_argument('--plot', action='store_true', - help="Show matplotlib plot after stopping") - p_track.add_argument('--json-lines', metavar='FILE', - help="Log as JSON-lines (one JSON object per line)") - - return parser - - -def main(): - parser = build_parser() - args = parser.parse_args() - - if not args.command: - parser.print_help() - sys.exit(0) - - dispatch = { - 'spectrum': cmd_spectrum, - 'scan': cmd_scan, - 'monitor': cmd_monitor, - 'lband': cmd_lband, - 'track': cmd_track, - } - - handler = dispatch.get(args.command) - if handler is None: - parser.print_help() - sys.exit(1) - - with SkyWalker1(verbose=args.verbose) as sw: - handler(sw, args) - - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 +""" +Genpix SkyWalker-1 multi-mode RF tool. + +Modes: + spectrum - Sweep spectrum analyzer (950-2150 MHz IF range) + scan - Automated transponder scanner (sweep + blind scan) + monitor - Real-time signal strength at a single frequency + lband - L-band direct input analyzer (no LNB) + track - Carrier/beacon tracker with logging +""" + +import sys +import os +import argparse +import time +import signal +import csv +import json +import struct +import math +from datetime import datetime + +# Add tools directory to path for library import +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from skywalker_lib import ( + SkyWalker1, MODULATIONS, FEC_RATES, MOD_FEC_GROUP, + LNB_LO_LOW, LNB_LO_HIGH, LBAND_ALLOCATIONS, + CMD_BLIND_SCAN, + snr_raw_to_db, snr_raw_to_pct, agc_to_power_db, + detect_peaks, if_to_rf, signal_bar, format_config_bits, +) + + +# --- Terminal rendering --- + +# ANSI color codes for waterfall display +WATERFALL_COLORS = [ + "\033[38;5;17m", # dark blue (weakest) + "\033[38;5;19m", + "\033[38;5;21m", + "\033[38;5;27m", + "\033[38;5;33m", + "\033[38;5;39m", # cyan + "\033[38;5;46m", # green + "\033[38;5;82m", + "\033[38;5;118m", + "\033[38;5;154m", # yellow-green + "\033[38;5;190m", + "\033[38;5;226m", # yellow + "\033[38;5;214m", + "\033[38;5;208m", # orange + "\033[38;5;196m", # red (strongest) + "\033[38;5;160m", +] +ANSI_RESET = "\033[0m" + +# Unicode block characters for bar charts +BARS_H = " ▏▎▍▌▋▊▉█" + +# Sparkline characters +SPARKS = "▁▂▃▄▅▆▇█" + + +def power_color(power_db: float, floor: float = -40.0, ceil: float = 0.0) -> str: + """Map a power_db value to an ANSI color escape.""" + ratio = (power_db - floor) / (ceil - floor) + ratio = max(0.0, min(1.0, ratio)) + idx = int(ratio * (len(WATERFALL_COLORS) - 1)) + return WATERFALL_COLORS[idx] + + +def ascii_bar_h(value: float, max_val: float, width: int = 50) -> str: + """Render a horizontal bar using Unicode block characters.""" + if max_val == 0: + return "" + ratio = max(0.0, min(1.0, value / max_val)) + full_blocks = int(ratio * width) + remainder = (ratio * width) - full_blocks + partial_idx = int(remainder * (len(BARS_H) - 1)) + + bar = "█" * full_blocks + if full_blocks < width: + bar += BARS_H[partial_idx] + bar += " " * (width - full_blocks - 1) + return bar + + +def sparkline(values: list, width: int = 60) -> str: + """Render a sparkline from a list of values.""" + if not values: + return "" + # Take last 'width' values + vals = values[-width:] + mn = min(vals) + mx = max(vals) + rng = mx - mn if mx != mn else 1.0 + return "".join( + SPARKS[min(len(SPARKS) - 1, int((v - mn) / rng * (len(SPARKS) - 1)))] + for v in vals + ) + + +def clear_line(): + """Clear the current terminal line.""" + sys.stdout.write("\r\033[K") + + +# --- Mode: spectrum --- + +def cmd_spectrum(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Sweep 950-2150 MHz (or custom range), display power-vs-frequency.""" + start = args.start + stop = args.stop + step = args.step + dwell = args.dwell + sr_ksps = args.sr + lnb_lo = args.lnb_lo + num_sweeps = args.sweeps + + steps = int((stop - start) / step) + 1 + est_time = steps * (dwell + 2) / 1000.0 # dwell + USB overhead + + print(f"SkyWalker-1 Spectrum Analyzer") + print(f"{'=' * 60}") + print(f" IF Range: {start}-{stop} MHz (step {step} MHz)") + if lnb_lo > 0: + print(f" RF Range: {start + lnb_lo:.0f}-{stop + lnb_lo:.0f} MHz (LNB LO {lnb_lo} MHz)") + else: + print(f" Direct input (no LNB offset)") + print(f" Steps: {steps}") + print(f" Dwell: {dwell} ms") + print(f" Symbol rate: {sr_ksps} ksps") + print(f" Est. sweep: {est_time:.1f}s") + if num_sweeps > 1: + print(f" Sweeps: {num_sweeps}") + print() + + sw.ensure_booted() + + csv_writer = None + csv_file = None + if args.csv: + csv_file = open(args.csv, 'w', newline='') + csv_writer = csv.writer(csv_file) + csv_writer.writerow(["sweep", "if_mhz", "rf_mhz", "snr_raw", "snr_db", + "agc1", "agc2", "power_db", "locked"]) + + all_sweeps = [] + + for sweep_num in range(num_sweeps): + if num_sweeps > 1: + print(f"\n--- Sweep {sweep_num + 1}/{num_sweeps} ---") + + def progress(freq, step_num, total, result): + pct = (step_num + 1) / total * 100 + rf = if_to_rf(freq, lnb_lo) + clear_line() + sys.stdout.write(f" [{pct:5.1f}%] {freq:.0f} MHz IF" + f" ({rf:.0f} MHz RF)" + f" SNR={result['snr_db']:.1f} dB" + f" AGC={result['agc1']}") + sys.stdout.flush() + + t0 = time.time() + freqs, powers, results = sw.sweep_spectrum( + start, stop, step, dwell, sr_ksps, + callback=progress if not args.waterfall else None + ) + elapsed = time.time() - t0 + clear_line() + print(f" Sweep complete: {len(freqs)} points in {elapsed:.1f}s") + + all_sweeps.append((freqs, powers, results)) + + # Write CSV + if csv_writer: + for i, (f, p, r) in enumerate(zip(freqs, powers, results)): + rf = if_to_rf(f, lnb_lo) + csv_writer.writerow([ + sweep_num, f"{f:.1f}", f"{rf:.1f}", + r["snr_raw"], f"{r['snr_db']:.2f}", + r["agc1"], r["agc2"], + f"{p:.2f}", int(r["locked"]) + ]) + + # Terminal bar chart display + if not args.waterfall: + print() + p_min = min(powers) if powers else -40 + p_max = max(powers) if powers else 0 + p_range = p_max - p_min if p_max != p_min else 1 + + for i, (f, p) in enumerate(zip(freqs, powers)): + rf = if_to_rf(f, lnb_lo) + bar = ascii_bar_h(p - p_min, p_range, width=40) + label = f"{rf:7.0f}" if lnb_lo > 0 else f"{f:7.0f}" + locked = results[i]["locked"] + lock_mark = " *" if locked else "" + print(f" {label} |{bar}| {p:6.1f} dB{lock_mark}") + + # Waterfall display + if args.waterfall: + p_min = min(powers) if powers else -40 + p_max = max(powers) if powers else 0 + line = "" + for p in powers: + color = power_color(p, p_min, p_max) + line += f"{color}█{ANSI_RESET}" + ts = datetime.now().strftime("%H:%M:%S") + print(f" {ts} {line}") + + # Peak detection + if not args.waterfall and freqs: + peaks = detect_peaks(freqs, powers, threshold_db=args.threshold if hasattr(args, 'threshold') else 3.0) + if peaks: + print(f"\n Peaks ({len(peaks)} found):") + for freq, pwr, idx in peaks: + rf = if_to_rf(freq, lnb_lo) + locked = results[idx]["locked"] + lock_str = " LOCKED" if locked else "" + label = f"{rf:.0f} MHz RF" if lnb_lo > 0 else f"{freq:.0f} MHz" + print(f" {label} {pwr:.1f} dB{lock_str}") + + if csv_file: + csv_file.close() + print(f"\n CSV saved: {args.csv}") + + # Matplotlib plot + if args.plot: + _plot_spectrum(freqs, powers, lnb_lo, all_sweeps) + + +def _plot_spectrum(freqs, powers, lnb_lo, all_sweeps=None): + """Show matplotlib spectrum plot.""" + try: + import matplotlib.pyplot as plt + except ImportError: + print(" matplotlib required for --plot: pip install matplotlib") + return + + rf_freqs = [if_to_rf(f, lnb_lo) for f in freqs] + x_label = "Frequency (MHz RF)" if lnb_lo > 0 else "Frequency (MHz IF)" + + fig, ax = plt.subplots(figsize=(14, 6)) + ax.plot(rf_freqs, powers, '-', linewidth=0.8, color='#2196F3') + ax.fill_between(rf_freqs, min(powers), powers, alpha=0.15, color='#2196F3') + ax.set_xlabel(x_label) + ax.set_ylabel("Power (dB, relative)") + ax.set_title("SkyWalker-1 Spectrum") + ax.grid(True, alpha=0.3) + + # Mark peaks + peaks = detect_peaks(freqs, powers) + if peaks: + peak_x = [if_to_rf(f, lnb_lo) for f, _, _ in peaks] + peak_y = [p for _, p, _ in peaks] + ax.scatter(peak_x, peak_y, color='#F44336', zorder=5, s=30) + for px, py in zip(peak_x, peak_y): + ax.annotate(f"{px:.0f}", (px, py), textcoords="offset points", + xytext=(0, 8), ha='center', fontsize=7, color='#F44336') + + plt.tight_layout() + plt.show() + + +# --- Mode: scan --- + +def cmd_scan(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Automated transponder scanner: coarse sweep, peak detect, blind scan.""" + start = args.start + stop = args.stop + lnb_lo = args.lnb_lo + threshold = args.threshold + sr_min = args.sr_min * 1000 + sr_max = args.sr_max * 1000 + sr_step = args.sr_step * 1000 + + print(f"SkyWalker-1 Transponder Scanner") + print(f"{'=' * 60}") + print(f" IF Range: {start}-{stop} MHz") + if lnb_lo > 0: + print(f" RF Range: {start + lnb_lo:.0f}-{stop + lnb_lo:.0f} MHz") + print(f" Threshold: {threshold} dB above noise floor") + print(f" SR Range: {args.sr_min}-{args.sr_max} ksps (step {args.sr_step})") + print() + + sw.ensure_booted() + + # Configure LNB + if args.pol: + sw.set_lnb_voltage(args.pol.upper() in ("H", "L")) + if args.band: + sw.set_22khz_tone(args.band == "high") + + # Phase 1: Coarse spectrum sweep + print("[Phase 1] Coarse spectrum sweep...") + coarse_step = 10 # MHz + freqs, powers, results = sw.sweep_spectrum( + start, stop, coarse_step, dwell_ms=15, sr_ksps=20000 + ) + print(f" {len(freqs)} points measured") + + # Phase 2: Find peaks + print(f"\n[Phase 2] Peak detection (threshold {threshold} dB)...") + peaks = detect_peaks(freqs, powers, threshold_db=threshold) + if not peaks: + print(" No peaks found above threshold.") + print(" Try lowering --threshold or checking dish alignment.") + return + + print(f" {len(peaks)} candidate peaks:") + for freq, pwr, idx in peaks: + rf = if_to_rf(freq, lnb_lo) + print(f" {rf:.0f} MHz {pwr:.1f} dB") + + # Phase 2.5: Fine sweep around each peak + print(f"\n[Phase 2.5] Fine sweep around peaks...") + refined_peaks = [] + for freq, pwr, idx in peaks: + fine_start = max(start, freq - 15) + fine_stop = min(stop, freq + 15) + fine_freqs, fine_powers, fine_results = sw.sweep_spectrum( + fine_start, fine_stop, step_mhz=2.0, dwell_ms=20, sr_ksps=20000 + ) + if fine_powers: + best_idx = fine_powers.index(max(fine_powers)) + refined_peaks.append(( + fine_freqs[best_idx], + fine_powers[best_idx], + fine_results[best_idx] + )) + rf = if_to_rf(fine_freqs[best_idx], lnb_lo) + print(f" {rf:.0f} MHz {fine_powers[best_idx]:.1f} dB (refined)") + + # Phase 3: Blind scan at each refined peak + print(f"\n[Phase 3] Blind scan at {len(refined_peaks)} peaks...") + found = [] + + for freq, pwr, result in refined_peaks: + freq_khz_int = int(freq * 1000) + rf = if_to_rf(freq, lnb_lo) + print(f" Scanning {rf:.0f} MHz (IF {freq:.0f})...", end="", flush=True) + + # Build blind scan EP0 payload + payload = struct.pack('= 8 and resp[0] != 0: + found_freq = struct.unpack_from(' None: + """Real-time signal monitor / dish alignment aid.""" + freq_mhz = args.freq + sr_ksps = args.sr + lnb_lo = args.lnb_lo + rate = args.rate + poll_interval = 1.0 / rate + + # Calculate IF frequency + if lnb_lo > 0: + if_mhz = freq_mhz - lnb_lo + else: + if_mhz = freq_mhz + + if_khz = int(if_mhz * 1000) + sr_sps = sr_ksps * 1000 + + print(f"SkyWalker-1 Signal Monitor") + print(f"{'=' * 60}") + if lnb_lo > 0: + print(f" Frequency: {freq_mhz} MHz (IF {if_mhz:.0f} MHz, LNB LO {lnb_lo} MHz)") + else: + print(f" Frequency: {if_mhz} MHz (direct input)") + print(f" Symbol rate: {sr_ksps} ksps") + print(f" Poll rate: {rate} Hz") + if args.audio: + print(f" Audio: ON") + print(f"\n Press Ctrl-C to stop\n") + + sw.ensure_booted() + + # Configure LNB + if args.pol: + sw.set_lnb_voltage(args.pol.upper() in ("H", "L")) + if args.band: + sw.set_22khz_tone(args.band == "high") + + # Initial tune + sw.tune(sr_sps, if_khz, 0, 5) # QPSK, auto-FEC + time.sleep(0.5) + + history = [] + peak_snr = 0.0 + peak_power = -99.0 + running = True + + def stop(signum, frame): + nonlocal running + running = False + + signal.signal(signal.SIGINT, stop) + signal.signal(signal.SIGTERM, stop) + + while running: + t0 = time.time() + + sig = sw.signal_monitor() + snr_db = sig["snr_db"] + power = sig["power_db"] + locked = sig["locked"] + agc1 = sig["agc1"] + + history.append(snr_db) + if args.peak_hold: + peak_snr = max(peak_snr, snr_db) + peak_power = max(peak_power, power) + + # Build display + lock_str = "LOCK" if locked else "----" + bar = signal_bar(sig["snr_pct"], width=35) + + clear_line() + line = f" [{lock_str}] SNR {snr_db:5.1f} dB AGC {agc1:5d} {bar}" + if args.peak_hold: + line += f" peak {peak_snr:.1f} dB" + sys.stdout.write(line) + + # Sparkline history + if len(history) > 1: + spark = sparkline(history, width=min(40, len(history))) + sys.stdout.write(f"\n History: {spark}") + sys.stdout.write("\033[F") # cursor up + + sys.stdout.flush() + + # Audio feedback + if args.audio: + _beep_proportional(snr_db) + + # Pace the polling + elapsed = time.time() - t0 + sleep_time = poll_interval - elapsed + if sleep_time > 0: + time.sleep(sleep_time) + + print(f"\n\n Stopped. {len(history)} samples collected.") + if args.peak_hold: + print(f" Peak SNR: {peak_snr:.1f} dB") + + if args.plot: + _plot_monitor_history(history, rate) + + +def _beep_proportional(snr_db: float): + """Emit a pitch-proportional beep for dish alignment.""" + # Map SNR 0-15 dB to frequency 200-2000 Hz + freq = int(200 + min(15, max(0, snr_db)) / 15 * 1800) + duration_ms = 50 + try: + sys.stdout.write(f"\033[10;{freq}]\033[11;{duration_ms}]\a") + sys.stdout.flush() + except Exception: + pass # Terminal doesn't support bell frequency control + + +def _plot_monitor_history(history, rate): + """Plot signal monitor history with matplotlib.""" + try: + import matplotlib.pyplot as plt + except ImportError: + print(" matplotlib required for --plot: pip install matplotlib") + return + + t = [i / rate for i in range(len(history))] + fig, ax = plt.subplots(figsize=(12, 5)) + ax.plot(t, history, '-', linewidth=0.8, color='#4CAF50') + ax.set_xlabel("Time (s)") + ax.set_ylabel("SNR (dB)") + ax.set_title("Signal Monitor History") + ax.grid(True, alpha=0.3) + plt.tight_layout() + plt.show() + + +# --- Mode: lband --- + +def cmd_lband(sw: SkyWalker1, args: argparse.Namespace) -> None: + """L-band direct input spectrum analyzer with allocation annotations.""" + start = args.start + stop = args.stop + step = args.step + dwell = args.dwell + + # Narrow to 23cm if requested + if args.ham_23cm: + start = 1240 + stop = 1300 + step = 0.5 + + steps = int((stop - start) / step) + 1 + est_time = steps * (dwell + 2) / 1000.0 + + print(f"SkyWalker-1 L-Band Analyzer") + print(f"{'=' * 60}") + print(f" Range: {start}-{stop} MHz (direct input, no LNB)") + print(f" Steps: {steps} (step {step} MHz, dwell {dwell} ms)") + print(f" Est. sweep: {est_time:.1f}s") + print() + print(" NOTE: Can detect carrier PRESENCE at any frequency even if") + print(" modulation is incompatible with the BCM4500 demodulator.") + print() + + sw.ensure_booted() + + # Disable LNB power for direct input + sw.start_intersil(on=False) + time.sleep(0.1) + + # Show band info + if args.band_info: + print(f" L-Band Allocations in range:") + for lo, hi, name in LBAND_ALLOCATIONS: + if lo < stop and hi > start: + overlap_lo = max(lo, start) + overlap_hi = min(hi, stop) + print(f" {overlap_lo:7.0f}-{overlap_hi:<7.0f} MHz {name}") + print() + + # Sweep + def progress(freq, step_num, total, result): + pct = (step_num + 1) / total * 100 + clear_line() + sys.stdout.write(f" [{pct:5.1f}%] {freq:.1f} MHz" + f" SNR={result['snr_db']:.1f} dB" + f" AGC={result['agc1']}") + sys.stdout.flush() + + t0 = time.time() + freqs, powers, results = sw.sweep_spectrum( + start, stop, step, dwell, sr_ksps=20000, + callback=progress if not args.waterfall else None + ) + elapsed = time.time() - t0 + clear_line() + print(f" Sweep complete: {len(freqs)} points in {elapsed:.1f}s\n") + + # CSV output + csv_writer = None + csv_file = None + if args.csv: + csv_file = open(args.csv, 'w', newline='') + csv_writer = csv.writer(csv_file) + csv_writer.writerow(["freq_mhz", "snr_raw", "snr_db", "agc1", "agc2", + "power_db", "locked", "allocation"]) + for i, (f, p, r) in enumerate(zip(freqs, powers, results)): + alloc = _freq_allocation(f) + csv_writer.writerow([ + f"{f:.1f}", r["snr_raw"], f"{r['snr_db']:.2f}", + r["agc1"], r["agc2"], f"{p:.2f}", int(r["locked"]), + alloc + ]) + csv_file.close() + print(f" CSV saved: {args.csv}") + + # Terminal display with allocation annotations + if not args.waterfall: + p_min = min(powers) if powers else -40 + p_max = max(powers) if powers else 0 + p_range = p_max - p_min if p_max != p_min else 1 + last_alloc = "" + + for i, (f, p) in enumerate(zip(freqs, powers)): + bar = ascii_bar_h(p - p_min, p_range, width=35) + locked = results[i]["locked"] + lock_mark = " *" if locked else "" + alloc = _freq_allocation(f) + + # Print allocation header when it changes + if alloc != last_alloc and alloc: + print(f" {'─' * 55}") + print(f" ┌ {alloc}") + last_alloc = alloc + elif alloc != last_alloc: + last_alloc = alloc + + print(f" {f:7.1f} |{bar}| {p:6.1f} dB{lock_mark}") + + # Waterfall + if args.waterfall: + p_min = min(powers) if powers else -40 + p_max = max(powers) if powers else 0 + line = "" + for p in powers: + color = power_color(p, p_min, p_max) + line += f"{color}█{ANSI_RESET}" + ts = datetime.now().strftime("%H:%M:%S") + print(f" {ts} {line}") + + # Peaks + peaks = detect_peaks(freqs, powers, threshold_db=3.0) + if peaks: + print(f"\n Peaks ({len(peaks)} found):") + for freq, pwr, idx in peaks: + alloc = _freq_allocation(freq) + alloc_str = f" [{alloc}]" if alloc else "" + locked = results[idx]["locked"] + lock_str = " LOCKED" if locked else "" + print(f" {freq:.1f} MHz {pwr:.1f} dB{lock_str}{alloc_str}") + + if args.plot: + _plot_lband(freqs, powers) + + +def _freq_allocation(freq_mhz: float) -> str: + """Return the allocation name for a given frequency, or empty string.""" + for lo, hi, name in LBAND_ALLOCATIONS: + if lo <= freq_mhz <= hi: + return name + return "" + + +def _plot_lband(freqs, powers): + """L-band spectrum plot with allocation shading.""" + try: + import matplotlib.pyplot as plt + except ImportError: + print(" matplotlib required for --plot: pip install matplotlib") + return + + fig, ax = plt.subplots(figsize=(14, 6)) + ax.plot(freqs, powers, '-', linewidth=0.8, color='#FF9800') + ax.fill_between(freqs, min(powers), powers, alpha=0.1, color='#FF9800') + + # Shade allocations + colors = ['#E3F2FD', '#FFF3E0', '#E8F5E9', '#F3E5F5', + '#FBE9E7', '#E0F7FA', '#ECEFF1'] + for i, (lo, hi, name) in enumerate(LBAND_ALLOCATIONS): + if lo < max(freqs) and hi > min(freqs): + c = colors[i % len(colors)] + ax.axvspan(lo, hi, alpha=0.3, color=c, label=name) + mid = (lo + hi) / 2 + ax.text(mid, max(powers) * 0.95, name, + ha='center', fontsize=6, rotation=45) + + ax.set_xlabel("Frequency (MHz)") + ax.set_ylabel("Power (dB, relative)") + ax.set_title("SkyWalker-1 L-Band Spectrum") + ax.grid(True, alpha=0.3) + plt.tight_layout() + plt.show() + + +# --- Mode: track --- + +def cmd_track(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Lock to a frequency and log power/lock/status over time.""" + freq_mhz = args.freq + sr_ksps = args.sr + lnb_lo = args.lnb_lo + rate = args.rate + duration = args.duration + poll_interval = 1.0 / rate + + if lnb_lo > 0: + if_mhz = freq_mhz - lnb_lo + else: + if_mhz = freq_mhz + + if_khz = int(if_mhz * 1000) + sr_sps = sr_ksps * 1000 + + print(f"SkyWalker-1 Carrier Tracker") + print(f"{'=' * 60}") + if lnb_lo > 0: + print(f" Frequency: {freq_mhz} MHz (IF {if_mhz:.0f} MHz)") + else: + print(f" Frequency: {if_mhz} MHz (direct)") + print(f" Symbol rate: {sr_ksps} ksps") + print(f" Poll rate: {rate} Hz") + if duration: + print(f" Duration: {duration}s") + if args.log: + print(f" Log file: {args.log}") + print(f"\n Press Ctrl-C to stop\n") + + sw.ensure_booted() + + if args.pol: + sw.set_lnb_voltage(args.pol.upper() in ("H", "L")) + if args.band: + sw.set_22khz_tone(args.band == "high") + + # Tune once + sw.tune(sr_sps, if_khz, 0, 5) + time.sleep(0.5) + + # Setup logging + log_file = None + log_writer = None + if args.log: + log_file = open(args.log, 'w', newline='') + log_writer = csv.writer(log_file) + log_writer.writerow(["timestamp", "elapsed_s", "snr_db", "agc1", "agc2", + "power_db", "locked", "lock_reg", "status_reg"]) + + jsonl_file = None + if args.json_lines: + jsonl_file = open(args.json_lines, 'w') + + history_snr = [] + history_power = [] + was_locked = None + sample_count = 0 + start_time = time.time() + running = True + + def stop_handler(signum, frame): + nonlocal running + running = False + + signal.signal(signal.SIGINT, stop_handler) + signal.signal(signal.SIGTERM, stop_handler) + + # Drift tracking window + drift_window = 5 # MHz each side + drift_history = [] + + while running: + if duration and (time.time() - start_time) >= duration: + break + + t0 = time.time() + elapsed = t0 - start_time + + sig = sw.signal_monitor() + snr_db = sig["snr_db"] + power = sig["power_db"] + locked = sig["locked"] + + history_snr.append(snr_db) + history_power.append(power) + sample_count += 1 + + # Lock state transitions + if was_locked is not None and locked != was_locked: + ts = datetime.now().strftime("%H:%M:%S.%f")[:-3] + if locked: + print(f"\n [{ts}] >>> LOCK ACQUIRED SNR {snr_db:.1f} dB") + else: + print(f"\n [{ts}] <<< LOCK LOST") + was_locked = locked + + # Drift tracking + if args.drift_track and sample_count % (rate * 5) == 0: + # Every 5 seconds, do a narrow sweep to find peak + drift_start = max(950, if_mhz - drift_window) + drift_stop = min(2150, if_mhz + drift_window) + drift_freqs, drift_powers, _ = sw.sweep_spectrum( + drift_start, drift_stop, step_mhz=1.0, dwell_ms=5, sr_ksps=sr_ksps + ) + if drift_powers: + peak_idx = drift_powers.index(max(drift_powers)) + peak_freq = drift_freqs[peak_idx] + drift_hz = (peak_freq - if_mhz) * 1000 + drift_history.append((elapsed, drift_hz)) + if abs(drift_hz) > 100: + print(f"\n DRIFT: {drift_hz:+.0f} kHz from center") + # Re-tune to original + sw.tune(sr_sps, if_khz, 0, 5) + time.sleep(0.1) + + # Display + lock_str = "LOCK" if locked else "----" + bar = signal_bar(sig["snr_pct"], width=30) + clear_line() + sys.stdout.write(f" [{lock_str}] {elapsed:7.1f}s SNR {snr_db:5.1f} dB {bar}" + f" #{sample_count}") + sys.stdout.flush() + + # Log + ts_iso = datetime.now().isoformat() + if log_writer: + log_writer.writerow([ + ts_iso, f"{elapsed:.3f}", f"{snr_db:.2f}", + sig["agc1"], sig["agc2"], f"{power:.2f}", + int(locked), sig["lock"], sig["status"] + ]) + + if jsonl_file: + record = { + "ts": ts_iso, + "elapsed": round(elapsed, 3), + "snr_db": round(snr_db, 2), + "agc1": sig["agc1"], + "agc2": sig["agc2"], + "power_db": round(power, 2), + "locked": locked, + } + jsonl_file.write(json.dumps(record) + "\n") + + # Pace + sleep_time = poll_interval - (time.time() - t0) + if sleep_time > 0: + time.sleep(sleep_time) + + total_time = time.time() - start_time + print(f"\n\n Stopped. {sample_count} samples in {total_time:.1f}s") + + if log_file: + log_file.close() + print(f" Log saved: {args.log}") + if jsonl_file: + jsonl_file.close() + print(f" JSON-lines saved: {args.json_lines}") + + if args.plot: + _plot_track(history_snr, history_power, rate, drift_history) + + +def _plot_track(history_snr, history_power, rate, drift_history=None): + """Plot tracking history.""" + try: + import matplotlib.pyplot as plt + except ImportError: + print(" matplotlib required for --plot: pip install matplotlib") + return + + t = [i / rate for i in range(len(history_snr))] + + fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True) + + axes[0].plot(t, history_snr, '-', linewidth=0.6, color='#4CAF50') + axes[0].set_ylabel("SNR (dB)") + axes[0].set_title("Carrier Tracking") + axes[0].grid(True, alpha=0.3) + + axes[1].plot(t, history_power, '-', linewidth=0.6, color='#2196F3') + axes[1].set_ylabel("Power (dB)") + axes[1].set_xlabel("Time (s)") + axes[1].grid(True, alpha=0.3) + + if drift_history: + ax_drift = axes[0].twinx() + dt = [d[0] for d in drift_history] + dv = [d[1] for d in drift_history] + ax_drift.plot(dt, dv, 'o-', color='#F44336', markersize=3, linewidth=0.8) + ax_drift.set_ylabel("Drift (kHz)", color='#F44336') + + plt.tight_layout() + plt.show() + + +# --- CLI --- + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="skywalker.py", + description="Genpix SkyWalker-1 multi-mode RF tool", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +examples: + %(prog)s spectrum --start 950 --stop 2150 --step 5 + %(prog)s spectrum --lnb-lo 9750 --start 950 --stop 2150 --plot + %(prog)s scan --lnb-lo 9750 --pol H --band high + %(prog)s monitor 12520 27500 --lnb-lo 9750 --pol H --rate 10 + %(prog)s lband --band-info + %(prog)s lband --23cm --plot + %(prog)s track 12520 27500 --lnb-lo 9750 --log signal.csv --duration 60 + +RF coverage (with different LNB configurations): + No LNB (direct): 950-2150 MHz (L-band: ham 23cm, GPS, GOES, HRPT) + Ku LNB (9750 LO): 10700-11900 MHz (satellite TV low band) + Ku LNB (10600 LO): 11550-12750 MHz (satellite TV high band) + Custom (9000 LO): 9950-11150 MHz (QO-100 DATV @ ~1491 MHz IF) +""") + parser.add_argument('-v', '--verbose', action='store_true', + help="Show raw USB traffic") + + sub = parser.add_subparsers(dest='command') + + # spectrum + p_spec = sub.add_parser('spectrum', help="Sweep spectrum analyzer", + formatter_class=argparse.RawDescriptionHelpFormatter) + p_spec.add_argument('--start', type=float, default=950, + help="Start IF frequency in MHz (default: 950)") + p_spec.add_argument('--stop', type=float, default=2150, + help="Stop IF frequency in MHz (default: 2150)") + p_spec.add_argument('--step', type=float, default=5, + help="Step size in MHz (default: 5)") + p_spec.add_argument('--dwell', type=int, default=10, + help="Dwell time per step in ms (default: 10)") + p_spec.add_argument('--lnb-lo', type=float, default=0, + help="LNB LO frequency in MHz (0=direct input)") + p_spec.add_argument('--sr', type=int, default=20000, + help="Symbol rate for measurement in ksps (default: 20000)") + p_spec.add_argument('--waterfall', action='store_true', + help="Waterfall display (time x frequency x power)") + p_spec.add_argument('--sweeps', type=int, default=1, + help="Number of sweeps (default: 1)") + p_spec.add_argument('--threshold', type=float, default=3.0, + help="Peak detection threshold in dB (default: 3)") + p_spec.add_argument('--plot', action='store_true', + help="Show matplotlib plot") + p_spec.add_argument('--csv', metavar='FILE', + help="Save sweep data to CSV") + + # scan + p_scan = sub.add_parser('scan', help="Automated transponder scanner", + formatter_class=argparse.RawDescriptionHelpFormatter) + p_scan.add_argument('--start', type=float, default=950, + help="Start IF frequency in MHz (default: 950)") + p_scan.add_argument('--stop', type=float, default=2150, + help="Stop IF frequency in MHz (default: 2150)") + p_scan.add_argument('--threshold', type=float, default=3, + help="Peak detection threshold in dB (default: 3)") + p_scan.add_argument('--sr-min', type=int, default=1000, + help="Minimum symbol rate in ksps (default: 1000)") + p_scan.add_argument('--sr-max', type=int, default=30000, + help="Maximum symbol rate in ksps (default: 30000)") + p_scan.add_argument('--sr-step', type=int, default=500, + help="Symbol rate step in ksps (default: 500)") + p_scan.add_argument('--lnb-lo', type=float, default=9750, + help="LNB LO frequency in MHz (default: 9750)") + p_scan.add_argument('--pol', choices=['H', 'V', 'L', 'R'], + help="Polarization (sets LNB voltage)") + p_scan.add_argument('--band', choices=['low', 'high'], + help="LNB band (sets 22 kHz tone)") + p_scan.add_argument('--json', action='store_true', + help="Output results as JSON") + p_scan.add_argument('--csv', metavar='FILE', + help="Save results to CSV") + + # monitor + p_mon = sub.add_parser('monitor', help="Real-time signal monitor / dish alignment", + formatter_class=argparse.RawDescriptionHelpFormatter) + p_mon.add_argument('freq', type=float, + help="Frequency in MHz (RF if --lnb-lo set, IF otherwise)") + p_mon.add_argument('sr', type=int, + help="Symbol rate in ksps") + p_mon.add_argument('--pol', choices=['H', 'V', 'L', 'R'], + help="Polarization (sets LNB voltage)") + p_mon.add_argument('--band', choices=['low', 'high'], + help="LNB band (sets 22 kHz tone)") + p_mon.add_argument('--lnb-lo', type=float, default=0, + help="LNB LO frequency in MHz (0=direct input)") + p_mon.add_argument('--rate', type=float, default=10, + help="Poll rate in Hz (default: 10, max ~50)") + p_mon.add_argument('--audio', action='store_true', + help="Pitch-proportional beep for hands-free alignment") + p_mon.add_argument('--peak-hold', action='store_true', + help="Track and display maximum signal seen") + p_mon.add_argument('--history', type=int, default=60, + help="Sparkline history length in samples (default: 60)") + p_mon.add_argument('--plot', action='store_true', + help="Show matplotlib plot after stopping") + + # lband + p_lband = sub.add_parser('lband', help="L-band direct input analyzer", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +L-band mode uses direct input (no LNB) to monitor 950-2150 MHz. +Can detect carrier PRESENCE at any frequency even if modulation +is incompatible with the BCM4500 demodulator. + +Known allocations in range: + 1240-1300 MHz Amateur 23cm + 1525-1559 MHz Inmarsat downlink + 1559-1610 MHz GNSS (GPS L1, Galileo E1) + 1610-1626 MHz Iridium downlink + 1670-1710 MHz MetSat (GOES LRIT, NOAA HRPT) + 1710-1785 MHz LTE/AWS uplink + 1920-2025 MHz UMTS uplink +""") + p_lband.add_argument('--start', type=float, default=950, + help="Start frequency in MHz (default: 950)") + p_lband.add_argument('--stop', type=float, default=2150, + help="Stop frequency in MHz (default: 2150)") + p_lband.add_argument('--step', type=float, default=2, + help="Step size in MHz (default: 2)") + p_lband.add_argument('--dwell', type=int, default=20, + help="Dwell time per step in ms (default: 20)") + p_lband.add_argument('--23cm', dest='ham_23cm', action='store_true', + help="Narrow to 1240-1300 MHz with 500 kHz steps") + p_lband.add_argument('--band-info', action='store_true', + help="Print L-band allocation table") + p_lband.add_argument('--waterfall', action='store_true', + help="Waterfall display") + p_lband.add_argument('--plot', action='store_true', + help="Show matplotlib plot") + p_lband.add_argument('--csv', metavar='FILE', + help="Save sweep data to CSV") + + # track + p_track = sub.add_parser('track', help="Carrier/beacon tracker with logging", + formatter_class=argparse.RawDescriptionHelpFormatter) + p_track.add_argument('freq', type=float, + help="Frequency in MHz (RF if --lnb-lo set, IF otherwise)") + p_track.add_argument('sr', type=int, + help="Symbol rate in ksps") + p_track.add_argument('--pol', choices=['H', 'V', 'L', 'R'], + help="Polarization (sets LNB voltage)") + p_track.add_argument('--band', choices=['low', 'high'], + help="LNB band (sets 22 kHz tone)") + p_track.add_argument('--lnb-lo', type=float, default=0, + help="LNB LO frequency in MHz (0=direct input)") + p_track.add_argument('--rate', type=float, default=1, + help="Poll rate in Hz (default: 1)") + p_track.add_argument('--duration', type=float, default=None, + help="Tracking duration in seconds (default: until Ctrl-C)") + p_track.add_argument('--log', metavar='FILE', + help="Log CSV: timestamp, snr, agc, power, lock, status") + p_track.add_argument('--drift-track', action='store_true', + help="Periodically sweep narrow window to measure frequency drift") + p_track.add_argument('--plot', action='store_true', + help="Show matplotlib plot after stopping") + p_track.add_argument('--json-lines', metavar='FILE', + help="Log as JSON-lines (one JSON object per line)") + + return parser + + +def main(): + parser = build_parser() + args = parser.parse_args() + + if not args.command: + parser.print_help() + sys.exit(0) + + dispatch = { + 'spectrum': cmd_spectrum, + 'scan': cmd_scan, + 'monitor': cmd_monitor, + 'lband': cmd_lband, + 'track': cmd_track, + } + + handler = dispatch.get(args.command) + if handler is None: + parser.print_help() + sys.exit(1) + + with SkyWalker1(verbose=args.verbose) as sw: + handler(sw, args) + + +if __name__ == '__main__': + main() diff --git a/tools/survey.py b/tools/survey.py index 1d78649..c8f833a 100644 --- a/tools/survey.py +++ b/tools/survey.py @@ -1,455 +1,455 @@ -#!/usr/bin/env python3 -""" -Carrier survey CLI for the Genpix SkyWalker-1. - -Subcommands: - full-scan Full six-stage carrier survey - quick-scan Fast sweep + peak detection only - diff Compare two saved survey catalogs - export Export a survey to CSV, JSON, or text - view View the latest or a specified survey - qo100 QO-100 narrowband transponder survey with optimized params -""" - -import sys -import os -import argparse -import csv -import io -import json -import time - -# Ensure the tools directory is on the import path -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from skywalker_lib import SkyWalker1 -from signal_analysis import adaptive_noise_floor, detect_peaks_enhanced, classify_carrier -from carrier_catalog import CarrierCatalog, CarrierEntry, CatalogDiff, CATALOG_DIR -from survey_engine import SurveyEngine - - -def progress_callback(verbose: bool): - """Return a callback function for SurveyEngine progress reporting.""" - def cb(stage, pct, msg): - if verbose: - print(f" [{stage:>17s}] {pct:5.1f}% {msg}", file=sys.stderr) - else: - sys.stderr.write(f"\r {stage}: {pct:.0f}% {msg[:60]:<60s}") - sys.stderr.flush() - return cb - - -# -- Subcommand handlers -- - -def cmd_full_scan(args: argparse.Namespace) -> None: - """Run a full six-stage carrier survey.""" - print(f"SkyWalker-1 Full Carrier Survey") - print(f" Range: {args.start}-{args.stop} MHz") - print(f" Coarse step: {args.coarse_step} MHz, Fine step: {args.fine_step} MHz") - print(f" SR range: {args.sr_min / 1e6:.1f} - {args.sr_max / 1e6:.1f} Msps") - print() - - cb = progress_callback(args.verbose) - - with SkyWalker1(verbose=args.verbose) as dev: - dev.ensure_booted() - if args.pol or args.band: - dev.configure_lnb(pol=args.pol, band=args.band) - - engine = SurveyEngine(dev, callback=cb) - catalog = engine.run_full_scan( - start_mhz=args.start, - stop_mhz=args.stop, - coarse_step=args.coarse_step, - fine_step=args.fine_step, - sr_min=args.sr_min, - sr_max=args.sr_max, - sr_step=args.sr_step, - ) - - if not args.verbose: - sys.stderr.write("\r" + " " * 80 + "\r") - sys.stderr.flush() - - # Set catalog metadata - catalog.band = args.band or "" - catalog.pol = args.pol or "" - if args.name: - catalog.name = args.name - - # Save - if args.output: - path = catalog.save(args.output) - else: - path = catalog.save() - - print() - print(catalog.summary()) - print() - print(f"Saved to: {path}") - - -def cmd_quick_scan(args: argparse.Namespace) -> None: - """Quick sweep + peak detection, no blind scan.""" - print(f"SkyWalker-1 Quick Scan") - print(f" Range: {args.start}-{args.stop} MHz, step: {args.step} MHz") - print() - - cb = progress_callback(args.verbose) - - with SkyWalker1(verbose=args.verbose) as dev: - dev.ensure_booted() - if args.pol or args.band: - dev.configure_lnb(pol=args.pol, band=args.band) - - engine = SurveyEngine(dev, callback=cb) - peaks = engine.run_quick_scan( - start_mhz=args.start, - stop_mhz=args.stop, - step=args.step, - ) - - if not args.verbose: - sys.stderr.write("\r" + " " * 80 + "\r") - sys.stderr.flush() - - if not peaks: - print("No peaks detected above noise floor.") - return - - print(f"\nDetected {len(peaks)} carrier(s):\n") - print(f" {'#':>3} {'Freq (MHz)':>10} {'Power (dB)':>10} " - f"{'BW (MHz)':>8} {'Prominence':>10} Quality") - print(f" {'---':>3} {'----------':>10} {'----------':>10} " - f"{'--------':>8} {'----------':>10} -------") - - for i, p in enumerate(sorted(peaks, key=lambda x: x["freq"]), 1): - cls = p.get("classification", classify_carrier(p["width_mhz"], p["power"])) - quality = cls.get("signal_quality", "?") - print(f" {i:3d} {p['freq']:>10.1f} {p['power']:>+10.1f} " - f"{p['width_mhz']:>8.1f} {p['prominence_db']:>+10.1f} {quality}") - - -def cmd_diff(args: argparse.Namespace) -> None: - """Compare two survey catalog files.""" - try: - old_cat = CarrierCatalog.load(args.file1) - except (FileNotFoundError, json.JSONDecodeError) as e: - print(f"Cannot load {args.file1}: {e}", file=sys.stderr) - sys.exit(1) - - try: - new_cat = CarrierCatalog.load(args.file2) - except (FileNotFoundError, json.JSONDecodeError) as e: - print(f"Cannot load {args.file2}: {e}", file=sys.stderr) - sys.exit(1) - - print(f"Comparing surveys:") - print(f" Old: {args.file1} ({old_cat.created})") - print(f" New: {args.file2} ({new_cat.created})") - print() - - diff = CatalogDiff.diff(old_cat, new_cat) - print(CatalogDiff.format_diff(diff)) - - if args.output: - with open(args.output, 'w') as f: - json.dump(diff, f, indent=2) - print(f"\nDiff saved to: {args.output}") - - -def cmd_export(args: argparse.Namespace) -> None: - """Export a survey catalog to CSV, JSON, or text.""" - try: - catalog = CarrierCatalog.load(args.file) - except (FileNotFoundError, json.JSONDecodeError) as e: - print(f"Cannot load {args.file}: {e}", file=sys.stderr) - sys.exit(1) - - fmt = args.format - - if fmt == "json": - output = json.dumps(catalog.to_dict(), indent=2) - elif fmt == "csv": - output = _catalog_to_csv(catalog) - else: - output = catalog.summary() - - if args.output: - with open(args.output, 'w') as f: - f.write(output) - print(f"Exported to: {args.output}") - else: - print(output) - - -def cmd_view(args: argparse.Namespace) -> None: - """View a specific survey or the latest one.""" - if args.file: - filename = args.file - else: - surveys = CarrierCatalog.list_surveys() - if not surveys: - print(f"No surveys found in {CATALOG_DIR}") - sys.exit(1) - filename = surveys[0]["path"] - print(f"(Showing latest: {surveys[0]['filename']})\n") - - try: - catalog = CarrierCatalog.load(filename) - except (FileNotFoundError, json.JSONDecodeError) as e: - print(f"Cannot load {filename}: {e}", file=sys.stderr) - sys.exit(1) - - print(catalog.summary()) - - if args.verbose and catalog.carriers: - print(f"\nDetailed carrier info:") - for i, c in enumerate(sorted(catalog.carriers, key=lambda x: x.freq_khz), 1): - print(f"\n --- Carrier {i} ---") - print(f" Frequency: {c.freq_mhz:.3f} MHz ({c.freq_khz} kHz)") - print(f" Power: {c.power_db:+.1f} dB") - print(f" SNR: {c.snr_db:.1f} dB") - if c.sr_sps: - print(f" Symbol rate: {c.sr_sps} sps ({c.sr_sps / 1e6:.3f} Msps)") - if c.modulation: - print(f" Modulation: {c.modulation}") - if c.fec: - print(f" FEC: {c.fec}") - print(f" Locked: {c.locked}") - print(f" Bandwidth: {c.bw_mhz:.1f} MHz") - if c.services: - print(f" Services: {', '.join(c.services)}") - print(f" First seen: {c.first_seen}") - print(f" Last seen: {c.last_seen}") - print(f" Scan count: {c.scan_count}") - if c.classification: - cls = c.classification - if "estimated_sr_range" in cls: - sr_lo, sr_hi = cls["estimated_sr_range"] - print(f" Est. SR: {sr_lo / 1e6:.1f} - {sr_hi / 1e6:.1f} Msps") - if "likely_modulation" in cls: - print(f" Likely mod: {', '.join(cls['likely_modulation'])}") - if "signal_quality" in cls: - print(f" Quality: {cls['signal_quality']}") - - -def cmd_qo100(args: argparse.Namespace) -> None: - """ - QO-100 narrowband transponder survey with optimized parameters. - - QO-100 (Es'hail-2) narrowband transponder: 10489.500 - 10489.800 MHz - With a typical LNB LO of 9750 MHz, the IF range is ~739.5 - 739.8 MHz. - - Since most QO-100 NB signals are very narrow (< 3 kHz audio, 1-2.7 ksps - digital), this mode uses the finest practical sweep resolution and - restricted SR range. - """ - lnb_lo = args.lnb_lo - # QO-100 NB transponder: 10489.500 - 10489.800 MHz - rf_start = 10489.5 - rf_stop = 10489.8 - if_start = rf_start - lnb_lo - if_stop = rf_stop - lnb_lo - - # Validate IF range is within device capability - if if_start < 950 or if_stop > 2150: - print(f"QO-100 IF range ({if_start:.1f} - {if_stop:.1f} MHz) is outside " - f"the 950-2150 MHz hardware range with LNB LO={lnb_lo} MHz.", - file=sys.stderr) - print(f"Check your LNB LO frequency.", file=sys.stderr) - sys.exit(1) - - print(f"QO-100 Narrowband Transponder Survey") - print(f" LNB LO: {lnb_lo} MHz") - print(f" RF range: {rf_start:.3f} - {rf_stop:.3f} MHz") - print(f" IF range: {if_start:.3f} - {if_stop:.3f} MHz") - print() - - # QO-100 NB uses very low symbol rates (1-33 ksps typical for DVB-S) - # The SkyWalker-1 minimum is 256 ksps, so we set a narrow range - sr_min = 256_000 - sr_max = 2_000_000 - sr_step = 100_000 - - cb = progress_callback(args.verbose) - - with SkyWalker1(verbose=args.verbose) as dev: - dev.ensure_booted() - # QO-100 is H-pol on most setups, high band for 10 GHz - dev.configure_lnb(pol="H", band="high", lnb_lo=lnb_lo) - - engine = SurveyEngine(dev, callback=cb) - catalog = engine.run_full_scan( - start_mhz=if_start, - stop_mhz=if_stop, - coarse_step=0.5, # 500 kHz steps for the narrow band - fine_step=0.1, # 100 kHz fine resolution - sr_min=sr_min, - sr_max=sr_max, - sr_step=sr_step, - ) - - if not args.verbose: - sys.stderr.write("\r" + " " * 80 + "\r") - sys.stderr.flush() - - catalog.name = "QO-100 Narrowband" - catalog.band = "high" - catalog.pol = "H" - catalog.lnb_lo_mhz = lnb_lo - catalog.notes = (f"QO-100 Es'hail-2 narrowband transponder. " - f"RF {rf_start}-{rf_stop} MHz, LNB LO {lnb_lo} MHz.") - - if args.output: - path = catalog.save(args.output) - else: - path = catalog.save(f"survey-qo100-nb-{time.strftime('%Y-%m-%d')}.json") - - print() - print(catalog.summary()) - print() - print(f"Saved to: {path}") - - -# -- Helpers -- - -def _catalog_to_csv(catalog: CarrierCatalog) -> str: - """Convert a catalog to CSV format.""" - buf = io.StringIO() - writer = csv.writer(buf) - writer.writerow([ - "freq_khz", "freq_mhz", "sr_sps", "modulation", "fec", - "power_db", "snr_db", "locked", "bw_mhz", "services", - "first_seen", "last_seen", "scan_count", - ]) - for c in sorted(catalog.carriers, key=lambda x: x.freq_khz): - writer.writerow([ - c.freq_khz, f"{c.freq_mhz:.3f}", c.sr_sps, - c.modulation, c.fec, - f"{c.power_db:.1f}", f"{c.snr_db:.1f}", - c.locked, f"{c.bw_mhz:.1f}", - "|".join(c.services), - c.first_seen, c.last_seen, c.scan_count, - ]) - return buf.getvalue() - - -# -- CLI -- - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - description="Carrier survey tool for the Genpix SkyWalker-1", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -examples: - %(prog)s full-scan - %(prog)s full-scan --start 1100 --stop 1200 --output my-scan.json - %(prog)s quick-scan - %(prog)s diff survey-2026-02-14-low-V.json survey-2026-02-15-low-V.json - %(prog)s export survey-2026-02-15-low-V.json --format csv - %(prog)s view - %(prog)s qo100 --lnb-lo 9750 -""") - parser.add_argument('-v', '--verbose', action='store_true', - help="Verbose progress and debug output") - - sub = parser.add_subparsers(dest='command') - - # full-scan - p_full = sub.add_parser('full-scan', help="Full six-stage carrier survey") - p_full.add_argument('--start', type=float, default=950, - help="Start frequency in MHz (default: 950)") - p_full.add_argument('--stop', type=float, default=2150, - help="Stop frequency in MHz (default: 2150)") - p_full.add_argument('--coarse-step', type=float, default=5.0, - help="Coarse sweep step in MHz (default: 5.0)") - p_full.add_argument('--fine-step', type=float, default=1.0, - help="Fine sweep step in MHz (default: 1.0)") - p_full.add_argument('--sr-min', type=int, default=1_000_000, - help="Min symbol rate for blind scan in sps (default: 1000000)") - p_full.add_argument('--sr-max', type=int, default=30_000_000, - help="Max symbol rate for blind scan in sps (default: 30000000)") - p_full.add_argument('--sr-step', type=int, default=1_000_000, - help="Symbol rate step for blind scan in sps (default: 1000000)") - p_full.add_argument('--pol', choices=['H', 'V', 'L', 'R'], - help="LNB polarization") - p_full.add_argument('--band', choices=['low', 'high'], - help="LNB band (low/high)") - p_full.add_argument('--name', type=str, default="", - help="Survey name/label") - p_full.add_argument('--output', '-o', type=str, default=None, - help="Output filename (default: auto-generated)") - - # quick-scan - p_quick = sub.add_parser('quick-scan', help="Quick sweep + peak detection") - p_quick.add_argument('--start', type=float, default=950, - help="Start frequency in MHz (default: 950)") - p_quick.add_argument('--stop', type=float, default=2150, - help="Stop frequency in MHz (default: 2150)") - p_quick.add_argument('--step', type=float, default=5.0, - help="Sweep step in MHz (default: 5.0)") - p_quick.add_argument('--pol', choices=['H', 'V', 'L', 'R'], - help="LNB polarization") - p_quick.add_argument('--band', choices=['low', 'high'], - help="LNB band (low/high)") - - # diff - p_diff = sub.add_parser('diff', help="Compare two survey catalogs") - p_diff.add_argument('file1', help="Older survey file") - p_diff.add_argument('file2', help="Newer survey file") - p_diff.add_argument('--output', '-o', type=str, default=None, - help="Save diff as JSON to this file") - - # export - p_export = sub.add_parser('export', help="Export survey to CSV/JSON/text") - p_export.add_argument('file', help="Survey file to export") - p_export.add_argument('--format', '-f', choices=['csv', 'json', 'text'], - default='text', help="Output format (default: text)") - p_export.add_argument('--output', '-o', type=str, default=None, - help="Output file (default: stdout)") - - # view - p_view = sub.add_parser('view', help="View a survey (latest if no file given)") - p_view.add_argument('file', nargs='?', default=None, - help="Survey file to view (default: latest)") - - # qo100 - p_qo100 = sub.add_parser('qo100', - help="QO-100 narrowband transponder survey") - p_qo100.add_argument('--lnb-lo', type=float, required=True, - help="LNB local oscillator frequency in MHz " - "(e.g., 9750 for universal LNB low band)") - p_qo100.add_argument('--output', '-o', type=str, default=None, - help="Output filename (default: auto-generated)") - - return parser - - -def main(): - parser = build_parser() - args = parser.parse_args() - - if not args.command: - parser.print_help() - sys.exit(1) - - dispatch = { - 'full-scan': cmd_full_scan, - 'quick-scan': cmd_quick_scan, - 'diff': cmd_diff, - 'export': cmd_export, - 'view': cmd_view, - 'qo100': cmd_qo100, - } - - handler = dispatch.get(args.command) - if handler is None: - parser.print_help() - sys.exit(1) - - handler(args) - - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 +""" +Carrier survey CLI for the Genpix SkyWalker-1. + +Subcommands: + full-scan Full six-stage carrier survey + quick-scan Fast sweep + peak detection only + diff Compare two saved survey catalogs + export Export a survey to CSV, JSON, or text + view View the latest or a specified survey + qo100 QO-100 narrowband transponder survey with optimized params +""" + +import sys +import os +import argparse +import csv +import io +import json +import time + +# Ensure the tools directory is on the import path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from skywalker_lib import SkyWalker1 +from signal_analysis import adaptive_noise_floor, detect_peaks_enhanced, classify_carrier +from carrier_catalog import CarrierCatalog, CarrierEntry, CatalogDiff, CATALOG_DIR +from survey_engine import SurveyEngine + + +def progress_callback(verbose: bool): + """Return a callback function for SurveyEngine progress reporting.""" + def cb(stage, pct, msg): + if verbose: + print(f" [{stage:>17s}] {pct:5.1f}% {msg}", file=sys.stderr) + else: + sys.stderr.write(f"\r {stage}: {pct:.0f}% {msg[:60]:<60s}") + sys.stderr.flush() + return cb + + +# -- Subcommand handlers -- + +def cmd_full_scan(args: argparse.Namespace) -> None: + """Run a full six-stage carrier survey.""" + print(f"SkyWalker-1 Full Carrier Survey") + print(f" Range: {args.start}-{args.stop} MHz") + print(f" Coarse step: {args.coarse_step} MHz, Fine step: {args.fine_step} MHz") + print(f" SR range: {args.sr_min / 1e6:.1f} - {args.sr_max / 1e6:.1f} Msps") + print() + + cb = progress_callback(args.verbose) + + with SkyWalker1(verbose=args.verbose) as dev: + dev.ensure_booted() + if args.pol or args.band: + dev.configure_lnb(pol=args.pol, band=args.band) + + engine = SurveyEngine(dev, callback=cb) + catalog = engine.run_full_scan( + start_mhz=args.start, + stop_mhz=args.stop, + coarse_step=args.coarse_step, + fine_step=args.fine_step, + sr_min=args.sr_min, + sr_max=args.sr_max, + sr_step=args.sr_step, + ) + + if not args.verbose: + sys.stderr.write("\r" + " " * 80 + "\r") + sys.stderr.flush() + + # Set catalog metadata + catalog.band = args.band or "" + catalog.pol = args.pol or "" + if args.name: + catalog.name = args.name + + # Save + if args.output: + path = catalog.save(args.output) + else: + path = catalog.save() + + print() + print(catalog.summary()) + print() + print(f"Saved to: {path}") + + +def cmd_quick_scan(args: argparse.Namespace) -> None: + """Quick sweep + peak detection, no blind scan.""" + print(f"SkyWalker-1 Quick Scan") + print(f" Range: {args.start}-{args.stop} MHz, step: {args.step} MHz") + print() + + cb = progress_callback(args.verbose) + + with SkyWalker1(verbose=args.verbose) as dev: + dev.ensure_booted() + if args.pol or args.band: + dev.configure_lnb(pol=args.pol, band=args.band) + + engine = SurveyEngine(dev, callback=cb) + peaks = engine.run_quick_scan( + start_mhz=args.start, + stop_mhz=args.stop, + step=args.step, + ) + + if not args.verbose: + sys.stderr.write("\r" + " " * 80 + "\r") + sys.stderr.flush() + + if not peaks: + print("No peaks detected above noise floor.") + return + + print(f"\nDetected {len(peaks)} carrier(s):\n") + print(f" {'#':>3} {'Freq (MHz)':>10} {'Power (dB)':>10} " + f"{'BW (MHz)':>8} {'Prominence':>10} Quality") + print(f" {'---':>3} {'----------':>10} {'----------':>10} " + f"{'--------':>8} {'----------':>10} -------") + + for i, p in enumerate(sorted(peaks, key=lambda x: x["freq"]), 1): + cls = p.get("classification", classify_carrier(p["width_mhz"], p["power"])) + quality = cls.get("signal_quality", "?") + print(f" {i:3d} {p['freq']:>10.1f} {p['power']:>+10.1f} " + f"{p['width_mhz']:>8.1f} {p['prominence_db']:>+10.1f} {quality}") + + +def cmd_diff(args: argparse.Namespace) -> None: + """Compare two survey catalog files.""" + try: + old_cat = CarrierCatalog.load(args.file1) + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Cannot load {args.file1}: {e}", file=sys.stderr) + sys.exit(1) + + try: + new_cat = CarrierCatalog.load(args.file2) + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Cannot load {args.file2}: {e}", file=sys.stderr) + sys.exit(1) + + print(f"Comparing surveys:") + print(f" Old: {args.file1} ({old_cat.created})") + print(f" New: {args.file2} ({new_cat.created})") + print() + + diff = CatalogDiff.diff(old_cat, new_cat) + print(CatalogDiff.format_diff(diff)) + + if args.output: + with open(args.output, 'w') as f: + json.dump(diff, f, indent=2) + print(f"\nDiff saved to: {args.output}") + + +def cmd_export(args: argparse.Namespace) -> None: + """Export a survey catalog to CSV, JSON, or text.""" + try: + catalog = CarrierCatalog.load(args.file) + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Cannot load {args.file}: {e}", file=sys.stderr) + sys.exit(1) + + fmt = args.format + + if fmt == "json": + output = json.dumps(catalog.to_dict(), indent=2) + elif fmt == "csv": + output = _catalog_to_csv(catalog) + else: + output = catalog.summary() + + if args.output: + with open(args.output, 'w') as f: + f.write(output) + print(f"Exported to: {args.output}") + else: + print(output) + + +def cmd_view(args: argparse.Namespace) -> None: + """View a specific survey or the latest one.""" + if args.file: + filename = args.file + else: + surveys = CarrierCatalog.list_surveys() + if not surveys: + print(f"No surveys found in {CATALOG_DIR}") + sys.exit(1) + filename = surveys[0]["path"] + print(f"(Showing latest: {surveys[0]['filename']})\n") + + try: + catalog = CarrierCatalog.load(filename) + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Cannot load {filename}: {e}", file=sys.stderr) + sys.exit(1) + + print(catalog.summary()) + + if args.verbose and catalog.carriers: + print(f"\nDetailed carrier info:") + for i, c in enumerate(sorted(catalog.carriers, key=lambda x: x.freq_khz), 1): + print(f"\n --- Carrier {i} ---") + print(f" Frequency: {c.freq_mhz:.3f} MHz ({c.freq_khz} kHz)") + print(f" Power: {c.power_db:+.1f} dB") + print(f" SNR: {c.snr_db:.1f} dB") + if c.sr_sps: + print(f" Symbol rate: {c.sr_sps} sps ({c.sr_sps / 1e6:.3f} Msps)") + if c.modulation: + print(f" Modulation: {c.modulation}") + if c.fec: + print(f" FEC: {c.fec}") + print(f" Locked: {c.locked}") + print(f" Bandwidth: {c.bw_mhz:.1f} MHz") + if c.services: + print(f" Services: {', '.join(c.services)}") + print(f" First seen: {c.first_seen}") + print(f" Last seen: {c.last_seen}") + print(f" Scan count: {c.scan_count}") + if c.classification: + cls = c.classification + if "estimated_sr_range" in cls: + sr_lo, sr_hi = cls["estimated_sr_range"] + print(f" Est. SR: {sr_lo / 1e6:.1f} - {sr_hi / 1e6:.1f} Msps") + if "likely_modulation" in cls: + print(f" Likely mod: {', '.join(cls['likely_modulation'])}") + if "signal_quality" in cls: + print(f" Quality: {cls['signal_quality']}") + + +def cmd_qo100(args: argparse.Namespace) -> None: + """ + QO-100 narrowband transponder survey with optimized parameters. + + QO-100 (Es'hail-2) narrowband transponder: 10489.500 - 10489.800 MHz + With a typical LNB LO of 9750 MHz, the IF range is ~739.5 - 739.8 MHz. + + Since most QO-100 NB signals are very narrow (< 3 kHz audio, 1-2.7 ksps + digital), this mode uses the finest practical sweep resolution and + restricted SR range. + """ + lnb_lo = args.lnb_lo + # QO-100 NB transponder: 10489.500 - 10489.800 MHz + rf_start = 10489.5 + rf_stop = 10489.8 + if_start = rf_start - lnb_lo + if_stop = rf_stop - lnb_lo + + # Validate IF range is within device capability + if if_start < 950 or if_stop > 2150: + print(f"QO-100 IF range ({if_start:.1f} - {if_stop:.1f} MHz) is outside " + f"the 950-2150 MHz hardware range with LNB LO={lnb_lo} MHz.", + file=sys.stderr) + print(f"Check your LNB LO frequency.", file=sys.stderr) + sys.exit(1) + + print(f"QO-100 Narrowband Transponder Survey") + print(f" LNB LO: {lnb_lo} MHz") + print(f" RF range: {rf_start:.3f} - {rf_stop:.3f} MHz") + print(f" IF range: {if_start:.3f} - {if_stop:.3f} MHz") + print() + + # QO-100 NB uses very low symbol rates (1-33 ksps typical for DVB-S) + # The SkyWalker-1 minimum is 256 ksps, so we set a narrow range + sr_min = 256_000 + sr_max = 2_000_000 + sr_step = 100_000 + + cb = progress_callback(args.verbose) + + with SkyWalker1(verbose=args.verbose) as dev: + dev.ensure_booted() + # QO-100 is H-pol on most setups, high band for 10 GHz + dev.configure_lnb(pol="H", band="high", lnb_lo=lnb_lo) + + engine = SurveyEngine(dev, callback=cb) + catalog = engine.run_full_scan( + start_mhz=if_start, + stop_mhz=if_stop, + coarse_step=0.5, # 500 kHz steps for the narrow band + fine_step=0.1, # 100 kHz fine resolution + sr_min=sr_min, + sr_max=sr_max, + sr_step=sr_step, + ) + + if not args.verbose: + sys.stderr.write("\r" + " " * 80 + "\r") + sys.stderr.flush() + + catalog.name = "QO-100 Narrowband" + catalog.band = "high" + catalog.pol = "H" + catalog.lnb_lo_mhz = lnb_lo + catalog.notes = (f"QO-100 Es'hail-2 narrowband transponder. " + f"RF {rf_start}-{rf_stop} MHz, LNB LO {lnb_lo} MHz.") + + if args.output: + path = catalog.save(args.output) + else: + path = catalog.save(f"survey-qo100-nb-{time.strftime('%Y-%m-%d')}.json") + + print() + print(catalog.summary()) + print() + print(f"Saved to: {path}") + + +# -- Helpers -- + +def _catalog_to_csv(catalog: CarrierCatalog) -> str: + """Convert a catalog to CSV format.""" + buf = io.StringIO() + writer = csv.writer(buf) + writer.writerow([ + "freq_khz", "freq_mhz", "sr_sps", "modulation", "fec", + "power_db", "snr_db", "locked", "bw_mhz", "services", + "first_seen", "last_seen", "scan_count", + ]) + for c in sorted(catalog.carriers, key=lambda x: x.freq_khz): + writer.writerow([ + c.freq_khz, f"{c.freq_mhz:.3f}", c.sr_sps, + c.modulation, c.fec, + f"{c.power_db:.1f}", f"{c.snr_db:.1f}", + c.locked, f"{c.bw_mhz:.1f}", + "|".join(c.services), + c.first_seen, c.last_seen, c.scan_count, + ]) + return buf.getvalue() + + +# -- CLI -- + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Carrier survey tool for the Genpix SkyWalker-1", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +examples: + %(prog)s full-scan + %(prog)s full-scan --start 1100 --stop 1200 --output my-scan.json + %(prog)s quick-scan + %(prog)s diff survey-2026-02-14-low-V.json survey-2026-02-15-low-V.json + %(prog)s export survey-2026-02-15-low-V.json --format csv + %(prog)s view + %(prog)s qo100 --lnb-lo 9750 +""") + parser.add_argument('-v', '--verbose', action='store_true', + help="Verbose progress and debug output") + + sub = parser.add_subparsers(dest='command') + + # full-scan + p_full = sub.add_parser('full-scan', help="Full six-stage carrier survey") + p_full.add_argument('--start', type=float, default=950, + help="Start frequency in MHz (default: 950)") + p_full.add_argument('--stop', type=float, default=2150, + help="Stop frequency in MHz (default: 2150)") + p_full.add_argument('--coarse-step', type=float, default=5.0, + help="Coarse sweep step in MHz (default: 5.0)") + p_full.add_argument('--fine-step', type=float, default=1.0, + help="Fine sweep step in MHz (default: 1.0)") + p_full.add_argument('--sr-min', type=int, default=1_000_000, + help="Min symbol rate for blind scan in sps (default: 1000000)") + p_full.add_argument('--sr-max', type=int, default=30_000_000, + help="Max symbol rate for blind scan in sps (default: 30000000)") + p_full.add_argument('--sr-step', type=int, default=1_000_000, + help="Symbol rate step for blind scan in sps (default: 1000000)") + p_full.add_argument('--pol', choices=['H', 'V', 'L', 'R'], + help="LNB polarization") + p_full.add_argument('--band', choices=['low', 'high'], + help="LNB band (low/high)") + p_full.add_argument('--name', type=str, default="", + help="Survey name/label") + p_full.add_argument('--output', '-o', type=str, default=None, + help="Output filename (default: auto-generated)") + + # quick-scan + p_quick = sub.add_parser('quick-scan', help="Quick sweep + peak detection") + p_quick.add_argument('--start', type=float, default=950, + help="Start frequency in MHz (default: 950)") + p_quick.add_argument('--stop', type=float, default=2150, + help="Stop frequency in MHz (default: 2150)") + p_quick.add_argument('--step', type=float, default=5.0, + help="Sweep step in MHz (default: 5.0)") + p_quick.add_argument('--pol', choices=['H', 'V', 'L', 'R'], + help="LNB polarization") + p_quick.add_argument('--band', choices=['low', 'high'], + help="LNB band (low/high)") + + # diff + p_diff = sub.add_parser('diff', help="Compare two survey catalogs") + p_diff.add_argument('file1', help="Older survey file") + p_diff.add_argument('file2', help="Newer survey file") + p_diff.add_argument('--output', '-o', type=str, default=None, + help="Save diff as JSON to this file") + + # export + p_export = sub.add_parser('export', help="Export survey to CSV/JSON/text") + p_export.add_argument('file', help="Survey file to export") + p_export.add_argument('--format', '-f', choices=['csv', 'json', 'text'], + default='text', help="Output format (default: text)") + p_export.add_argument('--output', '-o', type=str, default=None, + help="Output file (default: stdout)") + + # view + p_view = sub.add_parser('view', help="View a survey (latest if no file given)") + p_view.add_argument('file', nargs='?', default=None, + help="Survey file to view (default: latest)") + + # qo100 + p_qo100 = sub.add_parser('qo100', + help="QO-100 narrowband transponder survey") + p_qo100.add_argument('--lnb-lo', type=float, required=True, + help="LNB local oscillator frequency in MHz " + "(e.g., 9750 for universal LNB low band)") + p_qo100.add_argument('--output', '-o', type=str, default=None, + help="Output filename (default: auto-generated)") + + return parser + + +def main(): + parser = build_parser() + args = parser.parse_args() + + if not args.command: + parser.print_help() + sys.exit(1) + + dispatch = { + 'full-scan': cmd_full_scan, + 'quick-scan': cmd_quick_scan, + 'diff': cmd_diff, + 'export': cmd_export, + 'view': cmd_view, + 'qo100': cmd_qo100, + } + + handler = dispatch.get(args.command) + if handler is None: + parser.print_help() + sys.exit(1) + + handler(args) + + +if __name__ == '__main__': + main() diff --git a/tools/survey_engine.py b/tools/survey_engine.py index a2dc853..9fd94b9 100644 --- a/tools/survey_engine.py +++ b/tools/survey_engine.py @@ -1,440 +1,440 @@ -#!/usr/bin/env python3 -""" -Automated carrier survey engine -- six-stage pipeline. - -Orchestrates spectrum sweep, peak detection, blind scan, and TS -sampling to build a complete carrier catalog from the IF band. -""" - -import sys -import time -import io - -from skywalker_lib import SkyWalker1, MODULATIONS, MOD_FEC_GROUP, FEC_RATES -from signal_analysis import ( - adaptive_noise_floor, - detect_peaks_enhanced, - estimate_carrier_bw, - classify_carrier, -) -from carrier_catalog import CarrierEntry, CarrierCatalog -from ts_analyze import TSReader, PSIParser, parse_pat, parse_pmt, parse_sdt - - -# Modulation index table for reverse lookup -_MOD_BY_INDEX = {} -for name, (idx, desc) in MODULATIONS.items(): - _MOD_BY_INDEX[idx] = name - - -class SurveyEngine: - """ - Six-stage carrier survey pipeline: - - 1. Coarse sweep -- full IF range at configurable step size - 2. Peak detection -- adaptive noise floor, peak merging - 3. Fine sweep -- +/-10 MHz around each peak at 1 MHz steps - 4. Blind scan -- try symbol rate range at each refined peak - 5. TS sample -- for locked carriers, short capture + PAT/PMT/SDT - 6. Catalog assembly -- aggregate everything into a CarrierCatalog - """ - - STAGE_COARSE = "coarse_sweep" - STAGE_PEAKS = "peak_detection" - STAGE_FINE = "fine_sweep" - STAGE_BLIND = "blind_scan" - STAGE_TS = "ts_sample" - STAGE_CATALOG = "catalog_assembly" - - def __init__(self, device: SkyWalker1, callback=None): - """ - device -- open SkyWalker1 instance - callback -- optional function(stage, progress_pct, message) - called at each major step for progress reporting - """ - self.dev = device - self.callback = callback - - def _report(self, stage: str, pct: float, msg: str) -> None: - if self.callback: - self.callback(stage, pct, msg) - - # ------------------------------------------------------------------ - # Public entry points - # ------------------------------------------------------------------ - - def run_full_scan(self, start_mhz: float = 950, stop_mhz: float = 2150, - coarse_step: float = 5.0, fine_step: float = 1.0, - sr_min: int = 1_000_000, sr_max: int = 30_000_000, - sr_step: int = 1_000_000, - ts_capture_secs: float = 3.0) -> CarrierCatalog: - """ - Run all six stages and return a populated CarrierCatalog. - """ - # Stage 1: coarse sweep - self._report(self.STAGE_COARSE, 0, "Starting coarse sweep") - freqs, powers = self._coarse_sweep(start_mhz, stop_mhz, coarse_step) - self._report(self.STAGE_COARSE, 100, f"Coarse sweep done: {len(freqs)} points") - - # Stage 2: peak detection - self._report(self.STAGE_PEAKS, 0, "Detecting peaks") - peaks = self._detect_peaks(freqs, powers) - self._report(self.STAGE_PEAKS, 100, f"Found {len(peaks)} candidate peaks") - - if not peaks: - self._report(self.STAGE_CATALOG, 100, "No peaks found, empty catalog") - return self._assemble_catalog([], start_mhz, stop_mhz, - coarse_step, fine_step) - - # Stage 3: fine sweep around each peak - self._report(self.STAGE_FINE, 0, "Starting fine sweeps") - refined = self._fine_sweep(peaks, fine_step) - self._report(self.STAGE_FINE, 100, f"Refined to {len(refined)} carriers") - - # Stage 4: blind scan at each refined peak - self._report(self.STAGE_BLIND, 0, "Starting blind scan") - scanned = self._blind_scan_peaks(refined, sr_min, sr_max, sr_step) - self._report(self.STAGE_BLIND, 100, - f"Blind scan done: {sum(1 for s in scanned if s.get('locked'))} locked") - - # Stage 5: TS sample for locked carriers - locked = [s for s in scanned if s.get("locked")] - self._report(self.STAGE_TS, 0, f"Sampling TS from {len(locked)} locked carriers") - sampled = self._sample_ts(locked, capture_secs=ts_capture_secs) - self._report(self.STAGE_TS, 100, "TS sampling done") - - # Stage 6: assemble catalog - self._report(self.STAGE_CATALOG, 0, "Assembling catalog") - catalog = self._assemble_catalog(sampled, start_mhz, stop_mhz, - coarse_step, fine_step) - self._report(self.STAGE_CATALOG, 100, - f"Catalog ready: {len(catalog.carriers)} carriers") - return catalog - - def run_quick_scan(self, start_mhz: float = 950, stop_mhz: float = 2150, - step: float = 5.0) -> list: - """ - Quick scan: coarse sweep + peak detection only. - Returns list of peak dicts from detect_peaks_enhanced. - No blind scan or TS capture. - """ - self._report(self.STAGE_COARSE, 0, "Quick scan: coarse sweep") - freqs, powers = self._coarse_sweep(start_mhz, stop_mhz, step) - self._report(self.STAGE_COARSE, 100, f"Sweep done: {len(freqs)} points") - - self._report(self.STAGE_PEAKS, 0, "Quick scan: peak detection") - peaks = self._detect_peaks(freqs, powers) - self._report(self.STAGE_PEAKS, 100, f"Found {len(peaks)} peaks") - return peaks - - # ------------------------------------------------------------------ - # Internal stage methods - # ------------------------------------------------------------------ - - def _coarse_sweep(self, start_mhz: float, stop_mhz: float, - step: float) -> tuple: - """ - Stage 1: sweep the IF band and collect power measurements. - Returns (freqs_mhz[], powers_db[]). - """ - total_steps = int((stop_mhz - start_mhz) / step) + 1 - - def sweep_cb(freq, step_num, total, result): - pct = (step_num / max(total, 1)) * 100 - self._report(self.STAGE_COARSE, pct, - f"{freq:.0f} MHz {result['power_db']:+.1f} dB") - - freqs, powers, _ = self.dev.sweep_spectrum( - start_mhz, stop_mhz, step_mhz=step, - dwell_ms=15, callback=sweep_cb - ) - return freqs, powers - - def _detect_peaks(self, freqs: list, powers: list) -> list: - """ - Stage 2: enhanced peak detection with adaptive noise floor. - Returns list of peak dicts. - """ - noise_floor, mad = adaptive_noise_floor(powers) - self._report(self.STAGE_PEAKS, 50, - f"Noise floor: {noise_floor:.1f} dB, MAD: {mad:.2f} dB") - - peaks = detect_peaks_enhanced(freqs, powers, threshold_db=6.0) - - # Annotate each peak with classification - for p in peaks: - p["classification"] = classify_carrier(p["width_mhz"], p["power"]) - - return peaks - - def _fine_sweep(self, peaks: list, fine_step: float = 1.0) -> list: - """ - Stage 3: sweep +/-10 MHz around each peak at fine resolution. - Returns list of refined peak dicts with updated freq/power/width. - """ - refined = [] - for i, peak in enumerate(peaks): - pct = (i / max(len(peaks), 1)) * 100 - center = peak["freq"] - margin = max(peak["width_mhz"] * 1.5, 10.0) - fine_start = max(950.0, center - margin) - fine_stop = min(2150.0, center + margin) - - self._report(self.STAGE_FINE, pct, - f"Fine sweep {center:.0f} MHz ({fine_start:.0f}-{fine_stop:.0f})") - - freqs, powers, _ = self.dev.sweep_spectrum( - fine_start, fine_stop, step_mhz=fine_step, - dwell_ms=20 - ) - - # Re-detect peaks in the fine data - fine_peaks = detect_peaks_enhanced(freqs, powers, threshold_db=4.0) - if fine_peaks: - # Take the strongest peak from the fine sweep - best = max(fine_peaks, key=lambda p: p["power"]) - best["classification"] = classify_carrier( - best["width_mhz"], best["power"] - ) - refined.append(best) - else: - # Keep the coarse peak if fine sweep didn't improve it - refined.append(peak) - - return refined - - def _blind_scan_peaks(self, refined_peaks: list, - sr_min: int, sr_max: int, - sr_step: int) -> list: - """ - Stage 4: attempt blind scan at each refined peak frequency. - Returns list of result dicts, each with the peak info plus - blind scan results (locked, sr_sps, etc). - """ - results = [] - for i, peak in enumerate(refined_peaks): - pct = (i / max(len(refined_peaks), 1)) * 100 - freq_khz = int(peak["freq"] * 1000) - - self._report(self.STAGE_BLIND, pct, - f"Blind scan {peak['freq']:.1f} MHz") - - # Use classification to narrow SR range if possible - cls = peak.get("classification", {}) - sr_range = cls.get("estimated_sr_range", (sr_min, sr_max)) - scan_min = max(sr_min, sr_range[0]) - scan_max = min(sr_max, sr_range[1]) - - result = { - "freq_mhz": peak["freq"], - "freq_khz": freq_khz, - "power_db": peak["power"], - "width_mhz": peak["width_mhz"], - "prominence_db": peak.get("prominence_db", 0), - "classification": cls, - "locked": False, - "sr_sps": 0, - "mod_index": -1, - "fec_index": -1, - } - - # Try adaptive blind scan first (firmware-assisted) - try: - lock = self.dev.adaptive_blind_scan( - freq_khz, scan_min, scan_max, sr_step - ) - if lock and lock.get("locked"): - result["locked"] = True - result["sr_sps"] = lock["sr_sps"] - result["freq_khz"] = lock.get("freq_khz", freq_khz) - # Read signal quality - time.sleep(0.1) - sig = self.dev.signal_monitor() - result["snr_db"] = sig.get("snr_db", 0) - result["agc1"] = sig.get("agc1", 0) - except Exception as e: - self._report(self.STAGE_BLIND, pct, - f"Blind scan error at {peak['freq']:.1f} MHz: {e}") - - results.append(result) - - return results - - def _sample_ts(self, locked_carriers: list, - capture_secs: float = 3.0) -> list: - """ - Stage 5: for each locked carrier, tune + arm + capture TS data, - then parse PAT/PMT/SDT for service information. - """ - results = [] - for i, carrier in enumerate(locked_carriers): - pct = (i / max(len(locked_carriers), 1)) * 100 - freq_khz = carrier["freq_khz"] - sr_sps = carrier["sr_sps"] - - self._report(self.STAGE_TS, pct, - f"Sampling {carrier['freq_mhz']:.1f} MHz " - f"SR={sr_sps / 1e6:.3f} Msps") - - carrier["services"] = [] - carrier["pat"] = None - carrier["pmt"] = {} - - if sr_sps <= 0: - results.append(carrier) - continue - - try: - # Tune with QPSK auto-FEC as a safe default - self.dev.tune(sr_sps, freq_khz, 0, 5) - time.sleep(0.3) - - # Verify lock - sig = self.dev.signal_monitor() - if not sig.get("locked"): - results.append(carrier) - continue - - carrier["snr_db"] = sig.get("snr_db", 0) - - # Arm and capture TS data - self.dev.arm_transfer(True) - ts_data = bytearray() - deadline = time.time() + capture_secs - - while time.time() < deadline: - chunk = self.dev.read_stream(timeout=500) - if chunk: - ts_data.extend(chunk) - - self.dev.arm_transfer(False) - - # Parse the captured TS - if ts_data: - services = _parse_ts_services(bytes(ts_data)) - carrier["services"] = services.get("service_names", []) - carrier["pat"] = services.get("pat") - carrier["pmt"] = services.get("pmts", {}) - carrier["sdt"] = services.get("sdt") - - except Exception as e: - self._report(self.STAGE_TS, pct, - f"TS capture error at {carrier['freq_mhz']:.1f} MHz: {e}") - try: - self.dev.arm_transfer(False) - except Exception: - pass - - results.append(carrier) - - return results - - def _assemble_catalog(self, all_results: list, - start_mhz: float = 950, - stop_mhz: float = 2150, - coarse_step: float = 5.0, - fine_step: float = 1.0) -> CarrierCatalog: - """ - Stage 6: build a CarrierCatalog from the collected results. - """ - catalog = CarrierCatalog() - catalog.sweep_params = { - "start_mhz": start_mhz, - "stop_mhz": stop_mhz, - "coarse_step_mhz": coarse_step, - "fine_step_mhz": fine_step, - } - - for r in all_results: - mod_name = "" - if r.get("mod_index", -1) >= 0: - mod_name = _MOD_BY_INDEX.get(r["mod_index"], "") - - entry = CarrierEntry( - freq_khz=r.get("freq_khz", int(r.get("freq_mhz", 0) * 1000)), - sr_sps=r.get("sr_sps", 0), - modulation=mod_name, - fec="", - power_db=r.get("power_db", 0), - snr_db=r.get("snr_db", 0), - locked=r.get("locked", False), - services=r.get("services", []), - bw_mhz=r.get("width_mhz", 0), - classification=r.get("classification", {}), - ) - catalog.add_carrier(entry) - - return catalog - - -def _parse_ts_services(ts_data: bytes) -> dict: - """ - Parse PAT, PMT, and SDT from a chunk of TS data. - - Returns dict with: - pat - parsed PAT or None - pmts - {pmt_pid: parsed PMT} - sdt - parsed SDT or None - service_names - list of service name strings from SDT - """ - result = { - "pat": None, - "pmts": {}, - "sdt": None, - "service_names": [], - } - - source = io.BytesIO(ts_data) - reader = TSReader(source) - psi_pat = PSIParser() - psi_pmt = PSIParser() - psi_sdt = PSIParser() - - pat = None - pmt_pids = set() - pmts_found = {} - - try: - for pkt in reader.iter_packets(max_packets=50000): - # PAT on PID 0x0000 - if pkt.pid == 0x0000 and pat is None: - section = psi_pat.feed(pkt) - if section is not None: - pat = parse_pat(section) - if pat: - result["pat"] = pat - for prog, pid in pat["programs"].items(): - if prog != 0: - pmt_pids.add(pid) - - # PMT sections - if pkt.pid in pmt_pids and pkt.pid not in pmts_found: - section = psi_pmt.feed(pkt) - if section is not None: - pmt = parse_pmt(section) - if pmt: - pmts_found[pkt.pid] = pmt - - # SDT on PID 0x0011 - if pkt.pid == 0x0011 and result["sdt"] is None: - section = psi_sdt.feed(pkt) - if section is not None: - sdt = parse_sdt(section) - if sdt: - result["sdt"] = sdt - for svc in sdt.get("services", []): - name = svc.get("service_name", "") - if name: - result["service_names"].append(name) - - # Stop early once we have everything - if (pat is not None - and len(pmts_found) >= len(pmt_pids) - and result["sdt"] is not None): - break - - except Exception: - pass - - result["pmts"] = pmts_found - return result +#!/usr/bin/env python3 +""" +Automated carrier survey engine -- six-stage pipeline. + +Orchestrates spectrum sweep, peak detection, blind scan, and TS +sampling to build a complete carrier catalog from the IF band. +""" + +import sys +import time +import io + +from skywalker_lib import SkyWalker1, MODULATIONS, MOD_FEC_GROUP, FEC_RATES +from signal_analysis import ( + adaptive_noise_floor, + detect_peaks_enhanced, + estimate_carrier_bw, + classify_carrier, +) +from carrier_catalog import CarrierEntry, CarrierCatalog +from ts_analyze import TSReader, PSIParser, parse_pat, parse_pmt, parse_sdt + + +# Modulation index table for reverse lookup +_MOD_BY_INDEX = {} +for name, (idx, desc) in MODULATIONS.items(): + _MOD_BY_INDEX[idx] = name + + +class SurveyEngine: + """ + Six-stage carrier survey pipeline: + + 1. Coarse sweep -- full IF range at configurable step size + 2. Peak detection -- adaptive noise floor, peak merging + 3. Fine sweep -- +/-10 MHz around each peak at 1 MHz steps + 4. Blind scan -- try symbol rate range at each refined peak + 5. TS sample -- for locked carriers, short capture + PAT/PMT/SDT + 6. Catalog assembly -- aggregate everything into a CarrierCatalog + """ + + STAGE_COARSE = "coarse_sweep" + STAGE_PEAKS = "peak_detection" + STAGE_FINE = "fine_sweep" + STAGE_BLIND = "blind_scan" + STAGE_TS = "ts_sample" + STAGE_CATALOG = "catalog_assembly" + + def __init__(self, device: SkyWalker1, callback=None): + """ + device -- open SkyWalker1 instance + callback -- optional function(stage, progress_pct, message) + called at each major step for progress reporting + """ + self.dev = device + self.callback = callback + + def _report(self, stage: str, pct: float, msg: str) -> None: + if self.callback: + self.callback(stage, pct, msg) + + # ------------------------------------------------------------------ + # Public entry points + # ------------------------------------------------------------------ + + def run_full_scan(self, start_mhz: float = 950, stop_mhz: float = 2150, + coarse_step: float = 5.0, fine_step: float = 1.0, + sr_min: int = 1_000_000, sr_max: int = 30_000_000, + sr_step: int = 1_000_000, + ts_capture_secs: float = 3.0) -> CarrierCatalog: + """ + Run all six stages and return a populated CarrierCatalog. + """ + # Stage 1: coarse sweep + self._report(self.STAGE_COARSE, 0, "Starting coarse sweep") + freqs, powers = self._coarse_sweep(start_mhz, stop_mhz, coarse_step) + self._report(self.STAGE_COARSE, 100, f"Coarse sweep done: {len(freqs)} points") + + # Stage 2: peak detection + self._report(self.STAGE_PEAKS, 0, "Detecting peaks") + peaks = self._detect_peaks(freqs, powers) + self._report(self.STAGE_PEAKS, 100, f"Found {len(peaks)} candidate peaks") + + if not peaks: + self._report(self.STAGE_CATALOG, 100, "No peaks found, empty catalog") + return self._assemble_catalog([], start_mhz, stop_mhz, + coarse_step, fine_step) + + # Stage 3: fine sweep around each peak + self._report(self.STAGE_FINE, 0, "Starting fine sweeps") + refined = self._fine_sweep(peaks, fine_step) + self._report(self.STAGE_FINE, 100, f"Refined to {len(refined)} carriers") + + # Stage 4: blind scan at each refined peak + self._report(self.STAGE_BLIND, 0, "Starting blind scan") + scanned = self._blind_scan_peaks(refined, sr_min, sr_max, sr_step) + self._report(self.STAGE_BLIND, 100, + f"Blind scan done: {sum(1 for s in scanned if s.get('locked'))} locked") + + # Stage 5: TS sample for locked carriers + locked = [s for s in scanned if s.get("locked")] + self._report(self.STAGE_TS, 0, f"Sampling TS from {len(locked)} locked carriers") + sampled = self._sample_ts(locked, capture_secs=ts_capture_secs) + self._report(self.STAGE_TS, 100, "TS sampling done") + + # Stage 6: assemble catalog + self._report(self.STAGE_CATALOG, 0, "Assembling catalog") + catalog = self._assemble_catalog(sampled, start_mhz, stop_mhz, + coarse_step, fine_step) + self._report(self.STAGE_CATALOG, 100, + f"Catalog ready: {len(catalog.carriers)} carriers") + return catalog + + def run_quick_scan(self, start_mhz: float = 950, stop_mhz: float = 2150, + step: float = 5.0) -> list: + """ + Quick scan: coarse sweep + peak detection only. + Returns list of peak dicts from detect_peaks_enhanced. + No blind scan or TS capture. + """ + self._report(self.STAGE_COARSE, 0, "Quick scan: coarse sweep") + freqs, powers = self._coarse_sweep(start_mhz, stop_mhz, step) + self._report(self.STAGE_COARSE, 100, f"Sweep done: {len(freqs)} points") + + self._report(self.STAGE_PEAKS, 0, "Quick scan: peak detection") + peaks = self._detect_peaks(freqs, powers) + self._report(self.STAGE_PEAKS, 100, f"Found {len(peaks)} peaks") + return peaks + + # ------------------------------------------------------------------ + # Internal stage methods + # ------------------------------------------------------------------ + + def _coarse_sweep(self, start_mhz: float, stop_mhz: float, + step: float) -> tuple: + """ + Stage 1: sweep the IF band and collect power measurements. + Returns (freqs_mhz[], powers_db[]). + """ + total_steps = int((stop_mhz - start_mhz) / step) + 1 + + def sweep_cb(freq, step_num, total, result): + pct = (step_num / max(total, 1)) * 100 + self._report(self.STAGE_COARSE, pct, + f"{freq:.0f} MHz {result['power_db']:+.1f} dB") + + freqs, powers, _ = self.dev.sweep_spectrum( + start_mhz, stop_mhz, step_mhz=step, + dwell_ms=15, callback=sweep_cb + ) + return freqs, powers + + def _detect_peaks(self, freqs: list, powers: list) -> list: + """ + Stage 2: enhanced peak detection with adaptive noise floor. + Returns list of peak dicts. + """ + noise_floor, mad = adaptive_noise_floor(powers) + self._report(self.STAGE_PEAKS, 50, + f"Noise floor: {noise_floor:.1f} dB, MAD: {mad:.2f} dB") + + peaks = detect_peaks_enhanced(freqs, powers, threshold_db=6.0) + + # Annotate each peak with classification + for p in peaks: + p["classification"] = classify_carrier(p["width_mhz"], p["power"]) + + return peaks + + def _fine_sweep(self, peaks: list, fine_step: float = 1.0) -> list: + """ + Stage 3: sweep +/-10 MHz around each peak at fine resolution. + Returns list of refined peak dicts with updated freq/power/width. + """ + refined = [] + for i, peak in enumerate(peaks): + pct = (i / max(len(peaks), 1)) * 100 + center = peak["freq"] + margin = max(peak["width_mhz"] * 1.5, 10.0) + fine_start = max(950.0, center - margin) + fine_stop = min(2150.0, center + margin) + + self._report(self.STAGE_FINE, pct, + f"Fine sweep {center:.0f} MHz ({fine_start:.0f}-{fine_stop:.0f})") + + freqs, powers, _ = self.dev.sweep_spectrum( + fine_start, fine_stop, step_mhz=fine_step, + dwell_ms=20 + ) + + # Re-detect peaks in the fine data + fine_peaks = detect_peaks_enhanced(freqs, powers, threshold_db=4.0) + if fine_peaks: + # Take the strongest peak from the fine sweep + best = max(fine_peaks, key=lambda p: p["power"]) + best["classification"] = classify_carrier( + best["width_mhz"], best["power"] + ) + refined.append(best) + else: + # Keep the coarse peak if fine sweep didn't improve it + refined.append(peak) + + return refined + + def _blind_scan_peaks(self, refined_peaks: list, + sr_min: int, sr_max: int, + sr_step: int) -> list: + """ + Stage 4: attempt blind scan at each refined peak frequency. + Returns list of result dicts, each with the peak info plus + blind scan results (locked, sr_sps, etc). + """ + results = [] + for i, peak in enumerate(refined_peaks): + pct = (i / max(len(refined_peaks), 1)) * 100 + freq_khz = int(peak["freq"] * 1000) + + self._report(self.STAGE_BLIND, pct, + f"Blind scan {peak['freq']:.1f} MHz") + + # Use classification to narrow SR range if possible + cls = peak.get("classification", {}) + sr_range = cls.get("estimated_sr_range", (sr_min, sr_max)) + scan_min = max(sr_min, sr_range[0]) + scan_max = min(sr_max, sr_range[1]) + + result = { + "freq_mhz": peak["freq"], + "freq_khz": freq_khz, + "power_db": peak["power"], + "width_mhz": peak["width_mhz"], + "prominence_db": peak.get("prominence_db", 0), + "classification": cls, + "locked": False, + "sr_sps": 0, + "mod_index": -1, + "fec_index": -1, + } + + # Try adaptive blind scan first (firmware-assisted) + try: + lock = self.dev.adaptive_blind_scan( + freq_khz, scan_min, scan_max, sr_step + ) + if lock and lock.get("locked"): + result["locked"] = True + result["sr_sps"] = lock["sr_sps"] + result["freq_khz"] = lock.get("freq_khz", freq_khz) + # Read signal quality + time.sleep(0.1) + sig = self.dev.signal_monitor() + result["snr_db"] = sig.get("snr_db", 0) + result["agc1"] = sig.get("agc1", 0) + except Exception as e: + self._report(self.STAGE_BLIND, pct, + f"Blind scan error at {peak['freq']:.1f} MHz: {e}") + + results.append(result) + + return results + + def _sample_ts(self, locked_carriers: list, + capture_secs: float = 3.0) -> list: + """ + Stage 5: for each locked carrier, tune + arm + capture TS data, + then parse PAT/PMT/SDT for service information. + """ + results = [] + for i, carrier in enumerate(locked_carriers): + pct = (i / max(len(locked_carriers), 1)) * 100 + freq_khz = carrier["freq_khz"] + sr_sps = carrier["sr_sps"] + + self._report(self.STAGE_TS, pct, + f"Sampling {carrier['freq_mhz']:.1f} MHz " + f"SR={sr_sps / 1e6:.3f} Msps") + + carrier["services"] = [] + carrier["pat"] = None + carrier["pmt"] = {} + + if sr_sps <= 0: + results.append(carrier) + continue + + try: + # Tune with QPSK auto-FEC as a safe default + self.dev.tune(sr_sps, freq_khz, 0, 5) + time.sleep(0.3) + + # Verify lock + sig = self.dev.signal_monitor() + if not sig.get("locked"): + results.append(carrier) + continue + + carrier["snr_db"] = sig.get("snr_db", 0) + + # Arm and capture TS data + self.dev.arm_transfer(True) + ts_data = bytearray() + deadline = time.time() + capture_secs + + while time.time() < deadline: + chunk = self.dev.read_stream(timeout=500) + if chunk: + ts_data.extend(chunk) + + self.dev.arm_transfer(False) + + # Parse the captured TS + if ts_data: + services = _parse_ts_services(bytes(ts_data)) + carrier["services"] = services.get("service_names", []) + carrier["pat"] = services.get("pat") + carrier["pmt"] = services.get("pmts", {}) + carrier["sdt"] = services.get("sdt") + + except Exception as e: + self._report(self.STAGE_TS, pct, + f"TS capture error at {carrier['freq_mhz']:.1f} MHz: {e}") + try: + self.dev.arm_transfer(False) + except Exception: + pass + + results.append(carrier) + + return results + + def _assemble_catalog(self, all_results: list, + start_mhz: float = 950, + stop_mhz: float = 2150, + coarse_step: float = 5.0, + fine_step: float = 1.0) -> CarrierCatalog: + """ + Stage 6: build a CarrierCatalog from the collected results. + """ + catalog = CarrierCatalog() + catalog.sweep_params = { + "start_mhz": start_mhz, + "stop_mhz": stop_mhz, + "coarse_step_mhz": coarse_step, + "fine_step_mhz": fine_step, + } + + for r in all_results: + mod_name = "" + if r.get("mod_index", -1) >= 0: + mod_name = _MOD_BY_INDEX.get(r["mod_index"], "") + + entry = CarrierEntry( + freq_khz=r.get("freq_khz", int(r.get("freq_mhz", 0) * 1000)), + sr_sps=r.get("sr_sps", 0), + modulation=mod_name, + fec="", + power_db=r.get("power_db", 0), + snr_db=r.get("snr_db", 0), + locked=r.get("locked", False), + services=r.get("services", []), + bw_mhz=r.get("width_mhz", 0), + classification=r.get("classification", {}), + ) + catalog.add_carrier(entry) + + return catalog + + +def _parse_ts_services(ts_data: bytes) -> dict: + """ + Parse PAT, PMT, and SDT from a chunk of TS data. + + Returns dict with: + pat - parsed PAT or None + pmts - {pmt_pid: parsed PMT} + sdt - parsed SDT or None + service_names - list of service name strings from SDT + """ + result = { + "pat": None, + "pmts": {}, + "sdt": None, + "service_names": [], + } + + source = io.BytesIO(ts_data) + reader = TSReader(source) + psi_pat = PSIParser() + psi_pmt = PSIParser() + psi_sdt = PSIParser() + + pat = None + pmt_pids = set() + pmts_found = {} + + try: + for pkt in reader.iter_packets(max_packets=50000): + # PAT on PID 0x0000 + if pkt.pid == 0x0000 and pat is None: + section = psi_pat.feed(pkt) + if section is not None: + pat = parse_pat(section) + if pat: + result["pat"] = pat + for prog, pid in pat["programs"].items(): + if prog != 0: + pmt_pids.add(pid) + + # PMT sections + if pkt.pid in pmt_pids and pkt.pid not in pmts_found: + section = psi_pmt.feed(pkt) + if section is not None: + pmt = parse_pmt(section) + if pmt: + pmts_found[pkt.pid] = pmt + + # SDT on PID 0x0011 + if pkt.pid == 0x0011 and result["sdt"] is None: + section = psi_sdt.feed(pkt) + if section is not None: + sdt = parse_sdt(section) + if sdt: + result["sdt"] = sdt + for svc in sdt.get("services", []): + name = svc.get("service_name", "") + if name: + result["service_names"].append(name) + + # Stop early once we have everything + if (pat is not None + and len(pmts_found) >= len(pmt_pids) + and result["sdt"] is not None): + break + + except Exception: + pass + + result["pmts"] = pmts_found + return result diff --git a/tools/test_boot.py b/tools/test_boot.py index 04f1e0d..758629b 100644 --- a/tools/test_boot.py +++ b/tools/test_boot.py @@ -1,171 +1,171 @@ -#!/usr/bin/env python3 -"""Test BOOT_8PSK on SkyWalker-1 with custom firmware v3.01.0""" - -import usb.core -import usb.util -import sys -import time - -def find_device(): - dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) - if not dev: - print("Device not found!") - sys.exit(1) - return dev - -def setup_device(dev): - """Detach kernel driver and set configuration.""" - try: - if dev.is_kernel_driver_active(0): - dev.detach_kernel_driver(0) - print("Detached kernel driver from interface 0") - except Exception as e: - print(f"Driver detach note: {e}") - - try: - dev.set_configuration() - except usb.core.USBError: - # Already configured, that's fine - pass - -def main(): - dev = find_device() - setup_device(dev) - - # GET_FW_VERS (0x92) - print("=" * 50) - ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6) - major, minor, patch = ret[2], ret[1], ret[0] - day, month, year = ret[3], ret[4], ret[5] + 2000 - print(f"Firmware: v{major}.{minor:02d}.{patch} ({year}-{month:02d}-{day:02d})") - - # GET_8PSK_CONFIG (0x80) - ret = dev.ctrl_transfer(0xC0, 0x80, 0, 0, 1) - print(f"Config before boot: 0x{ret[0]:02X}") - - # BOOT_8PSK (0x89) with wValue=1 - print() - print("=" * 50) - print("Sending BOOT_8PSK(1)...") - print(" (This triggers: P0.5 reset, power on, 3-block register init)") - print() - - try: - ret = dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000) - except usb.core.USBError as e: - print(f"BOOT_8PSK USB error: {e}") - print("The device may have timed out during init.") - print("Trying to read config status anyway...") - try: - ret = dev.ctrl_transfer(0xC0, 0x80, 0, 0, 1) - print(f"Config after attempted boot: 0x{ret[0]:02X}") - except: - print("Device not responding. May need power cycle.") - sys.exit(1) - - status = ret[0] - stage = ret[1] if len(ret) > 1 else 0 - stage_names = { - 0: "NOT_STARTED", 1: "GPIO_SETUP", 2: "PWR_SETTLED", - 3: "I2C_PROBE", 4: "INIT_BLK0", 5: "INIT_BLK1", - 6: "INIT_BLK2", 0xFF: "COMPLETE" - } - flags = [] - if status & 0x01: flags.append("STARTED") - if status & 0x02: flags.append("FW_LOADED") - if status & 0x04: flags.append("INTERSIL") - if status & 0x08: flags.append("DVB_MODE") - if status & 0x10: flags.append("22KHZ") - if status & 0x20: flags.append("SEL18V") - if status & 0x40: flags.append("DC_TUNED") - if status & 0x80: flags.append("ARMED") - print(f"BOOT_8PSK response: 0x{status:02X} [{' | '.join(flags) if flags else 'none'}]") - print(f"Boot stage: 0x{stage:02X} [{stage_names.get(stage, 'UNKNOWN')}]") - - if status & 0x03 == 0x03: - print() - print("*** BCM4500 BOOT SUCCESS! ***") - print() - - # Read direct I2C registers - print("BCM4500 direct registers (via I2C_RAW_READ 0xB5):") - for reg in [0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8]: - try: - r = dev.ctrl_transfer(0xC0, 0xB5, 0x08, reg, 1) - print(f" Reg 0x{reg:02X} = 0x{r[0]:02X}") - except Exception as e: - print(f" Reg 0x{reg:02X}: ERROR {e}") - - # Read indirect registers through our protocol - print() - print("BCM4500 indirect registers (via RAW_DEMOD_READ 0xB1):") - for page in range(16): - try: - r = dev.ctrl_transfer(0xC0, 0xB1, page, 0, 1) - print(f" Page 0x{page:02X} = 0x{r[0]:02X}") - except Exception as e: - print(f" Page 0x{page:02X}: ERROR {e}") - - # I2C diagnostic - print() - print("I2C diagnostic (0xB6) for page 0x00:") - try: - r = dev.ctrl_transfer(0xC0, 0xB6, 0x00, 0, 8) - labels = ["wr_A6", "rb_A6", "wr_A8", "rb_A8", - "rb_A7", "fin_A6", "fin_A7", "fin_A8"] - for i, lab in enumerate(labels): - print(f" {lab}: 0x{r[i]:02X}") - except Exception as e: - print(f" ERROR: {e}") - - # Signal strength - print() - try: - r = dev.ctrl_transfer(0xC0, 0x87, 0, 0, 6) - print(f"Signal strength: {' '.join(f'{b:02X}' for b in r)}") - except Exception as e: - print(f"Signal strength ERROR: {e}") - - # Signal lock - try: - r = dev.ctrl_transfer(0xC0, 0x90, 0, 0, 1) - print(f"Signal lock: 0x{r[0]:02X}") - except Exception as e: - print(f"Signal lock ERROR: {e}") - - else: - print() - print("*** BOOT FAILED ***") - print() - - # I2C bus scan - print("I2C bus scan:") - try: - r = dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16) - addrs = [] - for bi in range(16): - for bit in range(8): - if r[bi] & (1 << bit): - addrs.append(bi * 8 + bit) - if addrs: - print(f" Found devices at: {[f'0x{a:02X}' for a in addrs]}") - else: - print(" No I2C devices found!") - except Exception as e: - print(f" Scan error: {e}") - - # Try raw I2C reads anyway - print() - print("Raw I2C reads to BCM4500 (0x08):") - for reg in [0xA2, 0xA4, 0xA6, 0xA7, 0xA8]: - try: - r = dev.ctrl_transfer(0xC0, 0xB5, 0x08, reg, 1) - print(f" Reg 0x{reg:02X} = 0x{r[0]:02X}") - except Exception as e: - print(f" Reg 0x{reg:02X}: ERROR {e}") - - print() - print("=" * 50) - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +"""Test BOOT_8PSK on SkyWalker-1 with custom firmware v3.01.0""" + +import usb.core +import usb.util +import sys +import time + +def find_device(): + dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) + if not dev: + print("Device not found!") + sys.exit(1) + return dev + +def setup_device(dev): + """Detach kernel driver and set configuration.""" + try: + if dev.is_kernel_driver_active(0): + dev.detach_kernel_driver(0) + print("Detached kernel driver from interface 0") + except Exception as e: + print(f"Driver detach note: {e}") + + try: + dev.set_configuration() + except usb.core.USBError: + # Already configured, that's fine + pass + +def main(): + dev = find_device() + setup_device(dev) + + # GET_FW_VERS (0x92) + print("=" * 50) + ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6) + major, minor, patch = ret[2], ret[1], ret[0] + day, month, year = ret[3], ret[4], ret[5] + 2000 + print(f"Firmware: v{major}.{minor:02d}.{patch} ({year}-{month:02d}-{day:02d})") + + # GET_8PSK_CONFIG (0x80) + ret = dev.ctrl_transfer(0xC0, 0x80, 0, 0, 1) + print(f"Config before boot: 0x{ret[0]:02X}") + + # BOOT_8PSK (0x89) with wValue=1 + print() + print("=" * 50) + print("Sending BOOT_8PSK(1)...") + print(" (This triggers: P0.5 reset, power on, 3-block register init)") + print() + + try: + ret = dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000) + except usb.core.USBError as e: + print(f"BOOT_8PSK USB error: {e}") + print("The device may have timed out during init.") + print("Trying to read config status anyway...") + try: + ret = dev.ctrl_transfer(0xC0, 0x80, 0, 0, 1) + print(f"Config after attempted boot: 0x{ret[0]:02X}") + except: + print("Device not responding. May need power cycle.") + sys.exit(1) + + status = ret[0] + stage = ret[1] if len(ret) > 1 else 0 + stage_names = { + 0: "NOT_STARTED", 1: "GPIO_SETUP", 2: "PWR_SETTLED", + 3: "I2C_PROBE", 4: "INIT_BLK0", 5: "INIT_BLK1", + 6: "INIT_BLK2", 0xFF: "COMPLETE" + } + flags = [] + if status & 0x01: flags.append("STARTED") + if status & 0x02: flags.append("FW_LOADED") + if status & 0x04: flags.append("INTERSIL") + if status & 0x08: flags.append("DVB_MODE") + if status & 0x10: flags.append("22KHZ") + if status & 0x20: flags.append("SEL18V") + if status & 0x40: flags.append("DC_TUNED") + if status & 0x80: flags.append("ARMED") + print(f"BOOT_8PSK response: 0x{status:02X} [{' | '.join(flags) if flags else 'none'}]") + print(f"Boot stage: 0x{stage:02X} [{stage_names.get(stage, 'UNKNOWN')}]") + + if status & 0x03 == 0x03: + print() + print("*** BCM4500 BOOT SUCCESS! ***") + print() + + # Read direct I2C registers + print("BCM4500 direct registers (via I2C_RAW_READ 0xB5):") + for reg in [0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8]: + try: + r = dev.ctrl_transfer(0xC0, 0xB5, 0x08, reg, 1) + print(f" Reg 0x{reg:02X} = 0x{r[0]:02X}") + except Exception as e: + print(f" Reg 0x{reg:02X}: ERROR {e}") + + # Read indirect registers through our protocol + print() + print("BCM4500 indirect registers (via RAW_DEMOD_READ 0xB1):") + for page in range(16): + try: + r = dev.ctrl_transfer(0xC0, 0xB1, page, 0, 1) + print(f" Page 0x{page:02X} = 0x{r[0]:02X}") + except Exception as e: + print(f" Page 0x{page:02X}: ERROR {e}") + + # I2C diagnostic + print() + print("I2C diagnostic (0xB6) for page 0x00:") + try: + r = dev.ctrl_transfer(0xC0, 0xB6, 0x00, 0, 8) + labels = ["wr_A6", "rb_A6", "wr_A8", "rb_A8", + "rb_A7", "fin_A6", "fin_A7", "fin_A8"] + for i, lab in enumerate(labels): + print(f" {lab}: 0x{r[i]:02X}") + except Exception as e: + print(f" ERROR: {e}") + + # Signal strength + print() + try: + r = dev.ctrl_transfer(0xC0, 0x87, 0, 0, 6) + print(f"Signal strength: {' '.join(f'{b:02X}' for b in r)}") + except Exception as e: + print(f"Signal strength ERROR: {e}") + + # Signal lock + try: + r = dev.ctrl_transfer(0xC0, 0x90, 0, 0, 1) + print(f"Signal lock: 0x{r[0]:02X}") + except Exception as e: + print(f"Signal lock ERROR: {e}") + + else: + print() + print("*** BOOT FAILED ***") + print() + + # I2C bus scan + print("I2C bus scan:") + try: + r = dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16) + addrs = [] + for bi in range(16): + for bit in range(8): + if r[bi] & (1 << bit): + addrs.append(bi * 8 + bit) + if addrs: + print(f" Found devices at: {[f'0x{a:02X}' for a in addrs]}") + else: + print(" No I2C devices found!") + except Exception as e: + print(f" Scan error: {e}") + + # Try raw I2C reads anyway + print() + print("Raw I2C reads to BCM4500 (0x08):") + for reg in [0xA2, 0xA4, 0xA6, 0xA7, 0xA8]: + try: + r = dev.ctrl_transfer(0xC0, 0xB5, 0x08, reg, 1) + print(f" Reg 0x{reg:02X} = 0x{r[0]:02X}") + except Exception as e: + print(f" Reg 0x{reg:02X}: ERROR {e}") + + print() + print("=" * 50) + +if __name__ == "__main__": + main() diff --git a/tools/test_boot_debug.py b/tools/test_boot_debug.py index 59dcbb4..ad1cfb2 100644 --- a/tools/test_boot_debug.py +++ b/tools/test_boot_debug.py @@ -1,127 +1,127 @@ -#!/usr/bin/env python3 -"""Incremental BOOT_8PSK debug tester for SkyWalker-1. - -Sends debug boot modes (wValue=0x80..0x83) one at a time to isolate -which stage of the BCM4500 boot sequence hangs the FX2 firmware. - -Usage: - sudo python3 test_boot_debug.py # run all debug stages - sudo python3 test_boot_debug.py 0x82 # run only stage 0x82 -""" - -import usb.core -import usb.util -import sys -import time - -BOOT_8PSK = 0x89 - -def find_device(): - dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) - if not dev: - print("Device not found!") - sys.exit(1) - return dev - -def setup_device(dev): - try: - if dev.is_kernel_driver_active(0): - dev.detach_kernel_driver(0) - except Exception: - pass - try: - dev.set_configuration() - except usb.core.USBError: - pass - -def decode_stage(stage): - names = { - 0x00: "NOT_STARTED", - 0x01: "GPIO_SETUP", - 0x02: "PWR_SETTLED", - 0x03: "I2C_PROBE", - 0x04: "INIT_BLK0", - 0x05: "INIT_BLK1", - 0x06: "INIT_BLK2", - 0xA1: "DEBUG_GPIO_OK", - 0xA2: "DEBUG_PROBE_OK", - 0xA3: "DEBUG_BLK0_OK", - 0xE3: "DEBUG_PROBE_FAIL", - 0xE4: "DEBUG_BLK0_FAIL", - 0xFF: "COMPLETE", - } - return names.get(stage, f"UNKNOWN(0x{stage:02X})") - -def test_mode(dev, wval, label, timeout_ms=3000): - """Send a debug boot mode and read 3-byte response.""" - print(f"\n{'─' * 50}") - print(f" Testing wValue=0x{wval:02X}: {label}") - print(f"{'─' * 50}") - - t0 = time.monotonic() - try: - ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, wval, 0, 3, timeout=timeout_ms) - except usb.core.USBError as e: - elapsed = (time.monotonic() - t0) * 1000 - print(f" FAILED after {elapsed:.0f}ms: {e}") - # Try to see if device is still alive - try: - dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=1000) - print(" Device still responds to GET_FW_VERS") - except: - print(" Device is HUNG (no response to GET_FW_VERS)") - return None - elapsed = (time.monotonic() - t0) * 1000 - - status = ret[0] - stage = ret[1] if len(ret) > 1 else 0 - probe = ret[2] if len(ret) > 2 else 0 - - print(f" Response in {elapsed:.0f}ms:") - print(f" config_status: 0x{status:02X}") - print(f" boot_stage: 0x{stage:02X} [{decode_stage(stage)}]") - print(f" probe_byte: 0x{probe:02X}") - return ret - -def main(): - dev = find_device() - setup_device(dev) - - # Verify firmware is responding - try: - ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=2000) - major, minor, patch = ret[2], ret[1], ret[0] - print(f"Firmware: v{major}.{minor:02d}.{patch}") - except usb.core.USBError as e: - print(f"GET_FW_VERS failed: {e}") - print("Device may be hung. Try reloading firmware with fw_load.py.") - sys.exit(1) - - ret = dev.ctrl_transfer(0xC0, 0x80, 0, 0, 1) - print(f"Config: 0x{ret[0]:02X}") - - # Parse optional argument for single-stage testing - single_stage = None - if len(sys.argv) > 1: - single_stage = int(sys.argv[1], 0) - - stages = [ - (0x80, "No-op: return current state only"), - (0x81, "GPIO setup + power + delays (no I2C)"), - (0x82, "GPIO + I2C bus reset + BCM4500 probe read"), - (0x83, "GPIO + I2C probe + write init block 0"), - ] - - for wval, label in stages: - if single_stage is not None and wval != single_stage: - continue - result = test_mode(dev, wval, label) - if result is None: - print("\n*** STOPPING: device not responding ***") - break - - print(f"\n{'=' * 50}") - print("Debug complete.") - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +"""Incremental BOOT_8PSK debug tester for SkyWalker-1. + +Sends debug boot modes (wValue=0x80..0x83) one at a time to isolate +which stage of the BCM4500 boot sequence hangs the FX2 firmware. + +Usage: + sudo python3 test_boot_debug.py # run all debug stages + sudo python3 test_boot_debug.py 0x82 # run only stage 0x82 +""" + +import usb.core +import usb.util +import sys +import time + +BOOT_8PSK = 0x89 + +def find_device(): + dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) + if not dev: + print("Device not found!") + sys.exit(1) + return dev + +def setup_device(dev): + try: + if dev.is_kernel_driver_active(0): + dev.detach_kernel_driver(0) + except Exception: + pass + try: + dev.set_configuration() + except usb.core.USBError: + pass + +def decode_stage(stage): + names = { + 0x00: "NOT_STARTED", + 0x01: "GPIO_SETUP", + 0x02: "PWR_SETTLED", + 0x03: "I2C_PROBE", + 0x04: "INIT_BLK0", + 0x05: "INIT_BLK1", + 0x06: "INIT_BLK2", + 0xA1: "DEBUG_GPIO_OK", + 0xA2: "DEBUG_PROBE_OK", + 0xA3: "DEBUG_BLK0_OK", + 0xE3: "DEBUG_PROBE_FAIL", + 0xE4: "DEBUG_BLK0_FAIL", + 0xFF: "COMPLETE", + } + return names.get(stage, f"UNKNOWN(0x{stage:02X})") + +def test_mode(dev, wval, label, timeout_ms=3000): + """Send a debug boot mode and read 3-byte response.""" + print(f"\n{'─' * 50}") + print(f" Testing wValue=0x{wval:02X}: {label}") + print(f"{'─' * 50}") + + t0 = time.monotonic() + try: + ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, wval, 0, 3, timeout=timeout_ms) + except usb.core.USBError as e: + elapsed = (time.monotonic() - t0) * 1000 + print(f" FAILED after {elapsed:.0f}ms: {e}") + # Try to see if device is still alive + try: + dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=1000) + print(" Device still responds to GET_FW_VERS") + except: + print(" Device is HUNG (no response to GET_FW_VERS)") + return None + elapsed = (time.monotonic() - t0) * 1000 + + status = ret[0] + stage = ret[1] if len(ret) > 1 else 0 + probe = ret[2] if len(ret) > 2 else 0 + + print(f" Response in {elapsed:.0f}ms:") + print(f" config_status: 0x{status:02X}") + print(f" boot_stage: 0x{stage:02X} [{decode_stage(stage)}]") + print(f" probe_byte: 0x{probe:02X}") + return ret + +def main(): + dev = find_device() + setup_device(dev) + + # Verify firmware is responding + try: + ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=2000) + major, minor, patch = ret[2], ret[1], ret[0] + print(f"Firmware: v{major}.{minor:02d}.{patch}") + except usb.core.USBError as e: + print(f"GET_FW_VERS failed: {e}") + print("Device may be hung. Try reloading firmware with fw_load.py.") + sys.exit(1) + + ret = dev.ctrl_transfer(0xC0, 0x80, 0, 0, 1) + print(f"Config: 0x{ret[0]:02X}") + + # Parse optional argument for single-stage testing + single_stage = None + if len(sys.argv) > 1: + single_stage = int(sys.argv[1], 0) + + stages = [ + (0x80, "No-op: return current state only"), + (0x81, "GPIO setup + power + delays (no I2C)"), + (0x82, "GPIO + I2C bus reset + BCM4500 probe read"), + (0x83, "GPIO + I2C probe + write init block 0"), + ] + + for wval, label in stages: + if single_stage is not None and wval != single_stage: + continue + result = test_mode(dev, wval, label) + if result is None: + print("\n*** STOPPING: device not responding ***") + break + + print(f"\n{'=' * 50}") + print("Debug complete.") + +if __name__ == "__main__": + main() diff --git a/tools/test_hamilton.py b/tools/test_hamilton.py index b0f1481..6c9862b 100644 --- a/tools/test_hamilton.py +++ b/tools/test_hamilton.py @@ -1,444 +1,444 @@ -#!/usr/bin/env python3 -""" -Hamilton Adversarial Test Suite — SkyWalker-1 v3.05.0 - -"What happens if the astronaut pushes the wrong button?" - -Tests operator error, invalid inputs, state machine violations, -boundary conditions, and rapid-fire stress to verify all safety -fixes from the Phase E Margaret Hamilton review. -""" - -import sys -import os -import time -import struct - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from skywalker_lib import SkyWalker1 -import usb.core - -ERR_NAMES = { - 0x00: 'OK', 0x01: 'I2C_TIMEOUT', 0x02: 'I2C_NAK', 0x03: 'BCM_TIMEOUT', - 0x04: 'BCM_NOT_READY', 0x05: 'BCM_VERIFY', 0x06: 'TUNE_FAIL', - 0x07: 'EP0_TIMEOUT', 0x08: 'GPIF_TIMEOUT', 0x09: 'EP2_TIMEOUT', - 0x0A: 'NOT_SUPPORTED', 0x0B: 'DISEQC_LEN', 0x0C: 'DISEQC_TIMER', - 0x0D: 'WDT_FIRED' -} - -passed = 0 -failed = 0 - - -def get_err(sw): - return sw.dev.ctrl_transfer(0xC0, 0xBC, 0, 0, 1)[0] - - -def err_name(code): - return ERR_NAMES.get(code, f'0x{code:02X}') - - -def device_alive(sw): - try: - fw = sw.get_fw_version() - return fw['version'] == '3.05.0' - except Exception: - return False - - -def test(sw, label, fn, expect_err=None, expect_no_hang=False): - """Run test, track error changes, verify device survives.""" - global passed, failed - - err_before = get_err(sw) - usb_err = None - try: - fn() - except usb.core.USBError as e: - usb_err = e - except Exception as e: - usb_err = e - - time.sleep(0.15) - - if not device_alive(sw): - print(f' [FAIL] {label}: DEVICE DIED!') - failed += 1 - return False - - err_after = get_err(sw) - changed = (err_after != err_before) - suffix = f' (USB: {usb_err})' if usb_err else '' - - if expect_err is not None: - if err_after == expect_err: - print(f' [PASS] {label}: err={err_name(expect_err)} as expected{suffix}') - passed += 1 - else: - print(f' [FAIL] {label}: expected {err_name(expect_err)}, got {err_name(err_after)}{suffix}') - failed += 1 - elif expect_no_hang: - print(f' [PASS] {label}: no hang, err={err_name(err_after)}{suffix}') - passed += 1 - else: - if changed: - print(f' [INFO] {label}: err changed {err_name(err_before)} -> {err_name(err_after)}{suffix}') - else: - print(f' [PASS] {label}: no new error{suffix}') - passed += 1 - - return True - - -def main(): - global passed, failed - - with SkyWalker1() as sw: - print('=' * 64) - print(' HAMILTON ADVERSARIAL TEST SUITE — SkyWalker-1 v3.05.0') - print(' "What if the astronaut pushes the wrong button?"') - print('=' * 64) - print() - - # Ensure clean starting state - sw.dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000) - sw.start_intersil(True) - time.sleep(0.5) - - # ============================================================ - print('=== CAT 1: DiSEqC Message Abuse ===') - print() - - test(sw, '1a. Tone burst B (M3: NOT_SUPPORTED)', - lambda: sw.send_diseqc_tone_burst(1), - expect_err=0x0A) - - test(sw, '1b. Tone burst wValue=0xFF', - lambda: sw.dev.ctrl_transfer(0x40, 0x8D, 0xFF, 0, None, timeout=3000), - expect_err=0x0A) - - test(sw, '1c. DiSEqC 2 bytes (too short)', - lambda: sw.dev.ctrl_transfer(0x40, 0x8D, 0xE0, 0, bytes([0xE0, 0x10]), timeout=3000), - expect_no_hang=True) - - test(sw, '1d. DiSEqC 8 bytes (too long)', - lambda: sw.dev.ctrl_transfer(0x40, 0x8D, 0xE0, 0, bytes([0xE0] * 8), timeout=3000), - expect_no_hang=True) - - test(sw, '1e. DiSEqC empty payload', - lambda: sw.dev.ctrl_transfer(0x40, 0x8D, 0xE0, 0, bytes([]), timeout=3000), - expect_no_hang=True) - - test(sw, '1f. Valid 4-byte DiSEqC (recovery)', - lambda: sw.send_diseqc_message(bytes([0xE0, 0x10, 0x38, 0xF0])), - expect_no_hang=True) - - test(sw, '1g. DiSEqC 1.2 motor halt (no motor)', - lambda: sw.send_diseqc_message(bytes([0xE0, 0x31, 0x60])), - expect_no_hang=True) - - test(sw, '1h. DiSEqC 1.2 drive east 255 steps', - lambda: sw.send_diseqc_message(bytes([0xE0, 0x31, 0x68, 0xFF])), - expect_no_hang=True) - - test(sw, '1i. DiSEqC 1.2 USALS GotoX (bogus angle)', - lambda: sw.send_diseqc_message(bytes([0xE0, 0x31, 0x6E, 0xFF, 0xFF])), - expect_no_hang=True) - - print() - - # ============================================================ - print('=== CAT 2: Tune Parameter Abuse ===') - print() - - test(sw, '2a. SR=0', - lambda: [sw.tune(0, 1000000, 0, 0), time.sleep(0.5)], - expect_no_hang=True) - - test(sw, '2b. SR=0xFFFFFFFF', - lambda: [sw.dev.ctrl_transfer(0x40, 0x86, 0, 0, - struct.pack('> Powering off BCM4500...') - sw.dev.ctrl_transfer(0xC0, 0x89, 0, 0, 3, timeout=5000) - time.sleep(0.5) - - test(sw, '4b. Tune with BCM off', - lambda: [sw.tune(20000000, 1000000, 0, 0), time.sleep(0.5)], - expect_err=0x04) # BCM_NOT_READY - - test(sw, '4c. Signal monitor with BCM off', - lambda: sw.dev.ctrl_transfer(0xC0, 0xB7, 0, 0, 8, timeout=3000), - expect_no_hang=True) - - test(sw, '4d. I2C bus scan with BCM off', - lambda: sw.dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16, timeout=5000), - expect_no_hang=True) - - test(sw, '4e. Hotplug rescan with BCM off', - lambda: sw.dev.ctrl_transfer(0xC0, 0xBE, 2, 0, 36, timeout=5000), - expect_no_hang=True) - - # 4f. Recovery - print(' >> Re-booting BCM4500...') - r = sw.dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000) - time.sleep(0.5) - cfg = sw.get_config() - if cfg & 0x03 == 0x03: - print(f' [PASS] 4f. Recovery: config=0x{cfg:02X} (STARTED|FW_LOADED)') - passed += 1 - else: - print(f' [FAIL] 4f. No recovery: config=0x{cfg:02X}') - failed += 1 - - # 4g. Arm/disarm rapid toggle - test(sw, '4g. Arm + immediate disarm', - lambda: [sw.arm_transfer(True), sw.arm_transfer(False)], - expect_no_hang=True) - - # 4h. Disarm when not armed - test(sw, '4h. Disarm when not armed', - lambda: sw.arm_transfer(False), - expect_no_hang=True) - - # 4i. Boot off/on/off/on rapid - test(sw, '4i. Rapid boot toggle (off-on-off-on)', - lambda: [ - sw.dev.ctrl_transfer(0xC0, 0x89, 0, 0, 3, timeout=5000), - time.sleep(0.2), - sw.dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000), - time.sleep(0.3), - sw.dev.ctrl_transfer(0xC0, 0x89, 0, 0, 3, timeout=5000), - time.sleep(0.2), - sw.dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000), - time.sleep(0.3), - ], - expect_no_hang=True) - - print() - - # ============================================================ - print('=== CAT 5: Boundary & Buffer Abuse ===') - print() - - # 5a. Request 0 bytes from GET_CONFIG - test(sw, '5a. GET_CONFIG request 0 bytes', - lambda: sw.dev.ctrl_transfer(0xC0, 0x80, 0, 0, 0, timeout=2000), - expect_no_hang=True) - - # 5b. Request 64 bytes from GET_CONFIG (returns 1) - test(sw, '5b. GET_CONFIG request 64 bytes', - lambda: sw.dev.ctrl_transfer(0xC0, 0x80, 0, 0, 64, timeout=2000), - expect_no_hang=True) - - # 5c. Request 64 bytes from GET_LAST_ERROR (returns 1) - test(sw, '5c. GET_LAST_ERROR request 64 bytes', - lambda: sw.dev.ctrl_transfer(0xC0, 0xBC, 0, 0, 64, timeout=2000), - expect_no_hang=True) - - # 5d. GET_FW_VERS request 1 byte (returns 6) - test(sw, '5d. GET_FW_VERS request 1 byte', - lambda: sw.dev.ctrl_transfer(0xC0, 0x92, 0, 0, 1, timeout=2000), - expect_no_hang=True) - - # 5e. GET_STREAM_DIAG request 1 byte (returns 12) - test(sw, '5e. GET_STREAM_DIAG request 1 byte', - lambda: sw.dev.ctrl_transfer(0xC0, 0xBD, 0, 0, 1, timeout=2000), - expect_no_hang=True) - - # 5f. GET_STREAM_DIAG with wval=0xFFFF (reset flag, but not 1) - test(sw, '5f. GET_STREAM_DIAG wval=0xFFFF', - lambda: sw.dev.ctrl_transfer(0xC0, 0xBD, 0xFFFF, 0, 12, timeout=2000), - expect_no_hang=True) - - # 5g. GET_HOTPLUG with wval=0xFFFF (unknown sub-command) - test(sw, '5g. GET_HOTPLUG wval=0xFFFF', - lambda: sw.dev.ctrl_transfer(0xC0, 0xBE, 0xFFFF, 0, 36, timeout=2000), - expect_no_hang=True) - - print() - - # ============================================================ - print('=== CAT 6: Rapid-Fire Stress ===') - print() - - t0 = time.time() - for i in range(200): - sw.get_config() - dt = time.time() - t0 - print(f' [PASS] 6a. 200 config reads: {dt * 1000:.0f}ms ({dt / 200 * 1000:.1f}ms/read)') - passed += 1 - - t0 = time.time() - for i in range(50): - get_err(sw) - dt = time.time() - t0 - print(f' [PASS] 6b. 50 error reads: {dt * 1000:.0f}ms ({dt / 50 * 1000:.1f}ms/read)') - passed += 1 - - t0 = time.time() - errs = 0 - for i in range(30): - try: - sw.signal_monitor() - except Exception: - errs += 1 - dt = time.time() - t0 - print(f' [PASS] 6c. 30 signal monitors: {dt * 1000:.0f}ms ({errs} errors)') - passed += 1 - - sw.start_intersil(True) - time.sleep(0.1) - t0 = time.time() - for i in range(40): - sw.set_lnb_voltage(i % 2 == 0) - dt = time.time() - t0 - print(f' [PASS] 6d. 40 voltage toggles: {dt * 1000:.0f}ms') - passed += 1 - - t0 = time.time() - for i in range(10): - try: - sw.send_diseqc_message(bytes([0xE0, 0x10, 0x38, 0xF0 | (i & 3)])) - time.sleep(0.05) - except Exception: - pass - dt = time.time() - t0 - print(f' [PASS] 6e. 10 DiSEqC msgs: {dt * 1000:.0f}ms') - passed += 1 - - print() - - # ============================================================ - print('=== CAT 7: Invalid Vendor Commands ===') - print() - - for cmd, name in [(0xFF, '0xFF'), (0x01, '0x01'), (0x50, '0x50'), - (0xFE, '0xFE'), (0x00, '0x00'), (0x79, '0x79')]: - try: - r = sw.dev.ctrl_transfer(0xC0, cmd, 0, 0, 1, timeout=2000) - print(f' [INFO] 7. Cmd {name}: accepted (0x{r[0]:02X})') - except usb.core.USBError: - print(f' [PASS] 7. Cmd {name}: STALL (rejected)') - passed += 1 - - test(sw, '7g. GET_CONFIG as OUT direction', - lambda: sw.dev.ctrl_transfer(0x40, 0x80, 0, 0, bytes([0xAA]), timeout=2000), - expect_no_hang=True) - - test(sw, '7h. 64-byte payload to GET_CONFIG', - lambda: sw.dev.ctrl_transfer(0x40, 0x80, 0, 0, bytes(64), timeout=2000), - expect_no_hang=True) - - print() - - # ============================================================ - # CLEANUP - # ============================================================ - sw.set_22khz_tone(False) - sw.set_lnb_voltage(False) - sw.start_intersil(False) - time.sleep(0.2) - - alive = device_alive(sw) - final_err = get_err(sw) - - print('=' * 64) - print(f' HAMILTON ADVERSARIAL TEST — FINAL RESULTS') - print(f' -----------------------------------------') - print(f' Tests passed: {passed}') - print(f' Tests failed: {failed}') - print(f' Device alive: {alive}') - print(f' Final error: 0x{final_err:02X} [{err_name(final_err)}]') - print(f' Watchdog fired: {"YES!" if final_err == 0x0D else "No"}') - verdict = 'PASS' if failed == 0 and alive else 'FAIL' - print(f' Verdict: {verdict}') - print('=' * 64) - - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 +""" +Hamilton Adversarial Test Suite — SkyWalker-1 v3.05.0 + +"What happens if the astronaut pushes the wrong button?" + +Tests operator error, invalid inputs, state machine violations, +boundary conditions, and rapid-fire stress to verify all safety +fixes from the Phase E Margaret Hamilton review. +""" + +import sys +import os +import time +import struct + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from skywalker_lib import SkyWalker1 +import usb.core + +ERR_NAMES = { + 0x00: 'OK', 0x01: 'I2C_TIMEOUT', 0x02: 'I2C_NAK', 0x03: 'BCM_TIMEOUT', + 0x04: 'BCM_NOT_READY', 0x05: 'BCM_VERIFY', 0x06: 'TUNE_FAIL', + 0x07: 'EP0_TIMEOUT', 0x08: 'GPIF_TIMEOUT', 0x09: 'EP2_TIMEOUT', + 0x0A: 'NOT_SUPPORTED', 0x0B: 'DISEQC_LEN', 0x0C: 'DISEQC_TIMER', + 0x0D: 'WDT_FIRED' +} + +passed = 0 +failed = 0 + + +def get_err(sw): + return sw.dev.ctrl_transfer(0xC0, 0xBC, 0, 0, 1)[0] + + +def err_name(code): + return ERR_NAMES.get(code, f'0x{code:02X}') + + +def device_alive(sw): + try: + fw = sw.get_fw_version() + return fw['version'] == '3.05.0' + except Exception: + return False + + +def test(sw, label, fn, expect_err=None, expect_no_hang=False): + """Run test, track error changes, verify device survives.""" + global passed, failed + + err_before = get_err(sw) + usb_err = None + try: + fn() + except usb.core.USBError as e: + usb_err = e + except Exception as e: + usb_err = e + + time.sleep(0.15) + + if not device_alive(sw): + print(f' [FAIL] {label}: DEVICE DIED!') + failed += 1 + return False + + err_after = get_err(sw) + changed = (err_after != err_before) + suffix = f' (USB: {usb_err})' if usb_err else '' + + if expect_err is not None: + if err_after == expect_err: + print(f' [PASS] {label}: err={err_name(expect_err)} as expected{suffix}') + passed += 1 + else: + print(f' [FAIL] {label}: expected {err_name(expect_err)}, got {err_name(err_after)}{suffix}') + failed += 1 + elif expect_no_hang: + print(f' [PASS] {label}: no hang, err={err_name(err_after)}{suffix}') + passed += 1 + else: + if changed: + print(f' [INFO] {label}: err changed {err_name(err_before)} -> {err_name(err_after)}{suffix}') + else: + print(f' [PASS] {label}: no new error{suffix}') + passed += 1 + + return True + + +def main(): + global passed, failed + + with SkyWalker1() as sw: + print('=' * 64) + print(' HAMILTON ADVERSARIAL TEST SUITE — SkyWalker-1 v3.05.0') + print(' "What if the astronaut pushes the wrong button?"') + print('=' * 64) + print() + + # Ensure clean starting state + sw.dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000) + sw.start_intersil(True) + time.sleep(0.5) + + # ============================================================ + print('=== CAT 1: DiSEqC Message Abuse ===') + print() + + test(sw, '1a. Tone burst B (M3: NOT_SUPPORTED)', + lambda: sw.send_diseqc_tone_burst(1), + expect_err=0x0A) + + test(sw, '1b. Tone burst wValue=0xFF', + lambda: sw.dev.ctrl_transfer(0x40, 0x8D, 0xFF, 0, None, timeout=3000), + expect_err=0x0A) + + test(sw, '1c. DiSEqC 2 bytes (too short)', + lambda: sw.dev.ctrl_transfer(0x40, 0x8D, 0xE0, 0, bytes([0xE0, 0x10]), timeout=3000), + expect_no_hang=True) + + test(sw, '1d. DiSEqC 8 bytes (too long)', + lambda: sw.dev.ctrl_transfer(0x40, 0x8D, 0xE0, 0, bytes([0xE0] * 8), timeout=3000), + expect_no_hang=True) + + test(sw, '1e. DiSEqC empty payload', + lambda: sw.dev.ctrl_transfer(0x40, 0x8D, 0xE0, 0, bytes([]), timeout=3000), + expect_no_hang=True) + + test(sw, '1f. Valid 4-byte DiSEqC (recovery)', + lambda: sw.send_diseqc_message(bytes([0xE0, 0x10, 0x38, 0xF0])), + expect_no_hang=True) + + test(sw, '1g. DiSEqC 1.2 motor halt (no motor)', + lambda: sw.send_diseqc_message(bytes([0xE0, 0x31, 0x60])), + expect_no_hang=True) + + test(sw, '1h. DiSEqC 1.2 drive east 255 steps', + lambda: sw.send_diseqc_message(bytes([0xE0, 0x31, 0x68, 0xFF])), + expect_no_hang=True) + + test(sw, '1i. DiSEqC 1.2 USALS GotoX (bogus angle)', + lambda: sw.send_diseqc_message(bytes([0xE0, 0x31, 0x6E, 0xFF, 0xFF])), + expect_no_hang=True) + + print() + + # ============================================================ + print('=== CAT 2: Tune Parameter Abuse ===') + print() + + test(sw, '2a. SR=0', + lambda: [sw.tune(0, 1000000, 0, 0), time.sleep(0.5)], + expect_no_hang=True) + + test(sw, '2b. SR=0xFFFFFFFF', + lambda: [sw.dev.ctrl_transfer(0x40, 0x86, 0, 0, + struct.pack('> Powering off BCM4500...') + sw.dev.ctrl_transfer(0xC0, 0x89, 0, 0, 3, timeout=5000) + time.sleep(0.5) + + test(sw, '4b. Tune with BCM off', + lambda: [sw.tune(20000000, 1000000, 0, 0), time.sleep(0.5)], + expect_err=0x04) # BCM_NOT_READY + + test(sw, '4c. Signal monitor with BCM off', + lambda: sw.dev.ctrl_transfer(0xC0, 0xB7, 0, 0, 8, timeout=3000), + expect_no_hang=True) + + test(sw, '4d. I2C bus scan with BCM off', + lambda: sw.dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16, timeout=5000), + expect_no_hang=True) + + test(sw, '4e. Hotplug rescan with BCM off', + lambda: sw.dev.ctrl_transfer(0xC0, 0xBE, 2, 0, 36, timeout=5000), + expect_no_hang=True) + + # 4f. Recovery + print(' >> Re-booting BCM4500...') + r = sw.dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000) + time.sleep(0.5) + cfg = sw.get_config() + if cfg & 0x03 == 0x03: + print(f' [PASS] 4f. Recovery: config=0x{cfg:02X} (STARTED|FW_LOADED)') + passed += 1 + else: + print(f' [FAIL] 4f. No recovery: config=0x{cfg:02X}') + failed += 1 + + # 4g. Arm/disarm rapid toggle + test(sw, '4g. Arm + immediate disarm', + lambda: [sw.arm_transfer(True), sw.arm_transfer(False)], + expect_no_hang=True) + + # 4h. Disarm when not armed + test(sw, '4h. Disarm when not armed', + lambda: sw.arm_transfer(False), + expect_no_hang=True) + + # 4i. Boot off/on/off/on rapid + test(sw, '4i. Rapid boot toggle (off-on-off-on)', + lambda: [ + sw.dev.ctrl_transfer(0xC0, 0x89, 0, 0, 3, timeout=5000), + time.sleep(0.2), + sw.dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000), + time.sleep(0.3), + sw.dev.ctrl_transfer(0xC0, 0x89, 0, 0, 3, timeout=5000), + time.sleep(0.2), + sw.dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000), + time.sleep(0.3), + ], + expect_no_hang=True) + + print() + + # ============================================================ + print('=== CAT 5: Boundary & Buffer Abuse ===') + print() + + # 5a. Request 0 bytes from GET_CONFIG + test(sw, '5a. GET_CONFIG request 0 bytes', + lambda: sw.dev.ctrl_transfer(0xC0, 0x80, 0, 0, 0, timeout=2000), + expect_no_hang=True) + + # 5b. Request 64 bytes from GET_CONFIG (returns 1) + test(sw, '5b. GET_CONFIG request 64 bytes', + lambda: sw.dev.ctrl_transfer(0xC0, 0x80, 0, 0, 64, timeout=2000), + expect_no_hang=True) + + # 5c. Request 64 bytes from GET_LAST_ERROR (returns 1) + test(sw, '5c. GET_LAST_ERROR request 64 bytes', + lambda: sw.dev.ctrl_transfer(0xC0, 0xBC, 0, 0, 64, timeout=2000), + expect_no_hang=True) + + # 5d. GET_FW_VERS request 1 byte (returns 6) + test(sw, '5d. GET_FW_VERS request 1 byte', + lambda: sw.dev.ctrl_transfer(0xC0, 0x92, 0, 0, 1, timeout=2000), + expect_no_hang=True) + + # 5e. GET_STREAM_DIAG request 1 byte (returns 12) + test(sw, '5e. GET_STREAM_DIAG request 1 byte', + lambda: sw.dev.ctrl_transfer(0xC0, 0xBD, 0, 0, 1, timeout=2000), + expect_no_hang=True) + + # 5f. GET_STREAM_DIAG with wval=0xFFFF (reset flag, but not 1) + test(sw, '5f. GET_STREAM_DIAG wval=0xFFFF', + lambda: sw.dev.ctrl_transfer(0xC0, 0xBD, 0xFFFF, 0, 12, timeout=2000), + expect_no_hang=True) + + # 5g. GET_HOTPLUG with wval=0xFFFF (unknown sub-command) + test(sw, '5g. GET_HOTPLUG wval=0xFFFF', + lambda: sw.dev.ctrl_transfer(0xC0, 0xBE, 0xFFFF, 0, 36, timeout=2000), + expect_no_hang=True) + + print() + + # ============================================================ + print('=== CAT 6: Rapid-Fire Stress ===') + print() + + t0 = time.time() + for i in range(200): + sw.get_config() + dt = time.time() - t0 + print(f' [PASS] 6a. 200 config reads: {dt * 1000:.0f}ms ({dt / 200 * 1000:.1f}ms/read)') + passed += 1 + + t0 = time.time() + for i in range(50): + get_err(sw) + dt = time.time() - t0 + print(f' [PASS] 6b. 50 error reads: {dt * 1000:.0f}ms ({dt / 50 * 1000:.1f}ms/read)') + passed += 1 + + t0 = time.time() + errs = 0 + for i in range(30): + try: + sw.signal_monitor() + except Exception: + errs += 1 + dt = time.time() - t0 + print(f' [PASS] 6c. 30 signal monitors: {dt * 1000:.0f}ms ({errs} errors)') + passed += 1 + + sw.start_intersil(True) + time.sleep(0.1) + t0 = time.time() + for i in range(40): + sw.set_lnb_voltage(i % 2 == 0) + dt = time.time() - t0 + print(f' [PASS] 6d. 40 voltage toggles: {dt * 1000:.0f}ms') + passed += 1 + + t0 = time.time() + for i in range(10): + try: + sw.send_diseqc_message(bytes([0xE0, 0x10, 0x38, 0xF0 | (i & 3)])) + time.sleep(0.05) + except Exception: + pass + dt = time.time() - t0 + print(f' [PASS] 6e. 10 DiSEqC msgs: {dt * 1000:.0f}ms') + passed += 1 + + print() + + # ============================================================ + print('=== CAT 7: Invalid Vendor Commands ===') + print() + + for cmd, name in [(0xFF, '0xFF'), (0x01, '0x01'), (0x50, '0x50'), + (0xFE, '0xFE'), (0x00, '0x00'), (0x79, '0x79')]: + try: + r = sw.dev.ctrl_transfer(0xC0, cmd, 0, 0, 1, timeout=2000) + print(f' [INFO] 7. Cmd {name}: accepted (0x{r[0]:02X})') + except usb.core.USBError: + print(f' [PASS] 7. Cmd {name}: STALL (rejected)') + passed += 1 + + test(sw, '7g. GET_CONFIG as OUT direction', + lambda: sw.dev.ctrl_transfer(0x40, 0x80, 0, 0, bytes([0xAA]), timeout=2000), + expect_no_hang=True) + + test(sw, '7h. 64-byte payload to GET_CONFIG', + lambda: sw.dev.ctrl_transfer(0x40, 0x80, 0, 0, bytes(64), timeout=2000), + expect_no_hang=True) + + print() + + # ============================================================ + # CLEANUP + # ============================================================ + sw.set_22khz_tone(False) + sw.set_lnb_voltage(False) + sw.start_intersil(False) + time.sleep(0.2) + + alive = device_alive(sw) + final_err = get_err(sw) + + print('=' * 64) + print(f' HAMILTON ADVERSARIAL TEST — FINAL RESULTS') + print(f' -----------------------------------------') + print(f' Tests passed: {passed}') + print(f' Tests failed: {failed}') + print(f' Device alive: {alive}') + print(f' Final error: 0x{final_err:02X} [{err_name(final_err)}]') + print(f' Watchdog fired: {"YES!" if final_err == 0x0D else "No"}') + verdict = 'PASS' if failed == 0 and alive else 'FAIL' + print(f' Verdict: {verdict}') + print('=' * 64) + + +if __name__ == '__main__': + main() diff --git a/tools/test_i2c_debug.py b/tools/test_i2c_debug.py index 143a313..3758624 100644 --- a/tools/test_i2c_debug.py +++ b/tools/test_i2c_debug.py @@ -1,118 +1,118 @@ -#!/usr/bin/env python3 -"""I2C debug tool for SkyWalker-1. - -First powers on the BCM4500 via GPIO debug mode (0x81), then: -1. Runs I2C bus scan (0xB4) to find any devices -2. Tries raw I2C reads (0xB5) to common BCM4500 addresses -3. Tests different post-reset delays -""" - -import usb.core -import usb.util -import sys -import time - -BOOT_8PSK = 0x89 - -def find_device(): - dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) - if not dev: - print("Device not found!") - sys.exit(1) - return dev - -def setup_device(dev): - try: - if dev.is_kernel_driver_active(0): - dev.detach_kernel_driver(0) - except Exception: - pass - try: - dev.set_configuration() - except usb.core.USBError: - pass - -def main(): - dev = find_device() - setup_device(dev) - - # Verify firmware - ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=2000) - major, minor, patch = ret[2], ret[1], ret[0] - print(f"Firmware: v{major}.{minor:02d}.{patch}") - - # Step 1: Power on BCM4500 via GPIO-only debug mode - print("\n--- Step 1: Power on BCM4500 (GPIO mode 0x81) ---") - ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000) - print(f" GPIO setup: stage=0x{ret[1]:02X}") - - # Step 2: I2C bus scan immediately - print("\n--- Step 2: I2C bus scan (immediately after power-on) ---") - try: - ret = dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16, timeout=5000) - addrs = [] - for bi in range(16): - for bit in range(8): - if ret[bi] & (1 << bit): - addrs.append(bi * 8 + bit) - if addrs: - print(f" Found devices at: {[f'0x{a:02X}' for a in addrs]}") - else: - print(" No I2C devices found!") - except usb.core.USBError as e: - print(f" Bus scan error: {e}") - - # Step 3: Wait longer and scan again - print("\n--- Step 3: Wait 500ms and scan again ---") - time.sleep(0.5) - try: - ret = dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16, timeout=5000) - addrs = [] - for bi in range(16): - for bit in range(8): - if ret[bi] & (1 << bit): - addrs.append(bi * 8 + bit) - if addrs: - print(f" Found devices at: {[f'0x{a:02X}' for a in addrs]}") - else: - print(" No I2C devices found!") - except usb.core.USBError as e: - print(f" Bus scan error: {e}") - - # Step 4: Try raw I2C reads to various addresses - print("\n--- Step 4: Raw I2C reads (0xB5) to likely BCM4500 addresses ---") - # BCM4500 could be at different addresses depending on pin strapping - # Common: 0x08 (AD=low), 0x0A (AD=high), or even other addresses - candidates = [0x08, 0x09, 0x0A, 0x0B, 0x10, 0x11, 0x68, 0x69, 0x60, 0x61] - for addr in candidates: - for reg in [0xA2, 0x00]: - try: - r = dev.ctrl_transfer(0xC0, 0xB5, addr, reg, 1, timeout=1000) - print(f" Addr 0x{addr:02X} Reg 0x{reg:02X} = 0x{r[0]:02X} <--- RESPONDS!") - except usb.core.USBError: - print(f" Addr 0x{addr:02X} Reg 0x{reg:02X} = (no response)") - - # Step 5: Check I2C bus state - print("\n--- Step 5: I2C controller state ---") - try: - # Read I2CTL and I2CS by inspecting them through a known-working address - # Actually, we can just observe what happens when we try reads - print(" (Bus scan and raw reads above show bus health)") - except Exception as e: - print(f" Error: {e}") - - # Step 6: Try I2C probe via debug mode 0x82 again with a delay - print("\n--- Step 6: Debug probe (0x82) after additional 1s delay ---") - time.sleep(1.0) - ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x82, 0, 3, timeout=3000) - stage = ret[1] - probe = ret[2] - if stage == 0xA2: - print(f" PROBE SUCCESS! BCM4500 status = 0x{probe:02X}") - else: - print(f" Probe failed: stage=0x{stage:02X} probe=0x{probe:02X}") - - print("\nDone.") - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +"""I2C debug tool for SkyWalker-1. + +First powers on the BCM4500 via GPIO debug mode (0x81), then: +1. Runs I2C bus scan (0xB4) to find any devices +2. Tries raw I2C reads (0xB5) to common BCM4500 addresses +3. Tests different post-reset delays +""" + +import usb.core +import usb.util +import sys +import time + +BOOT_8PSK = 0x89 + +def find_device(): + dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) + if not dev: + print("Device not found!") + sys.exit(1) + return dev + +def setup_device(dev): + try: + if dev.is_kernel_driver_active(0): + dev.detach_kernel_driver(0) + except Exception: + pass + try: + dev.set_configuration() + except usb.core.USBError: + pass + +def main(): + dev = find_device() + setup_device(dev) + + # Verify firmware + ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=2000) + major, minor, patch = ret[2], ret[1], ret[0] + print(f"Firmware: v{major}.{minor:02d}.{patch}") + + # Step 1: Power on BCM4500 via GPIO-only debug mode + print("\n--- Step 1: Power on BCM4500 (GPIO mode 0x81) ---") + ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000) + print(f" GPIO setup: stage=0x{ret[1]:02X}") + + # Step 2: I2C bus scan immediately + print("\n--- Step 2: I2C bus scan (immediately after power-on) ---") + try: + ret = dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16, timeout=5000) + addrs = [] + for bi in range(16): + for bit in range(8): + if ret[bi] & (1 << bit): + addrs.append(bi * 8 + bit) + if addrs: + print(f" Found devices at: {[f'0x{a:02X}' for a in addrs]}") + else: + print(" No I2C devices found!") + except usb.core.USBError as e: + print(f" Bus scan error: {e}") + + # Step 3: Wait longer and scan again + print("\n--- Step 3: Wait 500ms and scan again ---") + time.sleep(0.5) + try: + ret = dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16, timeout=5000) + addrs = [] + for bi in range(16): + for bit in range(8): + if ret[bi] & (1 << bit): + addrs.append(bi * 8 + bit) + if addrs: + print(f" Found devices at: {[f'0x{a:02X}' for a in addrs]}") + else: + print(" No I2C devices found!") + except usb.core.USBError as e: + print(f" Bus scan error: {e}") + + # Step 4: Try raw I2C reads to various addresses + print("\n--- Step 4: Raw I2C reads (0xB5) to likely BCM4500 addresses ---") + # BCM4500 could be at different addresses depending on pin strapping + # Common: 0x08 (AD=low), 0x0A (AD=high), or even other addresses + candidates = [0x08, 0x09, 0x0A, 0x0B, 0x10, 0x11, 0x68, 0x69, 0x60, 0x61] + for addr in candidates: + for reg in [0xA2, 0x00]: + try: + r = dev.ctrl_transfer(0xC0, 0xB5, addr, reg, 1, timeout=1000) + print(f" Addr 0x{addr:02X} Reg 0x{reg:02X} = 0x{r[0]:02X} <--- RESPONDS!") + except usb.core.USBError: + print(f" Addr 0x{addr:02X} Reg 0x{reg:02X} = (no response)") + + # Step 5: Check I2C bus state + print("\n--- Step 5: I2C controller state ---") + try: + # Read I2CTL and I2CS by inspecting them through a known-working address + # Actually, we can just observe what happens when we try reads + print(" (Bus scan and raw reads above show bus health)") + except Exception as e: + print(f" Error: {e}") + + # Step 6: Try I2C probe via debug mode 0x82 again with a delay + print("\n--- Step 6: Debug probe (0x82) after additional 1s delay ---") + time.sleep(1.0) + ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x82, 0, 3, timeout=3000) + stage = ret[1] + probe = ret[2] + if stage == 0xA2: + print(f" PROBE SUCCESS! BCM4500 status = 0x{probe:02X}") + else: + print(f" Probe failed: stage=0x{stage:02X} probe=0x{probe:02X}") + + print("\nDone.") + +if __name__ == "__main__": + main() diff --git a/tools/test_i2c_isolate.py b/tools/test_i2c_isolate.py index 4369c78..8d533d9 100644 --- a/tools/test_i2c_isolate.py +++ b/tools/test_i2c_isolate.py @@ -1,126 +1,126 @@ -#!/usr/bin/env python3 -"""Isolate whether bcm_direct_read is broken or if re-reset causes the failure. - -Test sequence: -1. Power on BCM4500 with 0x81 (GPIO only) -2. Wait 1s for chip to settle -3. Confirm chip alive via raw read 0xB5 -4. Try bcm_direct_read via debug mode 0x82 (which RE-RESETS the chip) -5. Immediately try raw read 0xB5 again (is chip alive after 0x82's reset?) -6. Wait various delays and retry raw reads - -This tells us if the issue is bcm_direct_read vs insufficient post-reset delay. -""" - -import usb.core -import usb.util -import sys -import time - -BOOT_8PSK = 0x89 - -def find_device(): - dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) - if not dev: - print("Device not found!") - sys.exit(1) - return dev - -def setup_device(dev): - try: - if dev.is_kernel_driver_active(0): - dev.detach_kernel_driver(0) - except Exception: - pass - try: - dev.set_configuration() - except usb.core.USBError: - pass - -def raw_read(dev, addr, reg, label=""): - """Read via 0xB5 raw I2C handler.""" - try: - r = dev.ctrl_transfer(0xC0, 0xB5, addr, reg, 1, timeout=1000) - val = r[0] - ok = val != 0xFF - mark = "OK" if ok else "no-resp" - print(f" {label}Raw read addr=0x{addr:02X} reg=0x{reg:02X} → 0x{val:02X} ({mark})") - return val, ok - except usb.core.USBError as e: - print(f" {label}Raw read addr=0x{addr:02X} reg=0x{reg:02X} → USB ERROR: {e}") - return None, False - -def main(): - dev = find_device() - setup_device(dev) - - ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=2000) - major, minor, patch = ret[2], ret[1], ret[0] - print(f"Firmware: v{major}.{minor:02d}.{patch}\n") - - # --- Test A: Verify BCM4500 alive from cold --- - print("=" * 55) - print("TEST A: Power on BCM4500, wait, then raw read") - print("=" * 55) - print(" Sending 0x81 (GPIO power on + reset release)...") - ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000) - print(f" GPIO done: stage=0x{ret[1]:02X}") - - print(" Waiting 1000ms for BCM4500 to settle...") - time.sleep(1.0) - - raw_read(dev, 0x08, 0xA2, "After 1s: ") - - # --- Test B: Now try bcm_direct_read (which re-resets) --- - print() - print("=" * 55) - print("TEST B: Run debug mode 0x82 (re-resets + probe via bcm_direct_read)") - print("=" * 55) - ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x82, 0, 3, timeout=3000) - stage = ret[1] - probe = ret[2] - if stage == 0xA2: - print(f" bcm_direct_read SUCCEEDED: status=0x{probe:02X}") - else: - print(f" bcm_direct_read FAILED: stage=0x{stage:02X} probe=0x{probe:02X}") - - # --- Test C: Immediately try raw read after 0x82 (same I2C function, no reset) --- - print() - print("=" * 55) - print("TEST C: Immediately try raw read 0xB5 (same i2c_combined_read)") - print("=" * 55) - raw_read(dev, 0x08, 0xA2, "Immediate: ") - - # --- Test D: Wait and retry at various intervals --- - print() - print("=" * 55) - print("TEST D: Raw reads with increasing delays after 0x82's reset") - print("=" * 55) - for delay_ms in [100, 200, 500, 1000, 2000]: - time.sleep(delay_ms / 1000.0) - raw_read(dev, 0x08, 0xA2, f"After {delay_ms}ms: ") - - # --- Test E: Redo power-on without reset, then probe --- - print() - print("=" * 55) - print("TEST E: Run 0x81 again (re-power), wait 1s, then 0x82") - print("=" * 55) - ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000) - print(f" GPIO done: stage=0x{ret[1]:02X}") - time.sleep(1.0) - raw_read(dev, 0x08, 0xA2, "After 0x81+1s: ") - - print(" Now running 0x82 (re-reset + probe)...") - ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x82, 0, 3, timeout=3000) - stage = ret[1] - probe = ret[2] - if stage == 0xA2: - print(f" bcm_direct_read SUCCEEDED: status=0x{probe:02X}") - else: - print(f" bcm_direct_read FAILED: stage=0x{stage:02X} probe=0x{probe:02X}") - - print("\n" + "=" * 55) - print("Analysis complete.") - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +"""Isolate whether bcm_direct_read is broken or if re-reset causes the failure. + +Test sequence: +1. Power on BCM4500 with 0x81 (GPIO only) +2. Wait 1s for chip to settle +3. Confirm chip alive via raw read 0xB5 +4. Try bcm_direct_read via debug mode 0x82 (which RE-RESETS the chip) +5. Immediately try raw read 0xB5 again (is chip alive after 0x82's reset?) +6. Wait various delays and retry raw reads + +This tells us if the issue is bcm_direct_read vs insufficient post-reset delay. +""" + +import usb.core +import usb.util +import sys +import time + +BOOT_8PSK = 0x89 + +def find_device(): + dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) + if not dev: + print("Device not found!") + sys.exit(1) + return dev + +def setup_device(dev): + try: + if dev.is_kernel_driver_active(0): + dev.detach_kernel_driver(0) + except Exception: + pass + try: + dev.set_configuration() + except usb.core.USBError: + pass + +def raw_read(dev, addr, reg, label=""): + """Read via 0xB5 raw I2C handler.""" + try: + r = dev.ctrl_transfer(0xC0, 0xB5, addr, reg, 1, timeout=1000) + val = r[0] + ok = val != 0xFF + mark = "OK" if ok else "no-resp" + print(f" {label}Raw read addr=0x{addr:02X} reg=0x{reg:02X} → 0x{val:02X} ({mark})") + return val, ok + except usb.core.USBError as e: + print(f" {label}Raw read addr=0x{addr:02X} reg=0x{reg:02X} → USB ERROR: {e}") + return None, False + +def main(): + dev = find_device() + setup_device(dev) + + ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=2000) + major, minor, patch = ret[2], ret[1], ret[0] + print(f"Firmware: v{major}.{minor:02d}.{patch}\n") + + # --- Test A: Verify BCM4500 alive from cold --- + print("=" * 55) + print("TEST A: Power on BCM4500, wait, then raw read") + print("=" * 55) + print(" Sending 0x81 (GPIO power on + reset release)...") + ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000) + print(f" GPIO done: stage=0x{ret[1]:02X}") + + print(" Waiting 1000ms for BCM4500 to settle...") + time.sleep(1.0) + + raw_read(dev, 0x08, 0xA2, "After 1s: ") + + # --- Test B: Now try bcm_direct_read (which re-resets) --- + print() + print("=" * 55) + print("TEST B: Run debug mode 0x82 (re-resets + probe via bcm_direct_read)") + print("=" * 55) + ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x82, 0, 3, timeout=3000) + stage = ret[1] + probe = ret[2] + if stage == 0xA2: + print(f" bcm_direct_read SUCCEEDED: status=0x{probe:02X}") + else: + print(f" bcm_direct_read FAILED: stage=0x{stage:02X} probe=0x{probe:02X}") + + # --- Test C: Immediately try raw read after 0x82 (same I2C function, no reset) --- + print() + print("=" * 55) + print("TEST C: Immediately try raw read 0xB5 (same i2c_combined_read)") + print("=" * 55) + raw_read(dev, 0x08, 0xA2, "Immediate: ") + + # --- Test D: Wait and retry at various intervals --- + print() + print("=" * 55) + print("TEST D: Raw reads with increasing delays after 0x82's reset") + print("=" * 55) + for delay_ms in [100, 200, 500, 1000, 2000]: + time.sleep(delay_ms / 1000.0) + raw_read(dev, 0x08, 0xA2, f"After {delay_ms}ms: ") + + # --- Test E: Redo power-on without reset, then probe --- + print() + print("=" * 55) + print("TEST E: Run 0x81 again (re-power), wait 1s, then 0x82") + print("=" * 55) + ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000) + print(f" GPIO done: stage=0x{ret[1]:02X}") + time.sleep(1.0) + raw_read(dev, 0x08, 0xA2, "After 0x81+1s: ") + + print(" Now running 0x82 (re-reset + probe)...") + ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x82, 0, 3, timeout=3000) + stage = ret[1] + probe = ret[2] + if stage == 0xA2: + print(f" bcm_direct_read SUCCEEDED: status=0x{probe:02X}") + else: + print(f" bcm_direct_read FAILED: stage=0x{stage:02X} probe=0x{probe:02X}") + + print("\n" + "=" * 55) + print("Analysis complete.") + +if __name__ == "__main__": + main() diff --git a/tools/test_i2c_pinpoint.py b/tools/test_i2c_pinpoint.py index feaf7b4..ab0af9f 100644 --- a/tools/test_i2c_pinpoint.py +++ b/tools/test_i2c_pinpoint.py @@ -1,122 +1,122 @@ -#!/usr/bin/env python3 -"""Pinpoint which element in mode 0x82 causes bcm_direct_read to fail. - -Test sequence: -1. Power on via 0x81, confirm alive with raw read -2. 0x84: bcm_direct_read ONLY (no GPIO, no reset, no bus reset) -3. 0x85: GPIO + reset + power but NO I2C bus reset (no bmSTOP) -4. 0x82: GPIO + I2C bus reset + reset + power + probe (the one that fails) -""" - -import usb.core -import usb.util -import sys -import time - -BOOT_8PSK = 0x89 - -def find_device(): - dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) - if not dev: - print("Device not found!") - sys.exit(1) - return dev - -def setup_device(dev): - try: - if dev.is_kernel_driver_active(0): - dev.detach_kernel_driver(0) - except Exception: - pass - try: - dev.set_configuration() - except usb.core.USBError: - pass - -def decode_stage(stage): - names = { - 0x00: "NOT_STARTED", 0xA1: "GPIO_OK", 0xA2: "PROBE_OK(0x82)", - 0xA3: "BLK0_OK", 0xA4: "PROBE_OK(0x84)", 0xA5: "PROBE_OK(0x85)", - 0xE3: "PROBE_FAIL", 0xE4: "BLK0_FAIL", - } - return names.get(stage, f"0x{stage:02X}") - -def test_boot_mode(dev, wval, label, timeout_ms=3000): - print(f"\n{'─' * 55}") - print(f" Mode 0x{wval:02X}: {label}") - print(f"{'─' * 55}") - - t0 = time.monotonic() - try: - ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, wval, 0, 3, timeout=timeout_ms) - except usb.core.USBError as e: - elapsed = (time.monotonic() - t0) * 1000 - print(f" TIMEOUT after {elapsed:.0f}ms: {e}") - return None - elapsed = (time.monotonic() - t0) * 1000 - - stage = ret[1] - probe = ret[2] - ok = stage not in (0xE3, 0xE4) - status_str = "SUCCESS" if ok else "FAILED" - print(f" {status_str} in {elapsed:.0f}ms") - print(f" stage=0x{stage:02X} [{decode_stage(stage)}] probe=0x{probe:02X}") - return ret - -def raw_read(dev, addr, reg): - try: - r = dev.ctrl_transfer(0xC0, 0xB5, addr, reg, 1, timeout=1000) - return r[0] - except: - return None - -def main(): - dev = find_device() - setup_device(dev) - - ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=2000) - major, minor, patch = ret[2], ret[1], ret[0] - print(f"Firmware: v{major}.{minor:02d}.{patch}") - - # Step 1: Power on via GPIO-only mode - print("\n=== STEP 1: Power on BCM4500 (mode 0x81) ===") - ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000) - print(f" GPIO setup done, stage=0x{ret[1]:02X}") - time.sleep(1.0) - - # Confirm alive - val = raw_read(dev, 0x08, 0xA2) - print(f" Raw read 0x08:0xA2 = 0x{val:02X}" if val is not None else " Raw read FAILED") - - # Step 2: Test 0x84 (I2C read ONLY, no GPIO manipulation) - test_boot_mode(dev, 0x84, "bcm_direct_read ONLY (no GPIO, chip already powered)") - - # Confirm still alive - val = raw_read(dev, 0x08, 0xA2) - print(f" Raw read after 0x84: 0x{val:02X}" if val is not None else " Raw read FAILED") - - # Step 3: Test 0x85 (GPIO + reset but NO I2C bus reset) - test_boot_mode(dev, 0x85, "GPIO + reset + power, NO bmSTOP (no I2C bus reset)") - - # Confirm still alive - time.sleep(0.1) - val = raw_read(dev, 0x08, 0xA2) - print(f" Raw read after 0x85: 0x{val:02X}" if val is not None else " Raw read FAILED") - - # Step 4: For comparison, test 0x82 (the one that fails) - test_boot_mode(dev, 0x82, "GPIO + I2C bmSTOP + reset + power + probe") - - # Confirm still alive - val = raw_read(dev, 0x08, 0xA2) - print(f" Raw read after 0x82: 0x{val:02X}" if val is not None else " Raw read FAILED") - - print(f"\n{'=' * 55}") - print("Analysis complete.") - print() - print("If 0x84 works → bcm_direct_read is fine, issue is in reset/GPIO sequence") - print("If 0x84 fails → bcm_direct_read itself has a bug") - print("If 0x85 works → I2CS bmSTOP (I2C bus reset) is the culprit in 0x82") - print("If 0x85 fails → re-reset of BCM4500 needs more delay") - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +"""Pinpoint which element in mode 0x82 causes bcm_direct_read to fail. + +Test sequence: +1. Power on via 0x81, confirm alive with raw read +2. 0x84: bcm_direct_read ONLY (no GPIO, no reset, no bus reset) +3. 0x85: GPIO + reset + power but NO I2C bus reset (no bmSTOP) +4. 0x82: GPIO + I2C bus reset + reset + power + probe (the one that fails) +""" + +import usb.core +import usb.util +import sys +import time + +BOOT_8PSK = 0x89 + +def find_device(): + dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203) + if not dev: + print("Device not found!") + sys.exit(1) + return dev + +def setup_device(dev): + try: + if dev.is_kernel_driver_active(0): + dev.detach_kernel_driver(0) + except Exception: + pass + try: + dev.set_configuration() + except usb.core.USBError: + pass + +def decode_stage(stage): + names = { + 0x00: "NOT_STARTED", 0xA1: "GPIO_OK", 0xA2: "PROBE_OK(0x82)", + 0xA3: "BLK0_OK", 0xA4: "PROBE_OK(0x84)", 0xA5: "PROBE_OK(0x85)", + 0xE3: "PROBE_FAIL", 0xE4: "BLK0_FAIL", + } + return names.get(stage, f"0x{stage:02X}") + +def test_boot_mode(dev, wval, label, timeout_ms=3000): + print(f"\n{'─' * 55}") + print(f" Mode 0x{wval:02X}: {label}") + print(f"{'─' * 55}") + + t0 = time.monotonic() + try: + ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, wval, 0, 3, timeout=timeout_ms) + except usb.core.USBError as e: + elapsed = (time.monotonic() - t0) * 1000 + print(f" TIMEOUT after {elapsed:.0f}ms: {e}") + return None + elapsed = (time.monotonic() - t0) * 1000 + + stage = ret[1] + probe = ret[2] + ok = stage not in (0xE3, 0xE4) + status_str = "SUCCESS" if ok else "FAILED" + print(f" {status_str} in {elapsed:.0f}ms") + print(f" stage=0x{stage:02X} [{decode_stage(stage)}] probe=0x{probe:02X}") + return ret + +def raw_read(dev, addr, reg): + try: + r = dev.ctrl_transfer(0xC0, 0xB5, addr, reg, 1, timeout=1000) + return r[0] + except: + return None + +def main(): + dev = find_device() + setup_device(dev) + + ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=2000) + major, minor, patch = ret[2], ret[1], ret[0] + print(f"Firmware: v{major}.{minor:02d}.{patch}") + + # Step 1: Power on via GPIO-only mode + print("\n=== STEP 1: Power on BCM4500 (mode 0x81) ===") + ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000) + print(f" GPIO setup done, stage=0x{ret[1]:02X}") + time.sleep(1.0) + + # Confirm alive + val = raw_read(dev, 0x08, 0xA2) + print(f" Raw read 0x08:0xA2 = 0x{val:02X}" if val is not None else " Raw read FAILED") + + # Step 2: Test 0x84 (I2C read ONLY, no GPIO manipulation) + test_boot_mode(dev, 0x84, "bcm_direct_read ONLY (no GPIO, chip already powered)") + + # Confirm still alive + val = raw_read(dev, 0x08, 0xA2) + print(f" Raw read after 0x84: 0x{val:02X}" if val is not None else " Raw read FAILED") + + # Step 3: Test 0x85 (GPIO + reset but NO I2C bus reset) + test_boot_mode(dev, 0x85, "GPIO + reset + power, NO bmSTOP (no I2C bus reset)") + + # Confirm still alive + time.sleep(0.1) + val = raw_read(dev, 0x08, 0xA2) + print(f" Raw read after 0x85: 0x{val:02X}" if val is not None else " Raw read FAILED") + + # Step 4: For comparison, test 0x82 (the one that fails) + test_boot_mode(dev, 0x82, "GPIO + I2C bmSTOP + reset + power + probe") + + # Confirm still alive + val = raw_read(dev, 0x08, 0xA2) + print(f" Raw read after 0x82: 0x{val:02X}" if val is not None else " Raw read FAILED") + + print(f"\n{'=' * 55}") + print("Analysis complete.") + print() + print("If 0x84 works → bcm_direct_read is fine, issue is in reset/GPIO sequence") + print("If 0x84 fails → bcm_direct_read itself has a bug") + print("If 0x85 works → I2CS bmSTOP (I2C bus reset) is the culprit in 0x82") + print("If 0x85 fails → re-reset of BCM4500 needs more delay") + +if __name__ == "__main__": + main() diff --git a/tools/ts_analyze.py b/tools/ts_analyze.py index c4ffe99..af01d28 100755 --- a/tools/ts_analyze.py +++ b/tools/ts_analyze.py @@ -1,1279 +1,1279 @@ -#!/usr/bin/env python3 -""" -Genpix SkyWalker-1 MPEG-2 Transport Stream analyzer. - -Parses and analyzes 188-byte MPEG-2 TS packets from .ts files captured -by tune.py, stdin pipes, or any standard transport stream source. - -Supports PID analysis, PAT/PMT parsing, continuity counter checking, -scrambling detection, hex packet dumps, and live stream monitoring. - -Reference: ISO/IEC 13818-1 (MPEG-2 Systems) -TS packet: 188 bytes, sync byte 0x47 -""" - -import sys -import struct -import argparse -import time -import os - -TS_PACKET_SIZE = 188 -TS_SYNC_BYTE = 0x47 - -# Well-known PID assignments (ISO 13818-1 Table 2-3) -KNOWN_PIDS = { - 0x0000: "PAT", - 0x0001: "CAT", - 0x0002: "TSDT", - 0x0010: "NIT/ST", - 0x0011: "SDT/BAT/ST", - 0x0012: "EIT/ST", - 0x0013: "RST/ST", - 0x0014: "TDT/TOT/ST", - 0x001E: "DIT", - 0x001F: "SIT", - 0x1FFF: "Null", -} - -# Stream type identifiers (ISO 13818-1 Table 2-36) -STREAM_TYPES = { - 0x00: "Reserved", - 0x01: "MPEG-1 Video (11172-2)", - 0x02: "MPEG-2 Video (13818-2)", - 0x03: "MPEG-1 Audio (11172-3)", - 0x04: "MPEG-2 Audio (13818-3)", - 0x05: "Private Sections (13818-1)", - 0x06: "PES Private Data", - 0x07: "MHEG", - 0x08: "DSM-CC", - 0x09: "H.222.1", - 0x0A: "DSM-CC Type A", - 0x0B: "DSM-CC Type B", - 0x0C: "DSM-CC Type C", - 0x0D: "DSM-CC Type D", - 0x0E: "Auxiliary", - 0x0F: "MPEG-2 AAC Audio", - 0x10: "MPEG-4 Visual", - 0x11: "MPEG-4 AAC Audio (LATM)", - 0x15: "Metadata in PES", - 0x1B: "H.264/AVC Video", - 0x24: "H.265/HEVC Video", - 0x42: "AVS Video", - 0x81: "AC-3 Audio (ATSC)", - 0x82: "DTS Audio", - 0x83: "Dolby TrueHD", - 0x84: "Dolby Digital Plus (EAC-3)", - 0x85: "DTS-HD", - 0x86: "DTS-HD Master Audio", - 0x87: "EAC-3 Audio (ATSC)", - 0xEA: "VC-1 Video", -} - - -class TSPacket: - """Parsed MPEG-2 transport stream packet header.""" - - __slots__ = ( - 'sync', 'tei', 'pusi', 'priority', 'pid', - 'scrambling', 'adaptation', 'continuity', - 'adaptation_field', 'payload', 'raw', - ) - - def __init__(self, data: bytes): - if len(data) != TS_PACKET_SIZE: - raise ValueError(f"Packet must be {TS_PACKET_SIZE} bytes, got {len(data)}") - - self.raw = data - self.sync = data[0] - self.tei = bool(data[1] & 0x80) - self.pusi = bool(data[1] & 0x40) - self.priority = bool(data[1] & 0x20) - self.pid = ((data[1] & 0x1F) << 8) | data[2] - self.scrambling = (data[3] >> 6) & 0x03 - self.adaptation = (data[3] >> 4) & 0x03 - self.continuity = data[3] & 0x0F - - # Parse adaptation field and payload boundaries - offset = 4 - self.adaptation_field = None - self.payload = None - - if self.adaptation & 0x02: - # Adaptation field present - if offset < TS_PACKET_SIZE: - af_len = data[offset] - af_end = offset + 1 + af_len - if af_end <= TS_PACKET_SIZE: - self.adaptation_field = data[offset:af_end] - offset = af_end - - if self.adaptation & 0x01: - # Payload present - if offset < TS_PACKET_SIZE: - self.payload = data[offset:] - - def has_pcr(self) -> bool: - """Check if adaptation field contains a PCR.""" - if self.adaptation_field is None or len(self.adaptation_field) < 7: - return False - af_flags = self.adaptation_field[1] if len(self.adaptation_field) > 1 else 0 - return bool(af_flags & 0x10) - - def get_pcr(self) -> int: - """Extract PCR value (in 27 MHz clock ticks). Returns -1 if no PCR.""" - if not self.has_pcr(): - return -1 - # PCR is 6 bytes starting at adaptation_field[2] - af = self.adaptation_field - pcr_base = (af[2] << 25) | (af[3] << 17) | (af[4] << 9) | \ - (af[5] << 1) | ((af[6] >> 7) & 0x01) - pcr_ext = ((af[6] & 0x01) << 8) | af[7] - return pcr_base * 300 + pcr_ext - - -class TSReader: - """Reads TS packets from a file or stream, handling sync alignment.""" - - def __init__(self, source, verbose: bool = False): - self.source = source - self.verbose = verbose - self.offset = 0 - self._sync_offset = -1 - - def find_sync(self, data: bytes) -> int: - """Find sync byte alignment in raw data. Returns byte offset or -1.""" - # Need at least 3 consecutive sync bytes to confirm alignment - for i in range(min(len(data), TS_PACKET_SIZE)): - if data[i] != TS_SYNC_BYTE: - continue - # Check for consecutive sync bytes at 188-byte intervals - ok = True - for check in range(1, 4): - pos = i + check * TS_PACKET_SIZE - if pos >= len(data): - # Not enough data to confirm, accept if at least one more matches - if check >= 2: - break - ok = False - break - if data[pos] != TS_SYNC_BYTE: - ok = False - break - if ok: - return i - return -1 - - def iter_packets(self, max_packets: int = 0): - """Yield TSPacket objects from the source.""" - buf = b'' - synced = False - count = 0 - - while True: - chunk = self.source.read(65536) - if not chunk: - break - buf += chunk - - if not synced: - sync_off = self.find_sync(buf) - if sync_off < 0: - # Keep last 187 bytes in case sync straddles chunk boundary - if len(buf) > TS_PACKET_SIZE * 4: - buf = buf[-(TS_PACKET_SIZE - 1):] - continue - self._sync_offset = sync_off + self.offset - if self.verbose and sync_off > 0: - print(f" Sync found at byte offset {sync_off}", file=sys.stderr) - buf = buf[sync_off:] - synced = True - - while len(buf) >= TS_PACKET_SIZE: - pkt_data = buf[:TS_PACKET_SIZE] - buf = buf[TS_PACKET_SIZE:] - - if pkt_data[0] != TS_SYNC_BYTE: - # Lost sync, try to re-acquire - synced = False - if self.verbose: - print(f" Sync lost, re-scanning...", file=sys.stderr) - break - - count += 1 - yield TSPacket(pkt_data) - - if max_packets and count >= max_packets: - return - - self.offset += len(chunk) - - @property - def sync_offset(self) -> int: - return self._sync_offset - - -class PSIParser: - """Parse PSI sections from TS packet payloads.""" - - def __init__(self): - self._section_bufs = {} # pid -> accumulated bytes - - def feed(self, pkt: TSPacket) -> dict: - """Feed a packet, return parsed section dict or None.""" - if pkt.payload is None: - return None - - pid = pkt.pid - payload = pkt.payload - - if pkt.pusi: - # Payload Unit Start Indicator set - if len(payload) < 1: - return None - pointer = payload[0] - payload = payload[1 + pointer:] - self._section_bufs[pid] = payload - elif pid in self._section_bufs: - self._section_bufs[pid] += payload - else: - return None - - return self._try_parse(pid) - - def _try_parse(self, pid: int) -> dict: - """Try to parse a complete section from the buffer.""" - buf = self._section_bufs.get(pid, b'') - if len(buf) < 3: - return None - - table_id = buf[0] - section_length = ((buf[1] & 0x0F) << 8) | buf[2] - total_len = 3 + section_length - - if len(buf) < total_len: - return None # Incomplete, wait for more data - - section = buf[:total_len] - # Clear buffer for next section - self._section_bufs[pid] = buf[total_len:] - - if section_length < 5: - return None - - result = { - "table_id": table_id, - "section_syntax": bool(buf[1] & 0x80), - "section_length": section_length, - "raw": section, - } - - if result["section_syntax"]: - result["table_id_ext"] = (section[3] << 8) | section[4] - result["version"] = (section[5] >> 1) & 0x1F - result["current_next"] = section[5] & 0x01 - result["section_number"] = section[6] - result["last_section_number"] = section[7] - result["data"] = section[8:-4] - result["crc32"] = struct.unpack_from('>I', section, total_len - 4)[0] - - return result - - -def parse_pat(section: dict) -> dict: - """Parse a Program Association Table section.""" - if section is None or section["table_id"] != 0x00: - return None - - transport_stream_id = section["table_id_ext"] - data = section["data"] - programs = {} - - for i in range(0, len(data), 4): - if i + 4 > len(data): - break - prog_num = (data[i] << 8) | data[i + 1] - pmt_pid = ((data[i + 2] & 0x1F) << 8) | data[i + 3] - programs[prog_num] = pmt_pid - - return { - "transport_stream_id": transport_stream_id, - "version": section["version"], - "programs": programs, - } - - -def parse_pmt(section: dict) -> dict: - """Parse a Program Map Table section.""" - if section is None or section["table_id"] != 0x02: - return None - - program_number = section["table_id_ext"] - data = section["raw"] - - if len(data) < 12: - return None - - pcr_pid = ((data[8] & 0x1F) << 8) | data[9] - prog_info_len = ((data[10] & 0x0F) << 8) | data[11] - - offset = 12 + prog_info_len - streams = [] - - while offset + 5 <= len(data) - 4: # -4 for CRC - stream_type = data[offset] - elementary_pid = ((data[offset + 1] & 0x1F) << 8) | data[offset + 2] - es_info_len = ((data[offset + 3] & 0x0F) << 8) | data[offset + 4] - - streams.append({ - "stream_type": stream_type, - "elementary_pid": elementary_pid, - "es_info_length": es_info_len, - "type_name": STREAM_TYPES.get(stream_type, f"Unknown (0x{stream_type:02X})"), - }) - offset += 5 + es_info_len - - return { - "program_number": program_number, - "version": section["version"], - "pcr_pid": pcr_pid, - "streams": streams, - } - - -def parse_sdt(section: dict) -> dict: - """ - Parse a Service Description Table section. - - Table IDs: 0x42 = SDT actual transport stream, - 0x46 = SDT other transport stream. - Carried on PID 0x0011. - - Returns dict with: - transport_stream_id - TS ID from the table extension - original_network_id - ONID from bytes [0:2] of section data - services - list of service dicts, each containing: - service_id - program number - service_type - numeric type (1=digital TV, 2=digital radio, etc) - service_name - decoded service name string - provider_name - decoded provider name string - eit_schedule - bool, EIT schedule flag - eit_present - bool, EIT present/following flag - running_status - numeric running status - free_ca - bool, free/scrambled flag - - Descriptor parsing: looks for tag 0x48 (service_descriptor) which - encodes service_type (1 byte), provider_name_length + provider_name, - service_name_length + service_name. - """ - if section is None: - return None - if section["table_id"] not in (0x42, 0x46): - return None - if not section.get("section_syntax"): - return None - - transport_stream_id = section["table_id_ext"] - data = section.get("data", b'') - - if len(data) < 2: - return None - - original_network_id = (data[0] << 8) | data[1] - # Byte 2 is reserved_future_use - offset = 3 - - services = [] - while offset + 5 <= len(data): - service_id = (data[offset] << 8) | data[offset + 1] - # byte 2: EIT flags and running status - flags_byte = data[offset + 2] - eit_schedule = bool(flags_byte & 0x02) - eit_present = bool(flags_byte & 0x01) - - status_byte = data[offset + 3] - running_status = (status_byte >> 5) & 0x07 - free_ca = bool(status_byte & 0x10) - descriptors_loop_length = ((status_byte & 0x0F) << 8) | data[offset + 4] - - offset += 5 - - # Parse descriptors for this service - service_type = 0 - service_name = "" - provider_name = "" - - desc_end = offset + descriptors_loop_length - if desc_end > len(data): - desc_end = len(data) - - while offset + 2 <= desc_end: - desc_tag = data[offset] - desc_len = data[offset + 1] - desc_data = data[offset + 2:offset + 2 + desc_len] - offset += 2 + desc_len - - if desc_tag == 0x48 and len(desc_data) >= 1: - # service_descriptor - service_type = desc_data[0] - pos = 1 - - # Provider name - if pos < len(desc_data): - prov_len = desc_data[pos] - pos += 1 - if pos + prov_len <= len(desc_data): - provider_name = _decode_dvb_string(desc_data[pos:pos + prov_len]) - pos += prov_len - - # Service name - if pos < len(desc_data): - svc_len = desc_data[pos] - pos += 1 - if pos + svc_len <= len(desc_data): - service_name = _decode_dvb_string(desc_data[pos:pos + svc_len]) - - # Advance past any unprocessed descriptor bytes - offset = max(offset, desc_end) - - services.append({ - "service_id": service_id, - "service_type": service_type, - "service_name": service_name, - "provider_name": provider_name, - "eit_schedule": eit_schedule, - "eit_present": eit_present, - "running_status": running_status, - "free_ca": free_ca, - }) - - return { - "table_id": section["table_id"], - "transport_stream_id": transport_stream_id, - "original_network_id": original_network_id, - "version": section["version"], - "services": services, - } - - -def parse_nit(section: dict) -> dict: - """ - Parse a Network Information Table section. - - Table IDs: 0x40 = NIT actual network, - 0x41 = NIT other network. - Carried on PID 0x0010. - - Returns dict with: - network_id - network ID from the table extension - network_name - decoded network name string (from descriptor 0x40) - transports - list of transport dicts, each containing: - ts_id - transport stream ID - original_network_id - ONID - frequency_ghz - satellite frequency in GHz (from 0x43) - polarization - string: 'H', 'V', 'L', or 'R' - symbol_rate - symbol rate in sps - fec - FEC inner code rate string - orbital_position - orbital position in degrees (+ east, - west) - modulation - modulation string - roll_off - roll-off factor string - - Descriptor parsing: looks for tag 0x43 (satellite_delivery_system_descriptor) - which is 11 bytes of BCD-encoded satellite parameters, and tag 0x40 - (network_name_descriptor) for the network name. - """ - if section is None: - return None - if section["table_id"] not in (0x40, 0x41): - return None - if not section.get("section_syntax"): - return None - - network_id = section["table_id_ext"] - data = section.get("data", b'') - - if len(data) < 2: - return None - - # Network descriptors loop - network_desc_length = ((data[0] & 0x0F) << 8) | data[1] - offset = 2 - - network_name = "" - - nd_end = offset + network_desc_length - if nd_end > len(data): - nd_end = len(data) - - while offset + 2 <= nd_end: - desc_tag = data[offset] - desc_len = data[offset + 1] - desc_data = data[offset + 2:offset + 2 + desc_len] - offset += 2 + desc_len - - if desc_tag == 0x40: - # network_name_descriptor - network_name = _decode_dvb_string(desc_data) - - offset = nd_end - - # Transport stream loop - if offset + 2 > len(data): - return { - "table_id": section["table_id"], - "network_id": network_id, - "network_name": network_name, - "version": section["version"], - "transports": [], - } - - ts_loop_length = ((data[offset] & 0x0F) << 8) | data[offset + 1] - offset += 2 - - transports = [] - ts_end = offset + ts_loop_length - if ts_end > len(data): - ts_end = len(data) - - while offset + 6 <= ts_end: - ts_id = (data[offset] << 8) | data[offset + 1] - original_network_id = (data[offset + 2] << 8) | data[offset + 3] - td_length = ((data[offset + 4] & 0x0F) << 8) | data[offset + 5] - offset += 6 - - # Parse transport descriptors - frequency_ghz = 0.0 - polarization = "" - symbol_rate = 0 - fec = "" - orbital_position = 0.0 - modulation = "" - roll_off = "" - - td_end = offset + td_length - if td_end > ts_end: - td_end = ts_end - - while offset + 2 <= td_end: - desc_tag = data[offset] - desc_len = data[offset + 1] - desc_data = data[offset + 2:offset + 2 + desc_len] - offset += 2 + desc_len - - if desc_tag == 0x43 and len(desc_data) >= 11: - # satellite_delivery_system_descriptor (11 bytes BCD) - frequency_ghz = _bcd_freq(desc_data[0:4]) - orbital_position = _bcd_orbital(desc_data[4:6]) - # Byte 6: west/east flag (bit 7), polarization (bits 6-5), - # roll-off (bits 4-3), modulation system (bit 2), - # modulation type (bits 1-0) - flag_byte = desc_data[6] - if not (flag_byte & 0x80): - orbital_position = -orbital_position # West - pol_bits = (flag_byte >> 5) & 0x03 - polarization = ["H", "V", "L", "R"][pol_bits] - ro_bits = (flag_byte >> 3) & 0x03 - roll_off = ["0.35", "0.25", "0.20", "reserved"][ro_bits] - mod_sys = (flag_byte >> 2) & 0x01 - mod_type = flag_byte & 0x03 - if mod_sys == 0: - modulation = ["auto", "QPSK", "8PSK", "16QAM"][mod_type] - else: - modulation = ["auto", "QPSK", "8PSK", "16APSK"][mod_type] - symbol_rate = _bcd_sr(desc_data[7:11]) - fec_inner = desc_data[10] & 0x0F - fec = _fec_inner_str(fec_inner) - - offset = max(offset, td_end) - - transports.append({ - "ts_id": ts_id, - "original_network_id": original_network_id, - "frequency_ghz": frequency_ghz, - "polarization": polarization, - "symbol_rate": symbol_rate, - "fec": fec, - "orbital_position": orbital_position, - "modulation": modulation, - "roll_off": roll_off, - }) - - return { - "table_id": section["table_id"], - "network_id": network_id, - "network_name": network_name, - "version": section["version"], - "transports": transports, - } - - -def _decode_dvb_string(data: bytes) -> str: - """ - Decode a DVB text string per EN 300 468 Annex A. - - If the first byte is a character table selector (0x01-0x1F), - select the appropriate encoding. Otherwise assume ISO 8859-1. - """ - if not data: - return "" - - first = data[0] - if first < 0x20: - # Character table selector byte - if first == 0x01: - return data[1:].decode('iso-8859-5', errors='replace') - elif first == 0x02: - return data[1:].decode('iso-8859-6', errors='replace') - elif first == 0x03: - return data[1:].decode('iso-8859-7', errors='replace') - elif first == 0x04: - return data[1:].decode('iso-8859-8', errors='replace') - elif first == 0x05: - return data[1:].decode('iso-8859-9', errors='replace') - elif first == 0x06: - return data[1:].decode('iso-8859-10', errors='replace') - elif first == 0x07: - return data[1:].decode('iso-8859-11', errors='replace') - elif first == 0x09: - return data[1:].decode('iso-8859-13', errors='replace') - elif first == 0x0A: - return data[1:].decode('iso-8859-14', errors='replace') - elif first == 0x0B: - return data[1:].decode('iso-8859-15', errors='replace') - elif first == 0x10: - # Two more selector bytes follow - if len(data) >= 3: - sub = (data[1] << 8) | data[2] - try: - return data[3:].decode(f'iso-8859-{sub}', errors='replace') - except (LookupError, ValueError): - return data[3:].decode('iso-8859-1', errors='replace') - return data[1:].decode('iso-8859-1', errors='replace') - elif first == 0x11: - return data[1:].decode('utf-16-be', errors='replace') - elif first == 0x13: - return data[1:].decode('gb2312', errors='replace') - elif first == 0x15: - return data[1:].decode('utf-8', errors='replace') - else: - # Unknown selector, skip it - return data[1:].decode('iso-8859-1', errors='replace') - - return data.decode('iso-8859-1', errors='replace') - - -def _bcd_freq(data: bytes) -> float: - """ - Decode 4-byte BCD frequency from satellite_delivery_system_descriptor. - Per EN 300 468, the 8 BCD digits encode the frequency such that - the integer value divided by 10^5 yields GHz. - e.g., 0x11 0x72 0x75 0x00 -> digits 11727500 -> 11.72750 GHz. - """ - value = 0 - for b in data: - value = value * 100 + ((b >> 4) & 0x0F) * 10 + (b & 0x0F) - return value / 1_000_000.0 - - -def _bcd_orbital(data: bytes) -> float: - """ - Decode 2-byte BCD orbital position per EN 300 468. - 4 BCD digits: XX.XX degrees (2 integer + 2 fractional). - e.g., 0x28 0x20 = 28.20 degrees (Astra 28.2E). - """ - value = 0 - for b in data: - value = value * 100 + ((b >> 4) & 0x0F) * 10 + (b & 0x0F) - return value / 100.0 - - -def _bcd_sr(data: bytes) -> int: - """ - Decode symbol rate from satellite_delivery_system_descriptor. - 4 bytes: upper 28 bits = 7 BCD digits of symbol rate (XXXX.XXX Msps), - lower 4 bits = FEC inner code (handled separately by caller). - e.g., 0x00 0x27 0x50 0x03 -> digits 0027500 -> 27.500 Msps = 27,500,000 sps. - """ - # Extract 7 BCD digits from the upper 28 bits (ignore last nibble = FEC) - value = 0 - for b in data[:4]: - value = value * 100 + ((b >> 4) & 0x0F) * 10 + (b & 0x0F) - # value now has 8 BCD digits decoded; drop the last one (FEC nibble) - value = value // 10 - # value = XXXX.XXX Msps as integer XXXXXXX, divide by 1000 for Msps - # Multiply by 1000 to get sps: (value / 1000) * 1e6 = value * 1000 - return value * 1000 - - -def _fec_inner_str(code: int) -> str: - """Convert FEC inner code rate nibble to string.""" - fec_map = { - 0: "not defined", - 1: "1/2", - 2: "2/3", - 3: "3/4", - 4: "5/6", - 5: "7/8", - 6: "8/9", - 7: "3/5", - 8: "4/5", - 9: "9/10", - 15: "none", - } - return fec_map.get(code, f"reserved({code})") - - -def open_input(path: str): - """Open TS input from a file path or stdin ('-').""" - if path == '-': - return sys.stdin.buffer - if not os.path.exists(path): - print(f"File not found: {path}") - sys.exit(1) - return open(path, 'rb') - - -def format_pid(pid: int, known: dict = None) -> str: - """Format a PID with its known name if available.""" - if known is None: - known = KNOWN_PIDS - name = known.get(pid, "") - if name: - return f"0x{pid:04X} ({name})" - return f"0x{pid:04X}" - - -# -- Subcommand handlers -- - -def cmd_analyze(args: argparse.Namespace) -> None: - """Full transport stream analysis.""" - source = open_input(args.input) - reader = TSReader(source, verbose=args.verbose) - - pid_counts = {} - pid_cc = {} # Last continuity counter per PID - cc_errors = {} - tei_count = 0 - scrambled_count = 0 - total_packets = 0 - first_pcr = None - first_pcr_pkt = 0 - last_pcr = None - last_pcr_pkt = 0 - - print(f"MPEG-2 Transport Stream Analysis") - print(f"{'=' * 60}") - if args.input != '-': - file_size = os.path.getsize(args.input) - print(f"File: {args.input} ({file_size:,} bytes)") - print() - - try: - for pkt in reader.iter_packets(max_packets=args.max_packets): - total_packets += 1 - pid = pkt.pid - - # PID counting - pid_counts[pid] = pid_counts.get(pid, 0) + 1 - - # TEI - if pkt.tei: - tei_count += 1 - - # Scrambling - if pkt.scrambling != 0: - scrambled_count += 1 - - # Continuity counter check (only for PIDs carrying payload) - if pkt.adaptation & 0x01 and pid != 0x1FFF: - if pid in pid_cc: - expected = (pid_cc[pid] + 1) & 0x0F - if pkt.continuity != expected and pkt.continuity != pid_cc[pid]: - cc_errors[pid] = cc_errors.get(pid, 0) + 1 - pid_cc[pid] = pkt.continuity - - # PCR extraction for bitrate calculation - if pkt.has_pcr(): - pcr = pkt.get_pcr() - if pcr >= 0: - if first_pcr is None: - first_pcr = pcr - first_pcr_pkt = total_packets - last_pcr = pcr - last_pcr_pkt = total_packets - except KeyboardInterrupt: - print("\n (interrupted)") - finally: - if source is not sys.stdin.buffer: - source.close() - - if total_packets == 0: - print("No valid TS packets found.") - return - - # Summary - if reader.sync_offset > 0: - print(f"Sync offset: {reader.sync_offset} bytes (skipped leading garbage)") - print(f"Total packets: {total_packets:,}") - print(f"Total bytes: {total_packets * TS_PACKET_SIZE:,}") - print(f"Unique PIDs: {len(pid_counts)}") - print(f"TEI errors: {tei_count}") - print(f"Scrambled: {scrambled_count}") - - # Bitrate - if first_pcr is not None and last_pcr is not None and last_pcr != first_pcr: - pcr_delta = last_pcr - first_pcr - pkt_delta = last_pcr_pkt - first_pcr_pkt - if pcr_delta > 0 and pkt_delta > 0: - duration = pcr_delta / 27_000_000.0 # PCR is 27 MHz clock - byte_count = pkt_delta * TS_PACKET_SIZE - bitrate = (byte_count * 8) / duration - if bitrate >= 1e6: - rate_str = f"{bitrate / 1e6:.2f} Mbps" - else: - rate_str = f"{bitrate / 1e3:.1f} kbps" - print(f"Duration: {duration:.2f}s (from PCR)") - print(f"Bitrate: {rate_str} (PCR-based)") - elif args.input != '-': - # File size estimate - file_size = os.path.getsize(args.input) - print(f"Bitrate: (no PCR found, cannot calculate from timing)") - - # PID table - print(f"\n{'=' * 60}") - print(f"PID Distribution") - print(f"{'=' * 60}") - print(f" {'PID':>6} {'Count':>10} {'%':>7} {'CC Err':>6} Name") - print(f" {'---':>6} {'-----':>10} {'--':>7} {'------':>6} ----") - - for pid in sorted(pid_counts.keys()): - count = pid_counts[pid] - pct = (count / total_packets) * 100 - cc_err = cc_errors.get(pid, 0) - name = KNOWN_PIDS.get(pid, "") - cc_str = str(cc_err) if cc_err > 0 else "-" - print(f" 0x{pid:04X} {count:>10,} {pct:>6.2f}% {cc_str:>6} {name}") - - # CC error summary - total_cc_errors = sum(cc_errors.values()) - if total_cc_errors > 0: - print(f"\nContinuity errors: {total_cc_errors} total across " - f"{len(cc_errors)} PID(s)") - - -def cmd_pids(args: argparse.Namespace) -> None: - """Quick PID summary table.""" - source = open_input(args.input) - reader = TSReader(source, verbose=args.verbose) - - pid_counts = {} - total = 0 - - try: - for pkt in reader.iter_packets(max_packets=args.max_packets): - total += 1 - pid_counts[pkt.pid] = pid_counts.get(pkt.pid, 0) + 1 - except KeyboardInterrupt: - pass - finally: - if source is not sys.stdin.buffer: - source.close() - - if total == 0: - print("No TS packets found.") - return - - print(f"PID Table ({total:,} packets)") - print(f"{'=' * 50}") - print(f" {'PID':>6} {'Count':>10} {'%':>7} Name") - print(f" {'---':>6} {'-----':>10} {'--':>7} ----") - - for pid in sorted(pid_counts.keys()): - count = pid_counts[pid] - pct = (count / total) * 100 - name = KNOWN_PIDS.get(pid, "") - print(f" 0x{pid:04X} {count:>10,} {pct:>6.2f}% {name}") - - -def cmd_pat(args: argparse.Namespace) -> None: - """Parse and display the Program Association Table.""" - source = open_input(args.input) - reader = TSReader(source, verbose=args.verbose) - psi = PSIParser() - pat_found = False - - try: - for pkt in reader.iter_packets(): - if pkt.pid != 0x0000: - continue - section = psi.feed(pkt) - if section is None: - continue - pat = parse_pat(section) - if pat is None: - continue - - pat_found = True - print(f"Program Association Table (PAT)") - print(f"{'=' * 50}") - print(f" Transport Stream ID: 0x{pat['transport_stream_id']:04X} " - f"({pat['transport_stream_id']})") - print(f" Version: {pat['version']}") - print(f" Programs: {len(pat['programs'])}") - print() - print(f" {'Program':>10} {'PMT PID':>10} Note") - print(f" {'-------':>10} {'-------':>10} ----") - for prog, pmt_pid in sorted(pat['programs'].items()): - note = "NIT" if prog == 0 else "" - print(f" {prog:>10} 0x{pmt_pid:04X} {note}") - break - - except KeyboardInterrupt: - pass - finally: - if source is not sys.stdin.buffer: - source.close() - - if not pat_found: - print("No PAT (PID 0x0000) found in stream.") - - -def cmd_pmt(args: argparse.Namespace) -> None: - """Parse and display Program Map Tables.""" - source = open_input(args.input) - reader = TSReader(source, verbose=args.verbose) - psi_pat = PSIParser() - psi_pmt = PSIParser() - - pat = None - pmt_pids = set() - pmts_found = {} - - try: - for pkt in reader.iter_packets(): - # First, collect PAT to learn PMT PIDs - if pkt.pid == 0x0000 and pat is None: - section = psi_pat.feed(pkt) - if section is not None: - pat = parse_pat(section) - if pat is not None: - for prog, pid in pat['programs'].items(): - if prog != 0: # Skip NIT reference - pmt_pids.add(pid) - - # Then collect PMT sections - if pkt.pid in pmt_pids and pkt.pid not in pmts_found: - section = psi_pmt.feed(pkt) - if section is not None: - pmt = parse_pmt(section) - if pmt is not None: - pmts_found[pkt.pid] = pmt - - # Done when we have all PMTs - if pat is not None and len(pmts_found) >= len(pmt_pids): - break - - except KeyboardInterrupt: - pass - finally: - if source is not sys.stdin.buffer: - source.close() - - if pat is None: - print("No PAT found -- cannot locate PMTs.") - return - - if not pmts_found: - print("PAT found but no PMT sections could be parsed.") - return - - print(f"Program Map Tables") - print(f"{'=' * 60}") - print(f"Transport Stream ID: 0x{pat['transport_stream_id']:04X}") - print() - - for pmt_pid in sorted(pmts_found.keys()): - pmt = pmts_found[pmt_pid] - print(f" Program {pmt['program_number']} (PMT PID 0x{pmt_pid:04X}, " - f"version {pmt['version']})") - print(f" PCR PID: 0x{pmt['pcr_pid']:04X}") - print(f" Streams:") - print(f" {'Type':>6} {'PID':>6} Description") - print(f" {'----':>6} {'---':>6} -----------") - for s in pmt['streams']: - print(f" 0x{s['stream_type']:02X} 0x{s['elementary_pid']:04X} " - f"{s['type_name']}") - print() - - -def cmd_dump(args: argparse.Namespace) -> None: - """Hex dump of individual TS packets.""" - source = open_input(args.input) - reader = TSReader(source, verbose=args.verbose) - filter_pid = args.pid - max_count = args.count - shown = 0 - - try: - for pkt in reader.iter_packets(): - if filter_pid is not None and pkt.pid != filter_pid: - continue - - shown += 1 - scrambling_str = ["none", "reserved", "even key", "odd key"][pkt.scrambling] - adapt_str = ["reserved", "payload only", "adapt only", "adapt+payload"][pkt.adaptation] - - print(f"Packet #{shown}") - print(f" PID: 0x{pkt.pid:04X} ({KNOWN_PIDS.get(pkt.pid, '')})") - print(f" TEI: {int(pkt.tei)} PUSI: {int(pkt.pusi)} " - f"Priority: {int(pkt.priority)}") - print(f" Scrambling: {scrambling_str} " - f"Adaptation: {adapt_str} CC: {pkt.continuity}") - - if pkt.adaptation_field is not None and len(pkt.adaptation_field) > 1: - af_len = pkt.adaptation_field[0] - af_flags = pkt.adaptation_field[1] if len(pkt.adaptation_field) > 1 else 0 - flags = [] - if af_flags & 0x80: flags.append("discontinuity") - if af_flags & 0x40: flags.append("random_access") - if af_flags & 0x20: flags.append("ES_priority") - if af_flags & 0x10: flags.append("PCR") - if af_flags & 0x08: flags.append("OPCR") - if af_flags & 0x04: flags.append("splice_point") - if af_flags & 0x02: flags.append("private_data") - if af_flags & 0x01: flags.append("extension") - print(f" Adaptation field: {af_len} bytes, " - f"flags=[{', '.join(flags) if flags else 'none'}]") - if pkt.has_pcr(): - pcr = pkt.get_pcr() - pcr_secs = pcr / 27_000_000.0 - print(f" PCR: {pcr} ({pcr_secs:.6f}s)") - - # Hex dump - data = pkt.raw - print(f" Hex:") - for row_off in range(0, len(data), 16): - row = data[row_off:row_off + 16] - hex_part = ' '.join(f'{b:02X}' for b in row) - ascii_part = ''.join(chr(b) if 0x20 <= b < 0x7F else '.' for b in row) - print(f" {row_off:04X}: {hex_part:<48} {ascii_part}") - print() - - if max_count and shown >= max_count: - break - - except KeyboardInterrupt: - pass - finally: - if source is not sys.stdin.buffer: - source.close() - - if shown == 0: - if filter_pid is not None: - print(f"No packets found with PID 0x{filter_pid:04X}") - else: - print("No TS packets found.") - - -def cmd_monitor(args: argparse.Namespace) -> None: - """Live stream monitoring from stdin or file.""" - source = open_input(args.input) - reader = TSReader(source, verbose=args.verbose) - - pid_counts = {} - known_pids = set() - cc_last = {} - cc_errors = 0 - tei_count = 0 - total_packets = 0 - interval_packets = 0 - start_time = time.time() - last_report = start_time - - print(f"MPEG-2 TS Live Monitor") - print(f"{'=' * 60}") - print(f"Ctrl-C to stop\n") - - try: - for pkt in reader.iter_packets(): - total_packets += 1 - interval_packets += 1 - pid = pkt.pid - - pid_counts[pid] = pid_counts.get(pid, 0) + 1 - - # New PID detection - if pid not in known_pids: - known_pids.add(pid) - name = KNOWN_PIDS.get(pid, "") - label = f" ({name})" if name else "" - elapsed = time.time() - start_time - print(f" [{elapsed:>7.1f}s] New PID: 0x{pid:04X}{label}") - - # TEI - if pkt.tei: - tei_count += 1 - elapsed = time.time() - start_time - print(f" [{elapsed:>7.1f}s] TEI error on PID 0x{pid:04X}") - - # CC check - if pkt.adaptation & 0x01 and pid != 0x1FFF: - if pid in cc_last: - expected = (cc_last[pid] + 1) & 0x0F - if pkt.continuity != expected and pkt.continuity != cc_last[pid]: - cc_errors += 1 - elapsed = time.time() - start_time - print(f" [{elapsed:>7.1f}s] CC error PID 0x{pid:04X}: " - f"expected {expected}, got {pkt.continuity}") - cc_last[pid] = pkt.continuity - - # Periodic status - now = time.time() - if now - last_report >= 1.0: - elapsed = now - start_time - total_bytes = total_packets * TS_PACKET_SIZE - bitrate = (interval_packets * TS_PACKET_SIZE * 8) - if bitrate >= 1e6: - rate_str = f"{bitrate / 1e6:.2f} Mbps" - else: - rate_str = f"{bitrate / 1e3:.1f} kbps" - sys.stderr.write( - f"\r {total_packets:>10,} pkts " - f"{total_bytes:>12,} bytes " - f"{rate_str:>12} " - f"PIDs:{len(known_pids):>3} " - f"CCerr:{cc_errors} " - f"TEI:{tei_count} " - f"({elapsed:.0f}s) " - ) - sys.stderr.flush() - interval_packets = 0 - last_report = now - - except KeyboardInterrupt: - pass - finally: - if source is not sys.stdin.buffer: - source.close() - - elapsed = time.time() - start_time - total_bytes = total_packets * TS_PACKET_SIZE - print(f"\n\nMonitor Summary") - print(f"{'=' * 40}") - print(f" Duration: {elapsed:.1f}s") - print(f" Packets: {total_packets:,}") - print(f" Bytes: {total_bytes:,}") - print(f" Unique PIDs: {len(known_pids)}") - print(f" CC errors: {cc_errors}") - print(f" TEI errors: {tei_count}") - - if elapsed > 0: - avg_bitrate = (total_bytes * 8) / elapsed - if avg_bitrate >= 1e6: - print(f" Avg bitrate: {avg_bitrate / 1e6:.2f} Mbps") - else: - print(f" Avg bitrate: {avg_bitrate / 1e3:.1f} kbps") - - -# -- CLI -- - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - description="MPEG-2 Transport Stream analyzer for Genpix SkyWalker-1", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -examples: - %(prog)s capture.ts - %(prog)s analyze capture.ts - %(prog)s pids capture.ts - %(prog)s pat capture.ts - %(prog)s pmt capture.ts - %(prog)s dump capture.ts --pid 0x100 --count 5 - %(prog)s monitor - - tune.py stream --stdout | %(prog)s monitor - -""") - parser.add_argument('-v', '--verbose', action='store_true', - help="Show sync search details and debug info") - - sub = parser.add_subparsers(dest='command') - - # analyze (default) - p_analyze = sub.add_parser('analyze', help="Full stream analysis (default)") - p_analyze.add_argument('input', help="TS file path or '-' for stdin") - p_analyze.add_argument('--max-packets', type=int, default=0, - help="Max packets to analyze (0 = all)") - - # pids - p_pids = sub.add_parser('pids', help="Quick PID summary table") - p_pids.add_argument('input', help="TS file path or '-' for stdin") - p_pids.add_argument('--max-packets', type=int, default=0, - help="Max packets to analyze (0 = all)") - - # pat - p_pat = sub.add_parser('pat', help="Parse Program Association Table") - p_pat.add_argument('input', help="TS file path or '-' for stdin") - - # pmt - p_pmt = sub.add_parser('pmt', help="Parse Program Map Tables") - p_pmt.add_argument('input', help="TS file path or '-' for stdin") - - # dump - p_dump = sub.add_parser('dump', help="Hex dump of TS packets") - p_dump.add_argument('input', help="TS file path or '-' for stdin") - p_dump.add_argument('--pid', type=lambda x: int(x, 0), default=None, - help="Filter by PID (hex: 0x100 or decimal: 256)") - p_dump.add_argument('--count', type=int, default=10, - help="Max packets to dump (default: 10)") - - # monitor - p_monitor = sub.add_parser('monitor', help="Live stream monitoring") - p_monitor.add_argument('input', help="TS file path or '-' for stdin") - - return parser - - -def main(): - parser = build_parser() - - # Handle bare filename without subcommand: default to 'analyze' - # Insert 'analyze' after any global flags but before the filename - subcmds = {'analyze', 'pids', 'pat', 'pmt', 'dump', 'monitor'} - argv = sys.argv[1:] - if argv: - first_pos = None - insert_idx = 0 - for i, a in enumerate(argv): - if not a.startswith('-'): - first_pos = a - insert_idx = i - break - # Skip flag and its value if it takes one (currently none do) - insert_idx = i + 1 - if first_pos is not None and first_pos not in subcmds: - argv.insert(insert_idx, 'analyze') - - args = parser.parse_args(argv) - - if not args.command: - parser.print_help() - sys.exit(1) - - dispatch = { - 'analyze': cmd_analyze, - 'pids': cmd_pids, - 'pat': cmd_pat, - 'pmt': cmd_pmt, - 'dump': cmd_dump, - 'monitor': cmd_monitor, - } - - handler = dispatch.get(args.command) - if handler is None: - parser.print_help() - sys.exit(1) - - handler(args) - - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 +""" +Genpix SkyWalker-1 MPEG-2 Transport Stream analyzer. + +Parses and analyzes 188-byte MPEG-2 TS packets from .ts files captured +by tune.py, stdin pipes, or any standard transport stream source. + +Supports PID analysis, PAT/PMT parsing, continuity counter checking, +scrambling detection, hex packet dumps, and live stream monitoring. + +Reference: ISO/IEC 13818-1 (MPEG-2 Systems) +TS packet: 188 bytes, sync byte 0x47 +""" + +import sys +import struct +import argparse +import time +import os + +TS_PACKET_SIZE = 188 +TS_SYNC_BYTE = 0x47 + +# Well-known PID assignments (ISO 13818-1 Table 2-3) +KNOWN_PIDS = { + 0x0000: "PAT", + 0x0001: "CAT", + 0x0002: "TSDT", + 0x0010: "NIT/ST", + 0x0011: "SDT/BAT/ST", + 0x0012: "EIT/ST", + 0x0013: "RST/ST", + 0x0014: "TDT/TOT/ST", + 0x001E: "DIT", + 0x001F: "SIT", + 0x1FFF: "Null", +} + +# Stream type identifiers (ISO 13818-1 Table 2-36) +STREAM_TYPES = { + 0x00: "Reserved", + 0x01: "MPEG-1 Video (11172-2)", + 0x02: "MPEG-2 Video (13818-2)", + 0x03: "MPEG-1 Audio (11172-3)", + 0x04: "MPEG-2 Audio (13818-3)", + 0x05: "Private Sections (13818-1)", + 0x06: "PES Private Data", + 0x07: "MHEG", + 0x08: "DSM-CC", + 0x09: "H.222.1", + 0x0A: "DSM-CC Type A", + 0x0B: "DSM-CC Type B", + 0x0C: "DSM-CC Type C", + 0x0D: "DSM-CC Type D", + 0x0E: "Auxiliary", + 0x0F: "MPEG-2 AAC Audio", + 0x10: "MPEG-4 Visual", + 0x11: "MPEG-4 AAC Audio (LATM)", + 0x15: "Metadata in PES", + 0x1B: "H.264/AVC Video", + 0x24: "H.265/HEVC Video", + 0x42: "AVS Video", + 0x81: "AC-3 Audio (ATSC)", + 0x82: "DTS Audio", + 0x83: "Dolby TrueHD", + 0x84: "Dolby Digital Plus (EAC-3)", + 0x85: "DTS-HD", + 0x86: "DTS-HD Master Audio", + 0x87: "EAC-3 Audio (ATSC)", + 0xEA: "VC-1 Video", +} + + +class TSPacket: + """Parsed MPEG-2 transport stream packet header.""" + + __slots__ = ( + 'sync', 'tei', 'pusi', 'priority', 'pid', + 'scrambling', 'adaptation', 'continuity', + 'adaptation_field', 'payload', 'raw', + ) + + def __init__(self, data: bytes): + if len(data) != TS_PACKET_SIZE: + raise ValueError(f"Packet must be {TS_PACKET_SIZE} bytes, got {len(data)}") + + self.raw = data + self.sync = data[0] + self.tei = bool(data[1] & 0x80) + self.pusi = bool(data[1] & 0x40) + self.priority = bool(data[1] & 0x20) + self.pid = ((data[1] & 0x1F) << 8) | data[2] + self.scrambling = (data[3] >> 6) & 0x03 + self.adaptation = (data[3] >> 4) & 0x03 + self.continuity = data[3] & 0x0F + + # Parse adaptation field and payload boundaries + offset = 4 + self.adaptation_field = None + self.payload = None + + if self.adaptation & 0x02: + # Adaptation field present + if offset < TS_PACKET_SIZE: + af_len = data[offset] + af_end = offset + 1 + af_len + if af_end <= TS_PACKET_SIZE: + self.adaptation_field = data[offset:af_end] + offset = af_end + + if self.adaptation & 0x01: + # Payload present + if offset < TS_PACKET_SIZE: + self.payload = data[offset:] + + def has_pcr(self) -> bool: + """Check if adaptation field contains a PCR.""" + if self.adaptation_field is None or len(self.adaptation_field) < 7: + return False + af_flags = self.adaptation_field[1] if len(self.adaptation_field) > 1 else 0 + return bool(af_flags & 0x10) + + def get_pcr(self) -> int: + """Extract PCR value (in 27 MHz clock ticks). Returns -1 if no PCR.""" + if not self.has_pcr(): + return -1 + # PCR is 6 bytes starting at adaptation_field[2] + af = self.adaptation_field + pcr_base = (af[2] << 25) | (af[3] << 17) | (af[4] << 9) | \ + (af[5] << 1) | ((af[6] >> 7) & 0x01) + pcr_ext = ((af[6] & 0x01) << 8) | af[7] + return pcr_base * 300 + pcr_ext + + +class TSReader: + """Reads TS packets from a file or stream, handling sync alignment.""" + + def __init__(self, source, verbose: bool = False): + self.source = source + self.verbose = verbose + self.offset = 0 + self._sync_offset = -1 + + def find_sync(self, data: bytes) -> int: + """Find sync byte alignment in raw data. Returns byte offset or -1.""" + # Need at least 3 consecutive sync bytes to confirm alignment + for i in range(min(len(data), TS_PACKET_SIZE)): + if data[i] != TS_SYNC_BYTE: + continue + # Check for consecutive sync bytes at 188-byte intervals + ok = True + for check in range(1, 4): + pos = i + check * TS_PACKET_SIZE + if pos >= len(data): + # Not enough data to confirm, accept if at least one more matches + if check >= 2: + break + ok = False + break + if data[pos] != TS_SYNC_BYTE: + ok = False + break + if ok: + return i + return -1 + + def iter_packets(self, max_packets: int = 0): + """Yield TSPacket objects from the source.""" + buf = b'' + synced = False + count = 0 + + while True: + chunk = self.source.read(65536) + if not chunk: + break + buf += chunk + + if not synced: + sync_off = self.find_sync(buf) + if sync_off < 0: + # Keep last 187 bytes in case sync straddles chunk boundary + if len(buf) > TS_PACKET_SIZE * 4: + buf = buf[-(TS_PACKET_SIZE - 1):] + continue + self._sync_offset = sync_off + self.offset + if self.verbose and sync_off > 0: + print(f" Sync found at byte offset {sync_off}", file=sys.stderr) + buf = buf[sync_off:] + synced = True + + while len(buf) >= TS_PACKET_SIZE: + pkt_data = buf[:TS_PACKET_SIZE] + buf = buf[TS_PACKET_SIZE:] + + if pkt_data[0] != TS_SYNC_BYTE: + # Lost sync, try to re-acquire + synced = False + if self.verbose: + print(f" Sync lost, re-scanning...", file=sys.stderr) + break + + count += 1 + yield TSPacket(pkt_data) + + if max_packets and count >= max_packets: + return + + self.offset += len(chunk) + + @property + def sync_offset(self) -> int: + return self._sync_offset + + +class PSIParser: + """Parse PSI sections from TS packet payloads.""" + + def __init__(self): + self._section_bufs = {} # pid -> accumulated bytes + + def feed(self, pkt: TSPacket) -> dict: + """Feed a packet, return parsed section dict or None.""" + if pkt.payload is None: + return None + + pid = pkt.pid + payload = pkt.payload + + if pkt.pusi: + # Payload Unit Start Indicator set + if len(payload) < 1: + return None + pointer = payload[0] + payload = payload[1 + pointer:] + self._section_bufs[pid] = payload + elif pid in self._section_bufs: + self._section_bufs[pid] += payload + else: + return None + + return self._try_parse(pid) + + def _try_parse(self, pid: int) -> dict: + """Try to parse a complete section from the buffer.""" + buf = self._section_bufs.get(pid, b'') + if len(buf) < 3: + return None + + table_id = buf[0] + section_length = ((buf[1] & 0x0F) << 8) | buf[2] + total_len = 3 + section_length + + if len(buf) < total_len: + return None # Incomplete, wait for more data + + section = buf[:total_len] + # Clear buffer for next section + self._section_bufs[pid] = buf[total_len:] + + if section_length < 5: + return None + + result = { + "table_id": table_id, + "section_syntax": bool(buf[1] & 0x80), + "section_length": section_length, + "raw": section, + } + + if result["section_syntax"]: + result["table_id_ext"] = (section[3] << 8) | section[4] + result["version"] = (section[5] >> 1) & 0x1F + result["current_next"] = section[5] & 0x01 + result["section_number"] = section[6] + result["last_section_number"] = section[7] + result["data"] = section[8:-4] + result["crc32"] = struct.unpack_from('>I', section, total_len - 4)[0] + + return result + + +def parse_pat(section: dict) -> dict: + """Parse a Program Association Table section.""" + if section is None or section["table_id"] != 0x00: + return None + + transport_stream_id = section["table_id_ext"] + data = section["data"] + programs = {} + + for i in range(0, len(data), 4): + if i + 4 > len(data): + break + prog_num = (data[i] << 8) | data[i + 1] + pmt_pid = ((data[i + 2] & 0x1F) << 8) | data[i + 3] + programs[prog_num] = pmt_pid + + return { + "transport_stream_id": transport_stream_id, + "version": section["version"], + "programs": programs, + } + + +def parse_pmt(section: dict) -> dict: + """Parse a Program Map Table section.""" + if section is None or section["table_id"] != 0x02: + return None + + program_number = section["table_id_ext"] + data = section["raw"] + + if len(data) < 12: + return None + + pcr_pid = ((data[8] & 0x1F) << 8) | data[9] + prog_info_len = ((data[10] & 0x0F) << 8) | data[11] + + offset = 12 + prog_info_len + streams = [] + + while offset + 5 <= len(data) - 4: # -4 for CRC + stream_type = data[offset] + elementary_pid = ((data[offset + 1] & 0x1F) << 8) | data[offset + 2] + es_info_len = ((data[offset + 3] & 0x0F) << 8) | data[offset + 4] + + streams.append({ + "stream_type": stream_type, + "elementary_pid": elementary_pid, + "es_info_length": es_info_len, + "type_name": STREAM_TYPES.get(stream_type, f"Unknown (0x{stream_type:02X})"), + }) + offset += 5 + es_info_len + + return { + "program_number": program_number, + "version": section["version"], + "pcr_pid": pcr_pid, + "streams": streams, + } + + +def parse_sdt(section: dict) -> dict: + """ + Parse a Service Description Table section. + + Table IDs: 0x42 = SDT actual transport stream, + 0x46 = SDT other transport stream. + Carried on PID 0x0011. + + Returns dict with: + transport_stream_id - TS ID from the table extension + original_network_id - ONID from bytes [0:2] of section data + services - list of service dicts, each containing: + service_id - program number + service_type - numeric type (1=digital TV, 2=digital radio, etc) + service_name - decoded service name string + provider_name - decoded provider name string + eit_schedule - bool, EIT schedule flag + eit_present - bool, EIT present/following flag + running_status - numeric running status + free_ca - bool, free/scrambled flag + + Descriptor parsing: looks for tag 0x48 (service_descriptor) which + encodes service_type (1 byte), provider_name_length + provider_name, + service_name_length + service_name. + """ + if section is None: + return None + if section["table_id"] not in (0x42, 0x46): + return None + if not section.get("section_syntax"): + return None + + transport_stream_id = section["table_id_ext"] + data = section.get("data", b'') + + if len(data) < 2: + return None + + original_network_id = (data[0] << 8) | data[1] + # Byte 2 is reserved_future_use + offset = 3 + + services = [] + while offset + 5 <= len(data): + service_id = (data[offset] << 8) | data[offset + 1] + # byte 2: EIT flags and running status + flags_byte = data[offset + 2] + eit_schedule = bool(flags_byte & 0x02) + eit_present = bool(flags_byte & 0x01) + + status_byte = data[offset + 3] + running_status = (status_byte >> 5) & 0x07 + free_ca = bool(status_byte & 0x10) + descriptors_loop_length = ((status_byte & 0x0F) << 8) | data[offset + 4] + + offset += 5 + + # Parse descriptors for this service + service_type = 0 + service_name = "" + provider_name = "" + + desc_end = offset + descriptors_loop_length + if desc_end > len(data): + desc_end = len(data) + + while offset + 2 <= desc_end: + desc_tag = data[offset] + desc_len = data[offset + 1] + desc_data = data[offset + 2:offset + 2 + desc_len] + offset += 2 + desc_len + + if desc_tag == 0x48 and len(desc_data) >= 1: + # service_descriptor + service_type = desc_data[0] + pos = 1 + + # Provider name + if pos < len(desc_data): + prov_len = desc_data[pos] + pos += 1 + if pos + prov_len <= len(desc_data): + provider_name = _decode_dvb_string(desc_data[pos:pos + prov_len]) + pos += prov_len + + # Service name + if pos < len(desc_data): + svc_len = desc_data[pos] + pos += 1 + if pos + svc_len <= len(desc_data): + service_name = _decode_dvb_string(desc_data[pos:pos + svc_len]) + + # Advance past any unprocessed descriptor bytes + offset = max(offset, desc_end) + + services.append({ + "service_id": service_id, + "service_type": service_type, + "service_name": service_name, + "provider_name": provider_name, + "eit_schedule": eit_schedule, + "eit_present": eit_present, + "running_status": running_status, + "free_ca": free_ca, + }) + + return { + "table_id": section["table_id"], + "transport_stream_id": transport_stream_id, + "original_network_id": original_network_id, + "version": section["version"], + "services": services, + } + + +def parse_nit(section: dict) -> dict: + """ + Parse a Network Information Table section. + + Table IDs: 0x40 = NIT actual network, + 0x41 = NIT other network. + Carried on PID 0x0010. + + Returns dict with: + network_id - network ID from the table extension + network_name - decoded network name string (from descriptor 0x40) + transports - list of transport dicts, each containing: + ts_id - transport stream ID + original_network_id - ONID + frequency_ghz - satellite frequency in GHz (from 0x43) + polarization - string: 'H', 'V', 'L', or 'R' + symbol_rate - symbol rate in sps + fec - FEC inner code rate string + orbital_position - orbital position in degrees (+ east, - west) + modulation - modulation string + roll_off - roll-off factor string + + Descriptor parsing: looks for tag 0x43 (satellite_delivery_system_descriptor) + which is 11 bytes of BCD-encoded satellite parameters, and tag 0x40 + (network_name_descriptor) for the network name. + """ + if section is None: + return None + if section["table_id"] not in (0x40, 0x41): + return None + if not section.get("section_syntax"): + return None + + network_id = section["table_id_ext"] + data = section.get("data", b'') + + if len(data) < 2: + return None + + # Network descriptors loop + network_desc_length = ((data[0] & 0x0F) << 8) | data[1] + offset = 2 + + network_name = "" + + nd_end = offset + network_desc_length + if nd_end > len(data): + nd_end = len(data) + + while offset + 2 <= nd_end: + desc_tag = data[offset] + desc_len = data[offset + 1] + desc_data = data[offset + 2:offset + 2 + desc_len] + offset += 2 + desc_len + + if desc_tag == 0x40: + # network_name_descriptor + network_name = _decode_dvb_string(desc_data) + + offset = nd_end + + # Transport stream loop + if offset + 2 > len(data): + return { + "table_id": section["table_id"], + "network_id": network_id, + "network_name": network_name, + "version": section["version"], + "transports": [], + } + + ts_loop_length = ((data[offset] & 0x0F) << 8) | data[offset + 1] + offset += 2 + + transports = [] + ts_end = offset + ts_loop_length + if ts_end > len(data): + ts_end = len(data) + + while offset + 6 <= ts_end: + ts_id = (data[offset] << 8) | data[offset + 1] + original_network_id = (data[offset + 2] << 8) | data[offset + 3] + td_length = ((data[offset + 4] & 0x0F) << 8) | data[offset + 5] + offset += 6 + + # Parse transport descriptors + frequency_ghz = 0.0 + polarization = "" + symbol_rate = 0 + fec = "" + orbital_position = 0.0 + modulation = "" + roll_off = "" + + td_end = offset + td_length + if td_end > ts_end: + td_end = ts_end + + while offset + 2 <= td_end: + desc_tag = data[offset] + desc_len = data[offset + 1] + desc_data = data[offset + 2:offset + 2 + desc_len] + offset += 2 + desc_len + + if desc_tag == 0x43 and len(desc_data) >= 11: + # satellite_delivery_system_descriptor (11 bytes BCD) + frequency_ghz = _bcd_freq(desc_data[0:4]) + orbital_position = _bcd_orbital(desc_data[4:6]) + # Byte 6: west/east flag (bit 7), polarization (bits 6-5), + # roll-off (bits 4-3), modulation system (bit 2), + # modulation type (bits 1-0) + flag_byte = desc_data[6] + if not (flag_byte & 0x80): + orbital_position = -orbital_position # West + pol_bits = (flag_byte >> 5) & 0x03 + polarization = ["H", "V", "L", "R"][pol_bits] + ro_bits = (flag_byte >> 3) & 0x03 + roll_off = ["0.35", "0.25", "0.20", "reserved"][ro_bits] + mod_sys = (flag_byte >> 2) & 0x01 + mod_type = flag_byte & 0x03 + if mod_sys == 0: + modulation = ["auto", "QPSK", "8PSK", "16QAM"][mod_type] + else: + modulation = ["auto", "QPSK", "8PSK", "16APSK"][mod_type] + symbol_rate = _bcd_sr(desc_data[7:11]) + fec_inner = desc_data[10] & 0x0F + fec = _fec_inner_str(fec_inner) + + offset = max(offset, td_end) + + transports.append({ + "ts_id": ts_id, + "original_network_id": original_network_id, + "frequency_ghz": frequency_ghz, + "polarization": polarization, + "symbol_rate": symbol_rate, + "fec": fec, + "orbital_position": orbital_position, + "modulation": modulation, + "roll_off": roll_off, + }) + + return { + "table_id": section["table_id"], + "network_id": network_id, + "network_name": network_name, + "version": section["version"], + "transports": transports, + } + + +def _decode_dvb_string(data: bytes) -> str: + """ + Decode a DVB text string per EN 300 468 Annex A. + + If the first byte is a character table selector (0x01-0x1F), + select the appropriate encoding. Otherwise assume ISO 8859-1. + """ + if not data: + return "" + + first = data[0] + if first < 0x20: + # Character table selector byte + if first == 0x01: + return data[1:].decode('iso-8859-5', errors='replace') + elif first == 0x02: + return data[1:].decode('iso-8859-6', errors='replace') + elif first == 0x03: + return data[1:].decode('iso-8859-7', errors='replace') + elif first == 0x04: + return data[1:].decode('iso-8859-8', errors='replace') + elif first == 0x05: + return data[1:].decode('iso-8859-9', errors='replace') + elif first == 0x06: + return data[1:].decode('iso-8859-10', errors='replace') + elif first == 0x07: + return data[1:].decode('iso-8859-11', errors='replace') + elif first == 0x09: + return data[1:].decode('iso-8859-13', errors='replace') + elif first == 0x0A: + return data[1:].decode('iso-8859-14', errors='replace') + elif first == 0x0B: + return data[1:].decode('iso-8859-15', errors='replace') + elif first == 0x10: + # Two more selector bytes follow + if len(data) >= 3: + sub = (data[1] << 8) | data[2] + try: + return data[3:].decode(f'iso-8859-{sub}', errors='replace') + except (LookupError, ValueError): + return data[3:].decode('iso-8859-1', errors='replace') + return data[1:].decode('iso-8859-1', errors='replace') + elif first == 0x11: + return data[1:].decode('utf-16-be', errors='replace') + elif first == 0x13: + return data[1:].decode('gb2312', errors='replace') + elif first == 0x15: + return data[1:].decode('utf-8', errors='replace') + else: + # Unknown selector, skip it + return data[1:].decode('iso-8859-1', errors='replace') + + return data.decode('iso-8859-1', errors='replace') + + +def _bcd_freq(data: bytes) -> float: + """ + Decode 4-byte BCD frequency from satellite_delivery_system_descriptor. + Per EN 300 468, the 8 BCD digits encode the frequency such that + the integer value divided by 10^5 yields GHz. + e.g., 0x11 0x72 0x75 0x00 -> digits 11727500 -> 11.72750 GHz. + """ + value = 0 + for b in data: + value = value * 100 + ((b >> 4) & 0x0F) * 10 + (b & 0x0F) + return value / 1_000_000.0 + + +def _bcd_orbital(data: bytes) -> float: + """ + Decode 2-byte BCD orbital position per EN 300 468. + 4 BCD digits: XX.XX degrees (2 integer + 2 fractional). + e.g., 0x28 0x20 = 28.20 degrees (Astra 28.2E). + """ + value = 0 + for b in data: + value = value * 100 + ((b >> 4) & 0x0F) * 10 + (b & 0x0F) + return value / 100.0 + + +def _bcd_sr(data: bytes) -> int: + """ + Decode symbol rate from satellite_delivery_system_descriptor. + 4 bytes: upper 28 bits = 7 BCD digits of symbol rate (XXXX.XXX Msps), + lower 4 bits = FEC inner code (handled separately by caller). + e.g., 0x00 0x27 0x50 0x03 -> digits 0027500 -> 27.500 Msps = 27,500,000 sps. + """ + # Extract 7 BCD digits from the upper 28 bits (ignore last nibble = FEC) + value = 0 + for b in data[:4]: + value = value * 100 + ((b >> 4) & 0x0F) * 10 + (b & 0x0F) + # value now has 8 BCD digits decoded; drop the last one (FEC nibble) + value = value // 10 + # value = XXXX.XXX Msps as integer XXXXXXX, divide by 1000 for Msps + # Multiply by 1000 to get sps: (value / 1000) * 1e6 = value * 1000 + return value * 1000 + + +def _fec_inner_str(code: int) -> str: + """Convert FEC inner code rate nibble to string.""" + fec_map = { + 0: "not defined", + 1: "1/2", + 2: "2/3", + 3: "3/4", + 4: "5/6", + 5: "7/8", + 6: "8/9", + 7: "3/5", + 8: "4/5", + 9: "9/10", + 15: "none", + } + return fec_map.get(code, f"reserved({code})") + + +def open_input(path: str): + """Open TS input from a file path or stdin ('-').""" + if path == '-': + return sys.stdin.buffer + if not os.path.exists(path): + print(f"File not found: {path}") + sys.exit(1) + return open(path, 'rb') + + +def format_pid(pid: int, known: dict = None) -> str: + """Format a PID with its known name if available.""" + if known is None: + known = KNOWN_PIDS + name = known.get(pid, "") + if name: + return f"0x{pid:04X} ({name})" + return f"0x{pid:04X}" + + +# -- Subcommand handlers -- + +def cmd_analyze(args: argparse.Namespace) -> None: + """Full transport stream analysis.""" + source = open_input(args.input) + reader = TSReader(source, verbose=args.verbose) + + pid_counts = {} + pid_cc = {} # Last continuity counter per PID + cc_errors = {} + tei_count = 0 + scrambled_count = 0 + total_packets = 0 + first_pcr = None + first_pcr_pkt = 0 + last_pcr = None + last_pcr_pkt = 0 + + print(f"MPEG-2 Transport Stream Analysis") + print(f"{'=' * 60}") + if args.input != '-': + file_size = os.path.getsize(args.input) + print(f"File: {args.input} ({file_size:,} bytes)") + print() + + try: + for pkt in reader.iter_packets(max_packets=args.max_packets): + total_packets += 1 + pid = pkt.pid + + # PID counting + pid_counts[pid] = pid_counts.get(pid, 0) + 1 + + # TEI + if pkt.tei: + tei_count += 1 + + # Scrambling + if pkt.scrambling != 0: + scrambled_count += 1 + + # Continuity counter check (only for PIDs carrying payload) + if pkt.adaptation & 0x01 and pid != 0x1FFF: + if pid in pid_cc: + expected = (pid_cc[pid] + 1) & 0x0F + if pkt.continuity != expected and pkt.continuity != pid_cc[pid]: + cc_errors[pid] = cc_errors.get(pid, 0) + 1 + pid_cc[pid] = pkt.continuity + + # PCR extraction for bitrate calculation + if pkt.has_pcr(): + pcr = pkt.get_pcr() + if pcr >= 0: + if first_pcr is None: + first_pcr = pcr + first_pcr_pkt = total_packets + last_pcr = pcr + last_pcr_pkt = total_packets + except KeyboardInterrupt: + print("\n (interrupted)") + finally: + if source is not sys.stdin.buffer: + source.close() + + if total_packets == 0: + print("No valid TS packets found.") + return + + # Summary + if reader.sync_offset > 0: + print(f"Sync offset: {reader.sync_offset} bytes (skipped leading garbage)") + print(f"Total packets: {total_packets:,}") + print(f"Total bytes: {total_packets * TS_PACKET_SIZE:,}") + print(f"Unique PIDs: {len(pid_counts)}") + print(f"TEI errors: {tei_count}") + print(f"Scrambled: {scrambled_count}") + + # Bitrate + if first_pcr is not None and last_pcr is not None and last_pcr != first_pcr: + pcr_delta = last_pcr - first_pcr + pkt_delta = last_pcr_pkt - first_pcr_pkt + if pcr_delta > 0 and pkt_delta > 0: + duration = pcr_delta / 27_000_000.0 # PCR is 27 MHz clock + byte_count = pkt_delta * TS_PACKET_SIZE + bitrate = (byte_count * 8) / duration + if bitrate >= 1e6: + rate_str = f"{bitrate / 1e6:.2f} Mbps" + else: + rate_str = f"{bitrate / 1e3:.1f} kbps" + print(f"Duration: {duration:.2f}s (from PCR)") + print(f"Bitrate: {rate_str} (PCR-based)") + elif args.input != '-': + # File size estimate + file_size = os.path.getsize(args.input) + print(f"Bitrate: (no PCR found, cannot calculate from timing)") + + # PID table + print(f"\n{'=' * 60}") + print(f"PID Distribution") + print(f"{'=' * 60}") + print(f" {'PID':>6} {'Count':>10} {'%':>7} {'CC Err':>6} Name") + print(f" {'---':>6} {'-----':>10} {'--':>7} {'------':>6} ----") + + for pid in sorted(pid_counts.keys()): + count = pid_counts[pid] + pct = (count / total_packets) * 100 + cc_err = cc_errors.get(pid, 0) + name = KNOWN_PIDS.get(pid, "") + cc_str = str(cc_err) if cc_err > 0 else "-" + print(f" 0x{pid:04X} {count:>10,} {pct:>6.2f}% {cc_str:>6} {name}") + + # CC error summary + total_cc_errors = sum(cc_errors.values()) + if total_cc_errors > 0: + print(f"\nContinuity errors: {total_cc_errors} total across " + f"{len(cc_errors)} PID(s)") + + +def cmd_pids(args: argparse.Namespace) -> None: + """Quick PID summary table.""" + source = open_input(args.input) + reader = TSReader(source, verbose=args.verbose) + + pid_counts = {} + total = 0 + + try: + for pkt in reader.iter_packets(max_packets=args.max_packets): + total += 1 + pid_counts[pkt.pid] = pid_counts.get(pkt.pid, 0) + 1 + except KeyboardInterrupt: + pass + finally: + if source is not sys.stdin.buffer: + source.close() + + if total == 0: + print("No TS packets found.") + return + + print(f"PID Table ({total:,} packets)") + print(f"{'=' * 50}") + print(f" {'PID':>6} {'Count':>10} {'%':>7} Name") + print(f" {'---':>6} {'-----':>10} {'--':>7} ----") + + for pid in sorted(pid_counts.keys()): + count = pid_counts[pid] + pct = (count / total) * 100 + name = KNOWN_PIDS.get(pid, "") + print(f" 0x{pid:04X} {count:>10,} {pct:>6.2f}% {name}") + + +def cmd_pat(args: argparse.Namespace) -> None: + """Parse and display the Program Association Table.""" + source = open_input(args.input) + reader = TSReader(source, verbose=args.verbose) + psi = PSIParser() + pat_found = False + + try: + for pkt in reader.iter_packets(): + if pkt.pid != 0x0000: + continue + section = psi.feed(pkt) + if section is None: + continue + pat = parse_pat(section) + if pat is None: + continue + + pat_found = True + print(f"Program Association Table (PAT)") + print(f"{'=' * 50}") + print(f" Transport Stream ID: 0x{pat['transport_stream_id']:04X} " + f"({pat['transport_stream_id']})") + print(f" Version: {pat['version']}") + print(f" Programs: {len(pat['programs'])}") + print() + print(f" {'Program':>10} {'PMT PID':>10} Note") + print(f" {'-------':>10} {'-------':>10} ----") + for prog, pmt_pid in sorted(pat['programs'].items()): + note = "NIT" if prog == 0 else "" + print(f" {prog:>10} 0x{pmt_pid:04X} {note}") + break + + except KeyboardInterrupt: + pass + finally: + if source is not sys.stdin.buffer: + source.close() + + if not pat_found: + print("No PAT (PID 0x0000) found in stream.") + + +def cmd_pmt(args: argparse.Namespace) -> None: + """Parse and display Program Map Tables.""" + source = open_input(args.input) + reader = TSReader(source, verbose=args.verbose) + psi_pat = PSIParser() + psi_pmt = PSIParser() + + pat = None + pmt_pids = set() + pmts_found = {} + + try: + for pkt in reader.iter_packets(): + # First, collect PAT to learn PMT PIDs + if pkt.pid == 0x0000 and pat is None: + section = psi_pat.feed(pkt) + if section is not None: + pat = parse_pat(section) + if pat is not None: + for prog, pid in pat['programs'].items(): + if prog != 0: # Skip NIT reference + pmt_pids.add(pid) + + # Then collect PMT sections + if pkt.pid in pmt_pids and pkt.pid not in pmts_found: + section = psi_pmt.feed(pkt) + if section is not None: + pmt = parse_pmt(section) + if pmt is not None: + pmts_found[pkt.pid] = pmt + + # Done when we have all PMTs + if pat is not None and len(pmts_found) >= len(pmt_pids): + break + + except KeyboardInterrupt: + pass + finally: + if source is not sys.stdin.buffer: + source.close() + + if pat is None: + print("No PAT found -- cannot locate PMTs.") + return + + if not pmts_found: + print("PAT found but no PMT sections could be parsed.") + return + + print(f"Program Map Tables") + print(f"{'=' * 60}") + print(f"Transport Stream ID: 0x{pat['transport_stream_id']:04X}") + print() + + for pmt_pid in sorted(pmts_found.keys()): + pmt = pmts_found[pmt_pid] + print(f" Program {pmt['program_number']} (PMT PID 0x{pmt_pid:04X}, " + f"version {pmt['version']})") + print(f" PCR PID: 0x{pmt['pcr_pid']:04X}") + print(f" Streams:") + print(f" {'Type':>6} {'PID':>6} Description") + print(f" {'----':>6} {'---':>6} -----------") + for s in pmt['streams']: + print(f" 0x{s['stream_type']:02X} 0x{s['elementary_pid']:04X} " + f"{s['type_name']}") + print() + + +def cmd_dump(args: argparse.Namespace) -> None: + """Hex dump of individual TS packets.""" + source = open_input(args.input) + reader = TSReader(source, verbose=args.verbose) + filter_pid = args.pid + max_count = args.count + shown = 0 + + try: + for pkt in reader.iter_packets(): + if filter_pid is not None and pkt.pid != filter_pid: + continue + + shown += 1 + scrambling_str = ["none", "reserved", "even key", "odd key"][pkt.scrambling] + adapt_str = ["reserved", "payload only", "adapt only", "adapt+payload"][pkt.adaptation] + + print(f"Packet #{shown}") + print(f" PID: 0x{pkt.pid:04X} ({KNOWN_PIDS.get(pkt.pid, '')})") + print(f" TEI: {int(pkt.tei)} PUSI: {int(pkt.pusi)} " + f"Priority: {int(pkt.priority)}") + print(f" Scrambling: {scrambling_str} " + f"Adaptation: {adapt_str} CC: {pkt.continuity}") + + if pkt.adaptation_field is not None and len(pkt.adaptation_field) > 1: + af_len = pkt.adaptation_field[0] + af_flags = pkt.adaptation_field[1] if len(pkt.adaptation_field) > 1 else 0 + flags = [] + if af_flags & 0x80: flags.append("discontinuity") + if af_flags & 0x40: flags.append("random_access") + if af_flags & 0x20: flags.append("ES_priority") + if af_flags & 0x10: flags.append("PCR") + if af_flags & 0x08: flags.append("OPCR") + if af_flags & 0x04: flags.append("splice_point") + if af_flags & 0x02: flags.append("private_data") + if af_flags & 0x01: flags.append("extension") + print(f" Adaptation field: {af_len} bytes, " + f"flags=[{', '.join(flags) if flags else 'none'}]") + if pkt.has_pcr(): + pcr = pkt.get_pcr() + pcr_secs = pcr / 27_000_000.0 + print(f" PCR: {pcr} ({pcr_secs:.6f}s)") + + # Hex dump + data = pkt.raw + print(f" Hex:") + for row_off in range(0, len(data), 16): + row = data[row_off:row_off + 16] + hex_part = ' '.join(f'{b:02X}' for b in row) + ascii_part = ''.join(chr(b) if 0x20 <= b < 0x7F else '.' for b in row) + print(f" {row_off:04X}: {hex_part:<48} {ascii_part}") + print() + + if max_count and shown >= max_count: + break + + except KeyboardInterrupt: + pass + finally: + if source is not sys.stdin.buffer: + source.close() + + if shown == 0: + if filter_pid is not None: + print(f"No packets found with PID 0x{filter_pid:04X}") + else: + print("No TS packets found.") + + +def cmd_monitor(args: argparse.Namespace) -> None: + """Live stream monitoring from stdin or file.""" + source = open_input(args.input) + reader = TSReader(source, verbose=args.verbose) + + pid_counts = {} + known_pids = set() + cc_last = {} + cc_errors = 0 + tei_count = 0 + total_packets = 0 + interval_packets = 0 + start_time = time.time() + last_report = start_time + + print(f"MPEG-2 TS Live Monitor") + print(f"{'=' * 60}") + print(f"Ctrl-C to stop\n") + + try: + for pkt in reader.iter_packets(): + total_packets += 1 + interval_packets += 1 + pid = pkt.pid + + pid_counts[pid] = pid_counts.get(pid, 0) + 1 + + # New PID detection + if pid not in known_pids: + known_pids.add(pid) + name = KNOWN_PIDS.get(pid, "") + label = f" ({name})" if name else "" + elapsed = time.time() - start_time + print(f" [{elapsed:>7.1f}s] New PID: 0x{pid:04X}{label}") + + # TEI + if pkt.tei: + tei_count += 1 + elapsed = time.time() - start_time + print(f" [{elapsed:>7.1f}s] TEI error on PID 0x{pid:04X}") + + # CC check + if pkt.adaptation & 0x01 and pid != 0x1FFF: + if pid in cc_last: + expected = (cc_last[pid] + 1) & 0x0F + if pkt.continuity != expected and pkt.continuity != cc_last[pid]: + cc_errors += 1 + elapsed = time.time() - start_time + print(f" [{elapsed:>7.1f}s] CC error PID 0x{pid:04X}: " + f"expected {expected}, got {pkt.continuity}") + cc_last[pid] = pkt.continuity + + # Periodic status + now = time.time() + if now - last_report >= 1.0: + elapsed = now - start_time + total_bytes = total_packets * TS_PACKET_SIZE + bitrate = (interval_packets * TS_PACKET_SIZE * 8) + if bitrate >= 1e6: + rate_str = f"{bitrate / 1e6:.2f} Mbps" + else: + rate_str = f"{bitrate / 1e3:.1f} kbps" + sys.stderr.write( + f"\r {total_packets:>10,} pkts " + f"{total_bytes:>12,} bytes " + f"{rate_str:>12} " + f"PIDs:{len(known_pids):>3} " + f"CCerr:{cc_errors} " + f"TEI:{tei_count} " + f"({elapsed:.0f}s) " + ) + sys.stderr.flush() + interval_packets = 0 + last_report = now + + except KeyboardInterrupt: + pass + finally: + if source is not sys.stdin.buffer: + source.close() + + elapsed = time.time() - start_time + total_bytes = total_packets * TS_PACKET_SIZE + print(f"\n\nMonitor Summary") + print(f"{'=' * 40}") + print(f" Duration: {elapsed:.1f}s") + print(f" Packets: {total_packets:,}") + print(f" Bytes: {total_bytes:,}") + print(f" Unique PIDs: {len(known_pids)}") + print(f" CC errors: {cc_errors}") + print(f" TEI errors: {tei_count}") + + if elapsed > 0: + avg_bitrate = (total_bytes * 8) / elapsed + if avg_bitrate >= 1e6: + print(f" Avg bitrate: {avg_bitrate / 1e6:.2f} Mbps") + else: + print(f" Avg bitrate: {avg_bitrate / 1e3:.1f} kbps") + + +# -- CLI -- + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="MPEG-2 Transport Stream analyzer for Genpix SkyWalker-1", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +examples: + %(prog)s capture.ts + %(prog)s analyze capture.ts + %(prog)s pids capture.ts + %(prog)s pat capture.ts + %(prog)s pmt capture.ts + %(prog)s dump capture.ts --pid 0x100 --count 5 + %(prog)s monitor - + tune.py stream --stdout | %(prog)s monitor - +""") + parser.add_argument('-v', '--verbose', action='store_true', + help="Show sync search details and debug info") + + sub = parser.add_subparsers(dest='command') + + # analyze (default) + p_analyze = sub.add_parser('analyze', help="Full stream analysis (default)") + p_analyze.add_argument('input', help="TS file path or '-' for stdin") + p_analyze.add_argument('--max-packets', type=int, default=0, + help="Max packets to analyze (0 = all)") + + # pids + p_pids = sub.add_parser('pids', help="Quick PID summary table") + p_pids.add_argument('input', help="TS file path or '-' for stdin") + p_pids.add_argument('--max-packets', type=int, default=0, + help="Max packets to analyze (0 = all)") + + # pat + p_pat = sub.add_parser('pat', help="Parse Program Association Table") + p_pat.add_argument('input', help="TS file path or '-' for stdin") + + # pmt + p_pmt = sub.add_parser('pmt', help="Parse Program Map Tables") + p_pmt.add_argument('input', help="TS file path or '-' for stdin") + + # dump + p_dump = sub.add_parser('dump', help="Hex dump of TS packets") + p_dump.add_argument('input', help="TS file path or '-' for stdin") + p_dump.add_argument('--pid', type=lambda x: int(x, 0), default=None, + help="Filter by PID (hex: 0x100 or decimal: 256)") + p_dump.add_argument('--count', type=int, default=10, + help="Max packets to dump (default: 10)") + + # monitor + p_monitor = sub.add_parser('monitor', help="Live stream monitoring") + p_monitor.add_argument('input', help="TS file path or '-' for stdin") + + return parser + + +def main(): + parser = build_parser() + + # Handle bare filename without subcommand: default to 'analyze' + # Insert 'analyze' after any global flags but before the filename + subcmds = {'analyze', 'pids', 'pat', 'pmt', 'dump', 'monitor'} + argv = sys.argv[1:] + if argv: + first_pos = None + insert_idx = 0 + for i, a in enumerate(argv): + if not a.startswith('-'): + first_pos = a + insert_idx = i + break + # Skip flag and its value if it takes one (currently none do) + insert_idx = i + 1 + if first_pos is not None and first_pos not in subcmds: + argv.insert(insert_idx, 'analyze') + + args = parser.parse_args(argv) + + if not args.command: + parser.print_help() + sys.exit(1) + + dispatch = { + 'analyze': cmd_analyze, + 'pids': cmd_pids, + 'pat': cmd_pat, + 'pmt': cmd_pmt, + 'dump': cmd_dump, + 'monitor': cmd_monitor, + } + + handler = dispatch.get(args.command) + if handler is None: + parser.print_help() + sys.exit(1) + + handler(args) + + +if __name__ == '__main__': + main() diff --git a/tools/tune.py b/tools/tune.py index 75c2bc6..4446a83 100755 --- a/tools/tune.py +++ b/tools/tune.py @@ -1,540 +1,540 @@ -#!/usr/bin/env python3 -""" -Genpix SkyWalker-1 DVB-S tuning and streaming tool. - -Controls the SkyWalker-1 USB DVB-S satellite receiver via vendor USB -control transfers. Supports tuning, LNB control, DiSEqC switching, -MPEG-2 transport stream capture, and signal monitoring. - -Hardware: Cypress FX2 (CY7C68013A) + Broadcom BCM4500 demodulator -USB: VID 0x09C0, PID 0x0203, EP2 bulk IN for TS data -""" - -import sys -import struct -import argparse -import time -import json -import signal -import os - -# Add tools directory to path for library import -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from skywalker_lib import ( - SkyWalker1, VENDOR_ID, PRODUCT_ID, EP2_URB_SIZE, - MODULATIONS, FEC_RATES, MOD_FEC_GROUP, - LNB_LO_LOW, LNB_LO_HIGH, - CONFIG_BITS, - signal_bar, format_config_bits, -) - -import usb.core - - -# -- Subcommand handlers -- - -def cmd_status(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Show device status, firmware version, signal info.""" - print(f"Genpix SkyWalker-1 Status") - print(f"{'=' * 50}") - - print(f"\nUSB: Bus {sw.dev.bus} Addr {sw.dev.address} " - f"(VID 0x{VENDOR_ID:04X}, PID 0x{PRODUCT_ID:04X})") - - # Firmware version - try: - fw = sw.get_fw_version() - print(f"FW: {fw['version']} (built {fw['date']})") - except usb.core.USBError: - print("FW: (read failed)") - fw = None - - # Config status - status = sw.get_config() - print(f"\nConfig: 0x{status:02X}") - bits = format_config_bits(status) - for name, is_set in bits: - state = "ON" if is_set else "off" - print(f" [{state:>3}] {name}") - - # Signal lock and strength - locked = sw.get_signal_lock() - print(f"\nSignal Lock: {'LOCKED' if locked else 'no lock'}") - if locked: - sig = sw.get_signal_strength() - print(f"SNR: {sig['snr_db']:.1f} dB (raw 0x{sig['snr_raw']:04X})") - print(f"Quality: {signal_bar(sig['snr_pct'])}") - - if args.json: - out = { - "usb": {"bus": sw.dev.bus, "address": sw.dev.address}, - "config": status, - "config_bits": {field: bool(status & bit) - for bit, (_name, field) in CONFIG_BITS.items()}, - "locked": locked, - } - if fw: - out["firmware"] = fw - if locked: - out["signal"] = sw.get_signal_strength() - print(f"\n{json.dumps(out, indent=2)}") - - -def cmd_tune(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Tune to a transponder.""" - freq_mhz = args.freq - sr_ksps = args.sr - mod_name = args.mod - fec_name = args.fec - pol = args.pol.upper() if args.pol else None - band = args.band - - # Resolve LNB LO - if args.lnb_lo: - lnb_lo = args.lnb_lo - elif band == "high": - lnb_lo = LNB_LO_HIGH - else: - lnb_lo = LNB_LO_LOW - - # Compute IF frequency - if_mhz = freq_mhz - lnb_lo - if_khz = int(if_mhz * 1000) - - if if_khz < 950000 or if_khz > 2150000: - print(f"WARNING: IF frequency {if_mhz} MHz is outside 950-2150 MHz range") - print(f" Downlink: {freq_mhz} MHz, LNB LO: {lnb_lo} MHz") - if if_khz < 0: - print(" IF is negative -- check your LNB LO frequency") - sys.exit(1) - - # Resolve modulation - if mod_name not in MODULATIONS: - print(f"Unknown modulation: {mod_name}") - print(f"Valid: {', '.join(MODULATIONS.keys())}") - sys.exit(1) - mod_index, mod_desc = MODULATIONS[mod_name] - - # Resolve FEC - fec_group = MOD_FEC_GROUP[mod_name] - fec_table = FEC_RATES[fec_group] - if fec_name not in fec_table: - print(f"Invalid FEC '{fec_name}' for {mod_desc}") - print(f"Valid: {', '.join(fec_table.keys())}") - sys.exit(1) - fec_index = fec_table[fec_name] - - sr_sps = sr_ksps * 1000 - - print(f"Tuning SkyWalker-1") - print(f"{'=' * 50}") - print(f" Downlink: {freq_mhz} MHz") - print(f" LNB LO: {lnb_lo} MHz") - print(f" IF Frequency: {if_mhz} MHz ({if_khz} kHz)") - print(f" Symbol Rate: {sr_ksps} ksps ({sr_sps} sps)") - print(f" Modulation: {mod_desc} (index {mod_index})") - print(f" FEC: {fec_name} (index {fec_index})") - if pol: - pol_desc = {"H": "Horizontal (18V)", "V": "Vertical (13V)", - "L": "Left circular (18V)", "R": "Right circular (13V)"} - print(f" Polarization: {pol_desc.get(pol, pol)}") - if band: - print(f" Band: {band} ({'22kHz on' if band == 'high' else '22kHz off'})") - print() - - # Step 1: Check device status - status = sw.get_config() - print(f"[1/8] Config status: 0x{status:02X}") - - # Step 2: Boot demodulator if needed - if not (status & 0x01): - print("[2/8] Booting 8PSK demodulator...") - sw.boot(on=True) - time.sleep(0.5) - status = sw.get_config() - if not (status & 0x01): - print(" FAILED: Device did not start") - sys.exit(1) - print(" OK") - else: - print("[2/8] Demodulator already running") - - # Step 3: Enable LNB power if needed - if not (status & 0x04): - print("[3/8] Enabling LNB power supply...") - sw.start_intersil(on=True) - time.sleep(0.3) - status = sw.get_config() - if not (status & 0x04): - print(" FAILED: LNB power did not enable") - sys.exit(1) - print(" OK") - else: - print("[3/8] LNB power already on") - - # Step 4: Set LNB voltage (polarization) - if pol: - high_voltage = pol in ("H", "L") - print(f"[4/8] Setting LNB voltage: {'18V' if high_voltage else '13V'}") - sw.set_lnb_voltage(high_voltage) - else: - print("[4/8] LNB voltage: not changed (no --pol specified)") - - # Step 5: Extra voltage if requested - if args.extra_volt: - print("[5/8] Enabling +1V LNB boost") - sw.set_extra_voltage(True) - else: - print("[5/8] Extra voltage: off") - - # Step 6: Set 22 kHz tone (band selection) - if band: - tone_on = (band == "high") - print(f"[6/8] 22 kHz tone: {'ON' if tone_on else 'OFF'}") - sw.set_22khz_tone(tone_on) - else: - print("[6/8] 22 kHz tone: not changed (no --band specified)") - - # Step 7: Send tune command - print(f"[7/8] Sending TUNE_8PSK...") - if sw.verbose: - payload_hex = struct.pack(' None: - """Stream MPEG-2 transport data to file or stdout.""" - # Verify signal lock - if not sw.get_signal_lock(): - print("No signal lock -- tune to a transponder first") - print(" Example: tune.py tune 12520 27500 --pol H --band high") - sys.exit(1) - - output_file = None - output_fd = None - - if args.stdout: - output_fd = sys.stdout.buffer - # Suppress all status output when piping - status_fd = sys.stderr - elif args.output: - output_file = args.output - output_fd = open(output_file, 'wb') - status_fd = sys.stdout - else: - print("Specify -o FILE or --stdout") - sys.exit(1) - - duration = args.duration - total_bytes = 0 - start_time = time.time() - last_report = start_time - running = True - - def stop_handler(signum, frame): - nonlocal running - running = False - - signal.signal(signal.SIGINT, stop_handler) - signal.signal(signal.SIGTERM, stop_handler) - - status_fd.write(f"Streaming TS data") - if output_file: - status_fd.write(f" to {output_file}") - if duration: - status_fd.write(f" for {duration}s") - status_fd.write("\n") - status_fd.flush() - - # Arm the transfer - sw.arm_transfer(on=True) - status_fd.write(" Armed. Reading EP2...\n") - status_fd.flush() - - try: - while running: - if duration and (time.time() - start_time) >= duration: - break - - chunk = sw.read_stream(EP2_URB_SIZE, timeout=2000) - if chunk: - output_fd.write(chunk) - total_bytes += len(chunk) - - now = time.time() - if now - last_report >= 1.0: - elapsed = now - start_time - bitrate = (total_bytes * 8) / elapsed if elapsed > 0 else 0 - if bitrate >= 1e6: - rate_str = f"{bitrate / 1e6:.2f} Mbps" - else: - rate_str = f"{bitrate / 1e3:.1f} kbps" - status_fd.write(f"\r {total_bytes:,} bytes {rate_str} " - f"({elapsed:.0f}s) ") - status_fd.flush() - last_report = now - - finally: - sw.arm_transfer(on=False) - status_fd.write(f"\n Stopped. Total: {total_bytes:,} bytes\n") - if output_file and output_fd: - output_fd.close() - status_fd.write(f" Saved to: {output_file}\n") - - -def cmd_diseqc(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Send DiSEqC commands.""" - if args.tone_burst is not None: - burst_val = {"A": 0, "a": 0, "B": 1, "b": 1}.get(args.tone_burst) - if burst_val is None: - print("Tone burst must be A or B") - sys.exit(1) - print(f"Sending tone burst: SEC_MINI_{args.tone_burst.upper()} (0x{burst_val:02X})") - sw.send_diseqc_tone_burst(burst_val) - print(" OK") - - elif args.port is not None: - port = args.port - if port < 1 or port > 4: - print("DiSEqC 1.0 port must be 1-4") - sys.exit(1) - # DiSEqC 1.0 committed switch command: - # Framing=0xE0 (command from master, no reply, first tx) - # Address=0x10 (any switch) - # Command=0x38 (Write N0 - committed switches) - # Data=0xF0 | ((port-1) << 2) with option/position bits - # Bits: [7:4]=0xF (always), [3]=pol, [2]=band, [1:0]=port - # For simplicity, just switch port without changing pol/band bits - data_byte = 0xF0 | ((port - 1) << 2) - msg = bytes([0xE0, 0x10, 0x38, data_byte]) - print(f"Sending DiSEqC 1.0: port {port}") - print(f" Message: {msg.hex(' ')}") - sw.send_diseqc_message(msg) - print(" OK") - - elif args.raw: - raw_bytes = bytes(int(b, 16) for b in args.raw) - if len(raw_bytes) < 3 or len(raw_bytes) > 6: - print("Raw DiSEqC message must be 3-6 bytes") - sys.exit(1) - print(f"Sending raw DiSEqC: {raw_bytes.hex(' ')}") - sw.send_diseqc_message(raw_bytes) - print(" OK") - - else: - print("Specify --port, --tone-burst, or --raw") - sys.exit(1) - - -def cmd_lnb(sw: SkyWalker1, args: argparse.Namespace) -> None: - """Control LNB voltage and 22 kHz tone.""" - did_something = False - - # Ensure LNB power is on first - status = sw.get_config() - if not (status & 0x04): - print("Enabling LNB power supply...") - sw.start_intersil(on=True) - time.sleep(0.3) - - if args.voltage is not None: - v = args.voltage - if v not in (13, 18): - print("Voltage must be 13 or 18") - sys.exit(1) - high = (v == 18) - print(f"Setting LNB voltage: {v}V") - sw.set_lnb_voltage(high) - did_something = True - - if args.extra_volt: - print("Enabling +1V LNB boost") - sw.set_extra_voltage(True) - did_something = True - - if args.tone is not None: - tone_on = args.tone.lower() in ("on", "1", "true", "yes") - print(f"22 kHz tone: {'ON' if tone_on else 'OFF'}") - sw.set_22khz_tone(tone_on) - did_something = True - - if args.power is not None: - power_on = args.power.lower() in ("on", "1", "true", "yes") - if power_on: - print("Enabling LNB power supply") - sw.start_intersil(on=True) - else: - print("Disabling LNB power supply") - sw.start_intersil(on=False) - did_something = True - - if not did_something: - # Just show current LNB state - status = sw.get_config() - print(f"LNB Status:") - print(f" Power: {'ON' if status & 0x04 else 'off'}") - print(f" Voltage: {'18V' if status & 0x20 else '13V'}") - print(f" 22 kHz: {'ON' if status & 0x10 else 'off'}") - print(f" Armed: {'YES' if status & 0x80 else 'no'}") - else: - # Read back config to confirm - time.sleep(0.1) - status = sw.get_config() - print(f"\nConfig: 0x{status:02X}") - print(f" Power: {'ON' if status & 0x04 else 'off'} " - f"Voltage: {'18V' if status & 0x20 else '13V'} " - f"22kHz: {'ON' if status & 0x10 else 'off'}") - - -# -- CLI -- - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - description="Genpix SkyWalker-1 DVB-S tuning and streaming tool", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""\ -examples: - %(prog)s status - %(prog)s tune 12520 27500 --pol H --band high - %(prog)s tune 12520 27500 --pol H --band high --mod qpsk --fec auto - %(prog)s stream -o capture.ts --duration 60 - %(prog)s stream --stdout | vlc - - %(prog)s diseqc --port 1 - %(prog)s diseqc --tone-burst A - %(prog)s diseqc --raw E0 10 38 F0 - %(prog)s lnb --voltage 18 --tone on -""") - parser.add_argument('-v', '--verbose', action='store_true', - help="Show raw USB traffic") - parser.add_argument('--json', action='store_true', - help="Output machine-readable JSON where supported") - - sub = parser.add_subparsers(dest='command') - - # status - p_status = sub.add_parser('status', help="Show device config, FW version, signal status") - p_status.add_argument('--json', action='store_true', default=False, - help="Output machine-readable JSON") - - # tune - p_tune = sub.add_parser('tune', help="Tune to a transponder") - p_tune.add_argument('freq', type=float, - help="Transponder downlink frequency in MHz (e.g. 12520)") - p_tune.add_argument('sr', type=int, - help="Symbol rate in ksps (e.g. 27500)") - p_tune.add_argument('--pol', choices=['H', 'V', 'L', 'R', 'h', 'v', 'l', 'r'], - help="Polarization: H/V (linear) or L/R (circular)") - p_tune.add_argument('--band', choices=['low', 'high'], - help="LNB band: low (tone off) or high (tone on)") - p_tune.add_argument('--lnb-lo', type=float, default=None, - help="LNB LO frequency in MHz (default: 9750 low, 10600 high)") - p_tune.add_argument('--mod', default='qpsk', - choices=list(MODULATIONS.keys()), - help="Modulation type (default: qpsk)") - p_tune.add_argument('--fec', default='auto', - help="FEC rate (default: auto). Options depend on modulation.") - p_tune.add_argument('--timeout', type=float, default=10, - help="Signal lock timeout in seconds (default: 10)") - p_tune.add_argument('--extra-volt', action='store_true', - help="Enable +1V LNB voltage boost for long cables") - p_tune.add_argument('--json', action='store_true', default=False, - help="Output machine-readable JSON") - - # stream - p_stream = sub.add_parser('stream', help="Stream MPEG-2 TS data") - p_stream.add_argument('-o', '--output', help="Output file for TS data") - p_stream.add_argument('--stdout', action='store_true', - help="Write TS stream to stdout (pipe to vlc, ffmpeg, etc)") - p_stream.add_argument('--duration', type=float, default=None, - help="Capture duration in seconds (default: until CTRL-C)") - - # diseqc - p_diseqc = sub.add_parser('diseqc', help="Send DiSEqC commands") - p_diseqc.add_argument('--port', type=int, - help="DiSEqC 1.0 switch port (1-4)") - p_diseqc.add_argument('--tone-burst', metavar='A|B', - help="Mini DiSEqC tone burst (A or B)") - p_diseqc.add_argument('--raw', nargs='+', metavar='HH', - help="Raw DiSEqC bytes in hex (e.g. E0 10 38 F0)") - - # lnb - p_lnb = sub.add_parser('lnb', help="LNB voltage and tone control") - p_lnb.add_argument('--voltage', type=int, choices=[13, 18], - help="LNB voltage (13V or 18V)") - p_lnb.add_argument('--tone', help="22 kHz tone (on/off)") - p_lnb.add_argument('--extra-volt', action='store_true', - help="Enable +1V LNB voltage boost") - p_lnb.add_argument('--power', help="LNB power supply (on/off)") - - return parser - - -def main(): - parser = build_parser() - args = parser.parse_args() - - if not args.command: - # Default to status if no subcommand - args.command = 'status' - args.json = getattr(args, 'json', False) - - dispatch = { - 'status': cmd_status, - 'tune': cmd_tune, - 'stream': cmd_stream, - 'diseqc': cmd_diseqc, - 'lnb': cmd_lnb, - } - - handler = dispatch.get(args.command) - if handler is None: - parser.print_help() - sys.exit(1) - - with SkyWalker1(verbose=args.verbose) as sw: - handler(sw, args) - - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 +""" +Genpix SkyWalker-1 DVB-S tuning and streaming tool. + +Controls the SkyWalker-1 USB DVB-S satellite receiver via vendor USB +control transfers. Supports tuning, LNB control, DiSEqC switching, +MPEG-2 transport stream capture, and signal monitoring. + +Hardware: Cypress FX2 (CY7C68013A) + Broadcom BCM4500 demodulator +USB: VID 0x09C0, PID 0x0203, EP2 bulk IN for TS data +""" + +import sys +import struct +import argparse +import time +import json +import signal +import os + +# Add tools directory to path for library import +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from skywalker_lib import ( + SkyWalker1, VENDOR_ID, PRODUCT_ID, EP2_URB_SIZE, + MODULATIONS, FEC_RATES, MOD_FEC_GROUP, + LNB_LO_LOW, LNB_LO_HIGH, + CONFIG_BITS, + signal_bar, format_config_bits, +) + +import usb.core + + +# -- Subcommand handlers -- + +def cmd_status(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Show device status, firmware version, signal info.""" + print(f"Genpix SkyWalker-1 Status") + print(f"{'=' * 50}") + + print(f"\nUSB: Bus {sw.dev.bus} Addr {sw.dev.address} " + f"(VID 0x{VENDOR_ID:04X}, PID 0x{PRODUCT_ID:04X})") + + # Firmware version + try: + fw = sw.get_fw_version() + print(f"FW: {fw['version']} (built {fw['date']})") + except usb.core.USBError: + print("FW: (read failed)") + fw = None + + # Config status + status = sw.get_config() + print(f"\nConfig: 0x{status:02X}") + bits = format_config_bits(status) + for name, is_set in bits: + state = "ON" if is_set else "off" + print(f" [{state:>3}] {name}") + + # Signal lock and strength + locked = sw.get_signal_lock() + print(f"\nSignal Lock: {'LOCKED' if locked else 'no lock'}") + if locked: + sig = sw.get_signal_strength() + print(f"SNR: {sig['snr_db']:.1f} dB (raw 0x{sig['snr_raw']:04X})") + print(f"Quality: {signal_bar(sig['snr_pct'])}") + + if args.json: + out = { + "usb": {"bus": sw.dev.bus, "address": sw.dev.address}, + "config": status, + "config_bits": {field: bool(status & bit) + for bit, (_name, field) in CONFIG_BITS.items()}, + "locked": locked, + } + if fw: + out["firmware"] = fw + if locked: + out["signal"] = sw.get_signal_strength() + print(f"\n{json.dumps(out, indent=2)}") + + +def cmd_tune(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Tune to a transponder.""" + freq_mhz = args.freq + sr_ksps = args.sr + mod_name = args.mod + fec_name = args.fec + pol = args.pol.upper() if args.pol else None + band = args.band + + # Resolve LNB LO + if args.lnb_lo: + lnb_lo = args.lnb_lo + elif band == "high": + lnb_lo = LNB_LO_HIGH + else: + lnb_lo = LNB_LO_LOW + + # Compute IF frequency + if_mhz = freq_mhz - lnb_lo + if_khz = int(if_mhz * 1000) + + if if_khz < 950000 or if_khz > 2150000: + print(f"WARNING: IF frequency {if_mhz} MHz is outside 950-2150 MHz range") + print(f" Downlink: {freq_mhz} MHz, LNB LO: {lnb_lo} MHz") + if if_khz < 0: + print(" IF is negative -- check your LNB LO frequency") + sys.exit(1) + + # Resolve modulation + if mod_name not in MODULATIONS: + print(f"Unknown modulation: {mod_name}") + print(f"Valid: {', '.join(MODULATIONS.keys())}") + sys.exit(1) + mod_index, mod_desc = MODULATIONS[mod_name] + + # Resolve FEC + fec_group = MOD_FEC_GROUP[mod_name] + fec_table = FEC_RATES[fec_group] + if fec_name not in fec_table: + print(f"Invalid FEC '{fec_name}' for {mod_desc}") + print(f"Valid: {', '.join(fec_table.keys())}") + sys.exit(1) + fec_index = fec_table[fec_name] + + sr_sps = sr_ksps * 1000 + + print(f"Tuning SkyWalker-1") + print(f"{'=' * 50}") + print(f" Downlink: {freq_mhz} MHz") + print(f" LNB LO: {lnb_lo} MHz") + print(f" IF Frequency: {if_mhz} MHz ({if_khz} kHz)") + print(f" Symbol Rate: {sr_ksps} ksps ({sr_sps} sps)") + print(f" Modulation: {mod_desc} (index {mod_index})") + print(f" FEC: {fec_name} (index {fec_index})") + if pol: + pol_desc = {"H": "Horizontal (18V)", "V": "Vertical (13V)", + "L": "Left circular (18V)", "R": "Right circular (13V)"} + print(f" Polarization: {pol_desc.get(pol, pol)}") + if band: + print(f" Band: {band} ({'22kHz on' if band == 'high' else '22kHz off'})") + print() + + # Step 1: Check device status + status = sw.get_config() + print(f"[1/8] Config status: 0x{status:02X}") + + # Step 2: Boot demodulator if needed + if not (status & 0x01): + print("[2/8] Booting 8PSK demodulator...") + sw.boot(on=True) + time.sleep(0.5) + status = sw.get_config() + if not (status & 0x01): + print(" FAILED: Device did not start") + sys.exit(1) + print(" OK") + else: + print("[2/8] Demodulator already running") + + # Step 3: Enable LNB power if needed + if not (status & 0x04): + print("[3/8] Enabling LNB power supply...") + sw.start_intersil(on=True) + time.sleep(0.3) + status = sw.get_config() + if not (status & 0x04): + print(" FAILED: LNB power did not enable") + sys.exit(1) + print(" OK") + else: + print("[3/8] LNB power already on") + + # Step 4: Set LNB voltage (polarization) + if pol: + high_voltage = pol in ("H", "L") + print(f"[4/8] Setting LNB voltage: {'18V' if high_voltage else '13V'}") + sw.set_lnb_voltage(high_voltage) + else: + print("[4/8] LNB voltage: not changed (no --pol specified)") + + # Step 5: Extra voltage if requested + if args.extra_volt: + print("[5/8] Enabling +1V LNB boost") + sw.set_extra_voltage(True) + else: + print("[5/8] Extra voltage: off") + + # Step 6: Set 22 kHz tone (band selection) + if band: + tone_on = (band == "high") + print(f"[6/8] 22 kHz tone: {'ON' if tone_on else 'OFF'}") + sw.set_22khz_tone(tone_on) + else: + print("[6/8] 22 kHz tone: not changed (no --band specified)") + + # Step 7: Send tune command + print(f"[7/8] Sending TUNE_8PSK...") + if sw.verbose: + payload_hex = struct.pack(' None: + """Stream MPEG-2 transport data to file or stdout.""" + # Verify signal lock + if not sw.get_signal_lock(): + print("No signal lock -- tune to a transponder first") + print(" Example: tune.py tune 12520 27500 --pol H --band high") + sys.exit(1) + + output_file = None + output_fd = None + + if args.stdout: + output_fd = sys.stdout.buffer + # Suppress all status output when piping + status_fd = sys.stderr + elif args.output: + output_file = args.output + output_fd = open(output_file, 'wb') + status_fd = sys.stdout + else: + print("Specify -o FILE or --stdout") + sys.exit(1) + + duration = args.duration + total_bytes = 0 + start_time = time.time() + last_report = start_time + running = True + + def stop_handler(signum, frame): + nonlocal running + running = False + + signal.signal(signal.SIGINT, stop_handler) + signal.signal(signal.SIGTERM, stop_handler) + + status_fd.write(f"Streaming TS data") + if output_file: + status_fd.write(f" to {output_file}") + if duration: + status_fd.write(f" for {duration}s") + status_fd.write("\n") + status_fd.flush() + + # Arm the transfer + sw.arm_transfer(on=True) + status_fd.write(" Armed. Reading EP2...\n") + status_fd.flush() + + try: + while running: + if duration and (time.time() - start_time) >= duration: + break + + chunk = sw.read_stream(EP2_URB_SIZE, timeout=2000) + if chunk: + output_fd.write(chunk) + total_bytes += len(chunk) + + now = time.time() + if now - last_report >= 1.0: + elapsed = now - start_time + bitrate = (total_bytes * 8) / elapsed if elapsed > 0 else 0 + if bitrate >= 1e6: + rate_str = f"{bitrate / 1e6:.2f} Mbps" + else: + rate_str = f"{bitrate / 1e3:.1f} kbps" + status_fd.write(f"\r {total_bytes:,} bytes {rate_str} " + f"({elapsed:.0f}s) ") + status_fd.flush() + last_report = now + + finally: + sw.arm_transfer(on=False) + status_fd.write(f"\n Stopped. Total: {total_bytes:,} bytes\n") + if output_file and output_fd: + output_fd.close() + status_fd.write(f" Saved to: {output_file}\n") + + +def cmd_diseqc(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Send DiSEqC commands.""" + if args.tone_burst is not None: + burst_val = {"A": 0, "a": 0, "B": 1, "b": 1}.get(args.tone_burst) + if burst_val is None: + print("Tone burst must be A or B") + sys.exit(1) + print(f"Sending tone burst: SEC_MINI_{args.tone_burst.upper()} (0x{burst_val:02X})") + sw.send_diseqc_tone_burst(burst_val) + print(" OK") + + elif args.port is not None: + port = args.port + if port < 1 or port > 4: + print("DiSEqC 1.0 port must be 1-4") + sys.exit(1) + # DiSEqC 1.0 committed switch command: + # Framing=0xE0 (command from master, no reply, first tx) + # Address=0x10 (any switch) + # Command=0x38 (Write N0 - committed switches) + # Data=0xF0 | ((port-1) << 2) with option/position bits + # Bits: [7:4]=0xF (always), [3]=pol, [2]=band, [1:0]=port + # For simplicity, just switch port without changing pol/band bits + data_byte = 0xF0 | ((port - 1) << 2) + msg = bytes([0xE0, 0x10, 0x38, data_byte]) + print(f"Sending DiSEqC 1.0: port {port}") + print(f" Message: {msg.hex(' ')}") + sw.send_diseqc_message(msg) + print(" OK") + + elif args.raw: + raw_bytes = bytes(int(b, 16) for b in args.raw) + if len(raw_bytes) < 3 or len(raw_bytes) > 6: + print("Raw DiSEqC message must be 3-6 bytes") + sys.exit(1) + print(f"Sending raw DiSEqC: {raw_bytes.hex(' ')}") + sw.send_diseqc_message(raw_bytes) + print(" OK") + + else: + print("Specify --port, --tone-burst, or --raw") + sys.exit(1) + + +def cmd_lnb(sw: SkyWalker1, args: argparse.Namespace) -> None: + """Control LNB voltage and 22 kHz tone.""" + did_something = False + + # Ensure LNB power is on first + status = sw.get_config() + if not (status & 0x04): + print("Enabling LNB power supply...") + sw.start_intersil(on=True) + time.sleep(0.3) + + if args.voltage is not None: + v = args.voltage + if v not in (13, 18): + print("Voltage must be 13 or 18") + sys.exit(1) + high = (v == 18) + print(f"Setting LNB voltage: {v}V") + sw.set_lnb_voltage(high) + did_something = True + + if args.extra_volt: + print("Enabling +1V LNB boost") + sw.set_extra_voltage(True) + did_something = True + + if args.tone is not None: + tone_on = args.tone.lower() in ("on", "1", "true", "yes") + print(f"22 kHz tone: {'ON' if tone_on else 'OFF'}") + sw.set_22khz_tone(tone_on) + did_something = True + + if args.power is not None: + power_on = args.power.lower() in ("on", "1", "true", "yes") + if power_on: + print("Enabling LNB power supply") + sw.start_intersil(on=True) + else: + print("Disabling LNB power supply") + sw.start_intersil(on=False) + did_something = True + + if not did_something: + # Just show current LNB state + status = sw.get_config() + print(f"LNB Status:") + print(f" Power: {'ON' if status & 0x04 else 'off'}") + print(f" Voltage: {'18V' if status & 0x20 else '13V'}") + print(f" 22 kHz: {'ON' if status & 0x10 else 'off'}") + print(f" Armed: {'YES' if status & 0x80 else 'no'}") + else: + # Read back config to confirm + time.sleep(0.1) + status = sw.get_config() + print(f"\nConfig: 0x{status:02X}") + print(f" Power: {'ON' if status & 0x04 else 'off'} " + f"Voltage: {'18V' if status & 0x20 else '13V'} " + f"22kHz: {'ON' if status & 0x10 else 'off'}") + + +# -- CLI -- + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Genpix SkyWalker-1 DVB-S tuning and streaming tool", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +examples: + %(prog)s status + %(prog)s tune 12520 27500 --pol H --band high + %(prog)s tune 12520 27500 --pol H --band high --mod qpsk --fec auto + %(prog)s stream -o capture.ts --duration 60 + %(prog)s stream --stdout | vlc - + %(prog)s diseqc --port 1 + %(prog)s diseqc --tone-burst A + %(prog)s diseqc --raw E0 10 38 F0 + %(prog)s lnb --voltage 18 --tone on +""") + parser.add_argument('-v', '--verbose', action='store_true', + help="Show raw USB traffic") + parser.add_argument('--json', action='store_true', + help="Output machine-readable JSON where supported") + + sub = parser.add_subparsers(dest='command') + + # status + p_status = sub.add_parser('status', help="Show device config, FW version, signal status") + p_status.add_argument('--json', action='store_true', default=False, + help="Output machine-readable JSON") + + # tune + p_tune = sub.add_parser('tune', help="Tune to a transponder") + p_tune.add_argument('freq', type=float, + help="Transponder downlink frequency in MHz (e.g. 12520)") + p_tune.add_argument('sr', type=int, + help="Symbol rate in ksps (e.g. 27500)") + p_tune.add_argument('--pol', choices=['H', 'V', 'L', 'R', 'h', 'v', 'l', 'r'], + help="Polarization: H/V (linear) or L/R (circular)") + p_tune.add_argument('--band', choices=['low', 'high'], + help="LNB band: low (tone off) or high (tone on)") + p_tune.add_argument('--lnb-lo', type=float, default=None, + help="LNB LO frequency in MHz (default: 9750 low, 10600 high)") + p_tune.add_argument('--mod', default='qpsk', + choices=list(MODULATIONS.keys()), + help="Modulation type (default: qpsk)") + p_tune.add_argument('--fec', default='auto', + help="FEC rate (default: auto). Options depend on modulation.") + p_tune.add_argument('--timeout', type=float, default=10, + help="Signal lock timeout in seconds (default: 10)") + p_tune.add_argument('--extra-volt', action='store_true', + help="Enable +1V LNB voltage boost for long cables") + p_tune.add_argument('--json', action='store_true', default=False, + help="Output machine-readable JSON") + + # stream + p_stream = sub.add_parser('stream', help="Stream MPEG-2 TS data") + p_stream.add_argument('-o', '--output', help="Output file for TS data") + p_stream.add_argument('--stdout', action='store_true', + help="Write TS stream to stdout (pipe to vlc, ffmpeg, etc)") + p_stream.add_argument('--duration', type=float, default=None, + help="Capture duration in seconds (default: until CTRL-C)") + + # diseqc + p_diseqc = sub.add_parser('diseqc', help="Send DiSEqC commands") + p_diseqc.add_argument('--port', type=int, + help="DiSEqC 1.0 switch port (1-4)") + p_diseqc.add_argument('--tone-burst', metavar='A|B', + help="Mini DiSEqC tone burst (A or B)") + p_diseqc.add_argument('--raw', nargs='+', metavar='HH', + help="Raw DiSEqC bytes in hex (e.g. E0 10 38 F0)") + + # lnb + p_lnb = sub.add_parser('lnb', help="LNB voltage and tone control") + p_lnb.add_argument('--voltage', type=int, choices=[13, 18], + help="LNB voltage (13V or 18V)") + p_lnb.add_argument('--tone', help="22 kHz tone (on/off)") + p_lnb.add_argument('--extra-volt', action='store_true', + help="Enable +1V LNB voltage boost") + p_lnb.add_argument('--power', help="LNB power supply (on/off)") + + return parser + + +def main(): + parser = build_parser() + args = parser.parse_args() + + if not args.command: + # Default to status if no subcommand + args.command = 'status' + args.json = getattr(args, 'json', False) + + dispatch = { + 'status': cmd_status, + 'tune': cmd_tune, + 'stream': cmd_stream, + 'diseqc': cmd_diseqc, + 'lnb': cmd_lnb, + } + + handler = dispatch.get(args.command) + if handler is None: + parser.print_help() + sys.exit(1) + + with SkyWalker1(verbose=args.verbose) as sw: + handler(sw, args) + + +if __name__ == '__main__': + main() diff --git a/tools/wine_memdump.py b/tools/wine_memdump.py index 4dd498b..da5faee 100644 --- a/tools/wine_memdump.py +++ b/tools/wine_memdump.py @@ -1,346 +1,346 @@ -#!/usr/bin/env python3 -""" -Run a Windows PE under Wine and dump its process memory after unpacking. - -Launches the EXE, waits for it to unpack, then reads /proc/PID/mem -guided by /proc/PID/maps to capture the unpacked code and data sections. -Searches the dump for FX2 firmware signatures. -""" -import subprocess -import time -import os -import sys -import signal -import struct -import re -import glob - - -def find_wine_pid(exe_basename, timeout=10): - """Find the Wine process PID by looking for the .exe in /proc.""" - deadline = time.time() + timeout - while time.time() < deadline: - for pid_dir in glob.glob('/proc/[0-9]*'): - try: - cmdline = open(f'{pid_dir}/cmdline', 'rb').read() - if exe_basename.lower().encode() in cmdline.lower(): - pid = int(os.path.basename(pid_dir)) - # Skip if it's our own python process - if pid == os.getpid(): - continue - return pid - except (PermissionError, FileNotFoundError, ProcessLookupError): - continue - time.sleep(0.2) - return None - - -def dump_process_memory(pid, output_dir): - """Dump all readable memory regions of a process.""" - maps_path = f'/proc/{pid}/maps' - mem_path = f'/proc/{pid}/mem' - - regions = [] - try: - with open(maps_path, 'r') as f: - for line in f: - parts = line.split() - addr_range = parts[0] - perms = parts[1] - # Only dump readable regions - if 'r' not in perms: - continue - start_s, end_s = addr_range.split('-') - start = int(start_s, 16) - end = int(end_s, 16) - size = end - start - # Skip huge regions (> 64MB) and tiny ones - if size > 64 * 1024 * 1024 or size < 64: - continue - pathname = parts[5].strip() if len(parts) > 5 else "" - regions.append((start, end, perms, pathname)) - except (PermissionError, FileNotFoundError) as e: - print(f" Cannot read maps: {e}") - return None - - print(f" Found {len(regions)} readable memory regions") - - all_data = bytearray() - region_info = [] - - try: - with open(mem_path, 'rb') as mem: - for start, end, perms, pathname in regions: - size = end - start - try: - mem.seek(start) - chunk = mem.read(size) - offset_in_dump = len(all_data) - all_data.extend(chunk) - region_info.append({ - 'va_start': start, - 'va_end': end, - 'perms': perms, - 'pathname': pathname, - 'dump_offset': offset_in_dump, - 'size': len(chunk) - }) - except (OSError, ValueError): - pass - except PermissionError as e: - print(f" Cannot read mem: {e}") - print(" Try running with sudo or as the same user as Wine") - return None - - # Save full dump - dump_file = os.path.join(output_dir, 'wine_memdump.bin') - with open(dump_file, 'wb') as f: - f.write(all_data) - print(f" Saved {len(all_data)} bytes to {dump_file}") - - # Save region map - map_file = os.path.join(output_dir, 'wine_memdump_regions.txt') - with open(map_file, 'w') as f: - for r in region_info: - f.write(f"0x{r['va_start']:08X}-0x{r['va_end']:08X} " - f"{r['perms']:5s} dump_off=0x{r['dump_offset']:08X} " - f"size=0x{r['size']:06X} {r['pathname']}\n") - print(f" Saved region map to {map_file}") - - return all_data, region_info - - -def search_firmware(data, region_info): - """Search dumped memory for FX2 firmware signatures.""" - print(f"\n{'=' * 50}") - print("Searching for firmware signatures...") - print(f"{'=' * 50}") - - # 1. C2 EEPROM header with Genpix VID - print("\n[1] C2 EEPROM headers (C2 C0 09 03 02):") - c2_genpix = bytes([0xC2, 0xC0, 0x09, 0x03, 0x02]) - pos = 0 - while True: - idx = data.find(c2_genpix, pos) - if idx < 0: - break - region = find_region(region_info, idx) - ctx = bytes(data[idx:idx + 32]) - print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") - # Parse the full C2 header - if idx + 8 <= len(data): - vid = data[idx + 1] | (data[idx + 2] << 8) - pid = data[idx + 3] | (data[idx + 4] << 8) - did = data[idx + 5] | (data[idx + 6] << 8) - config = data[idx + 7] - print(f" VID=0x{vid:04X} PID=0x{pid:04X} DID=0x{did:04X} Config=0x{config:02X}") - pos = idx + 1 - - # 2. FX2 RAM clear init sequence - print("\n[2] FX2 init sequence (78 7F E4 F6 D8 FD 75 81):") - fx2_init = bytes([0x78, 0x7F, 0xE4, 0xF6, 0xD8, 0xFD, 0x75, 0x81]) - pos = 0 - while True: - idx = data.find(fx2_init, pos) - if idx < 0: - break - region = find_region(region_info, idx) - ctx = bytes(data[max(0, idx - 4):idx + 16]) - print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") - pos = idx + 1 - - # 3. Partial RAM clear pattern - print("\n[3] RAM clear pattern (78 7F E4 F6 D8 FD):") - ram_clear = bytes([0x78, 0x7F, 0xE4, 0xF6, 0xD8, 0xFD]) - pos = 0 - hits = 0 - while True: - idx = data.find(ram_clear, pos) - if idx < 0: - break - region = find_region(region_info, idx) - ctx = bytes(data[max(0, idx - 4):idx + 16]) - print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") - hits += 1 - if hits >= 10: - break - pos = idx + 1 - - # 4. LJMP at what could be code address 0x0000 (start of firmware) - # Look for 02 XX XX where XX XX is 0x0100-0x3FFF - print("\n[4] C2 load records (LEN_H LEN_L 00 00 02 = record at addr 0x0000):") - for off in range(len(data) - 8): - rec_len = (data[off] << 8) | data[off + 1] - if 0x0100 <= rec_len <= 0x4000: - if data[off + 2] == 0x00 and data[off + 3] == 0x00 and data[off + 4] == 0x02: - target = (data[off + 5] << 8) | data[off + 6] - if 0x0100 <= target <= 0x3FFF: - # Check if this looks like a valid C2 record chain - region = find_region(region_info, off) - ctx = bytes(data[off:off + 16]) - # Also check 8 bytes before for C2 header - has_c2_header = (off >= 8 and data[off - 8] == 0xC2) - header_note = " ** C2 HEADER 8 BYTES BEFORE! **" if has_c2_header else "" - print(f" 0x{off:08X} (VA: {region}): len={rec_len} " - f"addr=0x0000 LJMP 0x{target:04X} -- {ctx.hex(' ')}{header_note}") - - # 5. Known VID/PID bytes near potential firmware data - print("\n[5] VID 0x09C0 references:") - vid_bytes = b'\xC0\x09' - pos = 0 - hits = 0 - while True: - idx = data.find(vid_bytes, pos) - if idx < 0: - break - # Check if followed by PID within 4 bytes - if idx + 4 < len(data): - nearby = data[idx:idx + 8] - if b'\x03\x02' in nearby: - region = find_region(region_info, idx) - ctx = bytes(data[max(0, idx - 4):idx + 16]) - print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") - hits += 1 - if hits >= 20: - break - pos = idx + 1 - - # 6. Search for the firmware version string "2.13" - print("\n[6] Version strings:") - for pattern in [b'2.13', b'2.06', b'2.10', b'SkyWalker', b'Genpix', - b'8PSK', b'EEPROM', b'firmware', b'I2C']: - pos = 0 - while True: - idx = data.find(pattern, pos) - if idx < 0: - break - region = find_region(region_info, idx) - # Get surrounding context as ascii - start = max(0, idx - 16) - end = min(len(data), idx + 48) - ctx_bytes = bytes(data[start:end]) - ctx_ascii = ctx_bytes.decode('ascii', errors='replace') - ctx_ascii = re.sub(r'[^\x20-\x7e]', '.', ctx_ascii) - print(f" 0x{idx:08X} (VA: {region}): '{ctx_ascii}'") - pos = idx + 1 - - # 7. Look for USB vendor request setup patterns - # The updater will set bRequest=0x83 (I2C_WRITE) or 0xA0 to write firmware - print("\n[7] USB transfer setup (IOCTL/vendor request patterns):") - # WinUSB_ControlTransfer uses WINUSB_SETUP_PACKET: - # RequestType(1), Request(1), Value(2), Index(2), Length(2) - # For vendor OUT: RequestType=0x40, Request=0x83/0xA0 - for req_type, req, desc in [(0x40, 0xA0, "FX2 RAM write"), - (0x40, 0x83, "I2C_WRITE"), - (0x40, 0x84, "I2C_READ")]: - pattern = bytes([req_type, req]) - pos = 0 - hits_count = 0 - while True: - idx = data.find(pattern, pos) - if idx < 0: - break - # Check if followed by reasonable wValue/wIndex - if idx + 8 <= len(data): - wval = struct.unpack_from(' 0 and wlen < 0x4000: - region = find_region(region_info, idx) - print(f" 0x{idx:08X} ({desc}): " - f"ReqType=0x{req_type:02X} Req=0x{req:02X} " - f"wVal=0x{wval:04X} wIdx=0x{widx:04X} wLen=0x{wlen:04X} " - f"(VA: {region})") - hits_count += 1 - if hits_count >= 10: - break - pos = idx + 1 - - -def find_region(region_info, dump_offset): - """Find the VA region for a given dump offset.""" - for r in region_info: - if r['dump_offset'] <= dump_offset < r['dump_offset'] + r['size']: - va = r['va_start'] + (dump_offset - r['dump_offset']) - return f"0x{va:08X} [{r['pathname'] or 'anon'}]" - return "unknown" - - -def main(): - import argparse - parser = argparse.ArgumentParser(description="Wine memory dump for firmware extraction") - parser.add_argument('exe', help='Windows PE executable to run under Wine') - parser.add_argument('-o', '--output-dir', default='.', - help='Output directory for dumps') - parser.add_argument('--wait', type=float, default=3.0, - help='Seconds to wait after launch for unpacking (default: 3)') - parser.add_argument('--skip-launch', action='store_true', - help='Skip launching Wine, just attach to existing process') - args = parser.parse_args() - - exe_path = os.path.abspath(args.exe) - exe_basename = os.path.basename(exe_path) - os.makedirs(args.output_dir, exist_ok=True) - - wine_proc = None - pid = None - - if not args.skip_launch: - print(f"Launching {exe_basename} under Wine...") - # Use WINEDEBUG=-all to reduce noise - env = os.environ.copy() - env['WINEDEBUG'] = '-all' - wine_proc = subprocess.Popen( - ['wine', exe_path], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=env - ) - print(f" Wine wrapper PID: {wine_proc.pid}") - - # Wait for the actual .exe process to appear - print(f" Waiting {args.wait}s for unpacking...") - time.sleep(args.wait) - - # Find the Windows process PID - print(f" Looking for {exe_basename} process...") - pid = find_wine_pid(exe_basename, timeout=5) - if pid is None: - # Try looking for wine-preloader or wine64-preloader - print(" Couldn't find by exe name, searching all wine processes...") - for pid_dir in glob.glob('/proc/[0-9]*'): - try: - cmdline = open(f'{pid_dir}/cmdline', 'rb').read() - if b'wine' in cmdline.lower() and pid_dir != f'/proc/{os.getpid()}': - p = int(os.path.basename(pid_dir)) - if wine_proc and p == wine_proc.pid: - continue - print(f" Found wine process PID {p}: {cmdline[:100]}") - except: - pass - - if pid is None and wine_proc: - pid = wine_proc.pid - print(f" Using Wine wrapper PID: {pid}") - - if pid: - print(f"\n Target PID: {pid}") - result = dump_process_memory(pid, args.output_dir) - if result: - data, region_info = result - search_firmware(data, region_info) - else: - print(" ERROR: Could not find process") - - # Cleanup - if wine_proc: - print("\nTerminating Wine process...") - try: - wine_proc.terminate() - wine_proc.wait(timeout=5) - except: - wine_proc.kill() - - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 +""" +Run a Windows PE under Wine and dump its process memory after unpacking. + +Launches the EXE, waits for it to unpack, then reads /proc/PID/mem +guided by /proc/PID/maps to capture the unpacked code and data sections. +Searches the dump for FX2 firmware signatures. +""" +import subprocess +import time +import os +import sys +import signal +import struct +import re +import glob + + +def find_wine_pid(exe_basename, timeout=10): + """Find the Wine process PID by looking for the .exe in /proc.""" + deadline = time.time() + timeout + while time.time() < deadline: + for pid_dir in glob.glob('/proc/[0-9]*'): + try: + cmdline = open(f'{pid_dir}/cmdline', 'rb').read() + if exe_basename.lower().encode() in cmdline.lower(): + pid = int(os.path.basename(pid_dir)) + # Skip if it's our own python process + if pid == os.getpid(): + continue + return pid + except (PermissionError, FileNotFoundError, ProcessLookupError): + continue + time.sleep(0.2) + return None + + +def dump_process_memory(pid, output_dir): + """Dump all readable memory regions of a process.""" + maps_path = f'/proc/{pid}/maps' + mem_path = f'/proc/{pid}/mem' + + regions = [] + try: + with open(maps_path, 'r') as f: + for line in f: + parts = line.split() + addr_range = parts[0] + perms = parts[1] + # Only dump readable regions + if 'r' not in perms: + continue + start_s, end_s = addr_range.split('-') + start = int(start_s, 16) + end = int(end_s, 16) + size = end - start + # Skip huge regions (> 64MB) and tiny ones + if size > 64 * 1024 * 1024 or size < 64: + continue + pathname = parts[5].strip() if len(parts) > 5 else "" + regions.append((start, end, perms, pathname)) + except (PermissionError, FileNotFoundError) as e: + print(f" Cannot read maps: {e}") + return None + + print(f" Found {len(regions)} readable memory regions") + + all_data = bytearray() + region_info = [] + + try: + with open(mem_path, 'rb') as mem: + for start, end, perms, pathname in regions: + size = end - start + try: + mem.seek(start) + chunk = mem.read(size) + offset_in_dump = len(all_data) + all_data.extend(chunk) + region_info.append({ + 'va_start': start, + 'va_end': end, + 'perms': perms, + 'pathname': pathname, + 'dump_offset': offset_in_dump, + 'size': len(chunk) + }) + except (OSError, ValueError): + pass + except PermissionError as e: + print(f" Cannot read mem: {e}") + print(" Try running with sudo or as the same user as Wine") + return None + + # Save full dump + dump_file = os.path.join(output_dir, 'wine_memdump.bin') + with open(dump_file, 'wb') as f: + f.write(all_data) + print(f" Saved {len(all_data)} bytes to {dump_file}") + + # Save region map + map_file = os.path.join(output_dir, 'wine_memdump_regions.txt') + with open(map_file, 'w') as f: + for r in region_info: + f.write(f"0x{r['va_start']:08X}-0x{r['va_end']:08X} " + f"{r['perms']:5s} dump_off=0x{r['dump_offset']:08X} " + f"size=0x{r['size']:06X} {r['pathname']}\n") + print(f" Saved region map to {map_file}") + + return all_data, region_info + + +def search_firmware(data, region_info): + """Search dumped memory for FX2 firmware signatures.""" + print(f"\n{'=' * 50}") + print("Searching for firmware signatures...") + print(f"{'=' * 50}") + + # 1. C2 EEPROM header with Genpix VID + print("\n[1] C2 EEPROM headers (C2 C0 09 03 02):") + c2_genpix = bytes([0xC2, 0xC0, 0x09, 0x03, 0x02]) + pos = 0 + while True: + idx = data.find(c2_genpix, pos) + if idx < 0: + break + region = find_region(region_info, idx) + ctx = bytes(data[idx:idx + 32]) + print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") + # Parse the full C2 header + if idx + 8 <= len(data): + vid = data[idx + 1] | (data[idx + 2] << 8) + pid = data[idx + 3] | (data[idx + 4] << 8) + did = data[idx + 5] | (data[idx + 6] << 8) + config = data[idx + 7] + print(f" VID=0x{vid:04X} PID=0x{pid:04X} DID=0x{did:04X} Config=0x{config:02X}") + pos = idx + 1 + + # 2. FX2 RAM clear init sequence + print("\n[2] FX2 init sequence (78 7F E4 F6 D8 FD 75 81):") + fx2_init = bytes([0x78, 0x7F, 0xE4, 0xF6, 0xD8, 0xFD, 0x75, 0x81]) + pos = 0 + while True: + idx = data.find(fx2_init, pos) + if idx < 0: + break + region = find_region(region_info, idx) + ctx = bytes(data[max(0, idx - 4):idx + 16]) + print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") + pos = idx + 1 + + # 3. Partial RAM clear pattern + print("\n[3] RAM clear pattern (78 7F E4 F6 D8 FD):") + ram_clear = bytes([0x78, 0x7F, 0xE4, 0xF6, 0xD8, 0xFD]) + pos = 0 + hits = 0 + while True: + idx = data.find(ram_clear, pos) + if idx < 0: + break + region = find_region(region_info, idx) + ctx = bytes(data[max(0, idx - 4):idx + 16]) + print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") + hits += 1 + if hits >= 10: + break + pos = idx + 1 + + # 4. LJMP at what could be code address 0x0000 (start of firmware) + # Look for 02 XX XX where XX XX is 0x0100-0x3FFF + print("\n[4] C2 load records (LEN_H LEN_L 00 00 02 = record at addr 0x0000):") + for off in range(len(data) - 8): + rec_len = (data[off] << 8) | data[off + 1] + if 0x0100 <= rec_len <= 0x4000: + if data[off + 2] == 0x00 and data[off + 3] == 0x00 and data[off + 4] == 0x02: + target = (data[off + 5] << 8) | data[off + 6] + if 0x0100 <= target <= 0x3FFF: + # Check if this looks like a valid C2 record chain + region = find_region(region_info, off) + ctx = bytes(data[off:off + 16]) + # Also check 8 bytes before for C2 header + has_c2_header = (off >= 8 and data[off - 8] == 0xC2) + header_note = " ** C2 HEADER 8 BYTES BEFORE! **" if has_c2_header else "" + print(f" 0x{off:08X} (VA: {region}): len={rec_len} " + f"addr=0x0000 LJMP 0x{target:04X} -- {ctx.hex(' ')}{header_note}") + + # 5. Known VID/PID bytes near potential firmware data + print("\n[5] VID 0x09C0 references:") + vid_bytes = b'\xC0\x09' + pos = 0 + hits = 0 + while True: + idx = data.find(vid_bytes, pos) + if idx < 0: + break + # Check if followed by PID within 4 bytes + if idx + 4 < len(data): + nearby = data[idx:idx + 8] + if b'\x03\x02' in nearby: + region = find_region(region_info, idx) + ctx = bytes(data[max(0, idx - 4):idx + 16]) + print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") + hits += 1 + if hits >= 20: + break + pos = idx + 1 + + # 6. Search for the firmware version string "2.13" + print("\n[6] Version strings:") + for pattern in [b'2.13', b'2.06', b'2.10', b'SkyWalker', b'Genpix', + b'8PSK', b'EEPROM', b'firmware', b'I2C']: + pos = 0 + while True: + idx = data.find(pattern, pos) + if idx < 0: + break + region = find_region(region_info, idx) + # Get surrounding context as ascii + start = max(0, idx - 16) + end = min(len(data), idx + 48) + ctx_bytes = bytes(data[start:end]) + ctx_ascii = ctx_bytes.decode('ascii', errors='replace') + ctx_ascii = re.sub(r'[^\x20-\x7e]', '.', ctx_ascii) + print(f" 0x{idx:08X} (VA: {region}): '{ctx_ascii}'") + pos = idx + 1 + + # 7. Look for USB vendor request setup patterns + # The updater will set bRequest=0x83 (I2C_WRITE) or 0xA0 to write firmware + print("\n[7] USB transfer setup (IOCTL/vendor request patterns):") + # WinUSB_ControlTransfer uses WINUSB_SETUP_PACKET: + # RequestType(1), Request(1), Value(2), Index(2), Length(2) + # For vendor OUT: RequestType=0x40, Request=0x83/0xA0 + for req_type, req, desc in [(0x40, 0xA0, "FX2 RAM write"), + (0x40, 0x83, "I2C_WRITE"), + (0x40, 0x84, "I2C_READ")]: + pattern = bytes([req_type, req]) + pos = 0 + hits_count = 0 + while True: + idx = data.find(pattern, pos) + if idx < 0: + break + # Check if followed by reasonable wValue/wIndex + if idx + 8 <= len(data): + wval = struct.unpack_from(' 0 and wlen < 0x4000: + region = find_region(region_info, idx) + print(f" 0x{idx:08X} ({desc}): " + f"ReqType=0x{req_type:02X} Req=0x{req:02X} " + f"wVal=0x{wval:04X} wIdx=0x{widx:04X} wLen=0x{wlen:04X} " + f"(VA: {region})") + hits_count += 1 + if hits_count >= 10: + break + pos = idx + 1 + + +def find_region(region_info, dump_offset): + """Find the VA region for a given dump offset.""" + for r in region_info: + if r['dump_offset'] <= dump_offset < r['dump_offset'] + r['size']: + va = r['va_start'] + (dump_offset - r['dump_offset']) + return f"0x{va:08X} [{r['pathname'] or 'anon'}]" + return "unknown" + + +def main(): + import argparse + parser = argparse.ArgumentParser(description="Wine memory dump for firmware extraction") + parser.add_argument('exe', help='Windows PE executable to run under Wine') + parser.add_argument('-o', '--output-dir', default='.', + help='Output directory for dumps') + parser.add_argument('--wait', type=float, default=3.0, + help='Seconds to wait after launch for unpacking (default: 3)') + parser.add_argument('--skip-launch', action='store_true', + help='Skip launching Wine, just attach to existing process') + args = parser.parse_args() + + exe_path = os.path.abspath(args.exe) + exe_basename = os.path.basename(exe_path) + os.makedirs(args.output_dir, exist_ok=True) + + wine_proc = None + pid = None + + if not args.skip_launch: + print(f"Launching {exe_basename} under Wine...") + # Use WINEDEBUG=-all to reduce noise + env = os.environ.copy() + env['WINEDEBUG'] = '-all' + wine_proc = subprocess.Popen( + ['wine', exe_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env + ) + print(f" Wine wrapper PID: {wine_proc.pid}") + + # Wait for the actual .exe process to appear + print(f" Waiting {args.wait}s for unpacking...") + time.sleep(args.wait) + + # Find the Windows process PID + print(f" Looking for {exe_basename} process...") + pid = find_wine_pid(exe_basename, timeout=5) + if pid is None: + # Try looking for wine-preloader or wine64-preloader + print(" Couldn't find by exe name, searching all wine processes...") + for pid_dir in glob.glob('/proc/[0-9]*'): + try: + cmdline = open(f'{pid_dir}/cmdline', 'rb').read() + if b'wine' in cmdline.lower() and pid_dir != f'/proc/{os.getpid()}': + p = int(os.path.basename(pid_dir)) + if wine_proc and p == wine_proc.pid: + continue + print(f" Found wine process PID {p}: {cmdline[:100]}") + except: + pass + + if pid is None and wine_proc: + pid = wine_proc.pid + print(f" Using Wine wrapper PID: {pid}") + + if pid: + print(f"\n Target PID: {pid}") + result = dump_process_memory(pid, args.output_dir) + if result: + data, region_info = result + search_firmware(data, region_info) + else: + print(" ERROR: Could not find process") + + # Cleanup + if wine_proc: + print("\nTerminating Wine process...") + try: + wine_proc.terminate() + wine_proc.wait(timeout=5) + except: + wine_proc.kill() + + +if __name__ == '__main__': + main() diff --git a/tui/pyproject.toml b/tui/pyproject.toml index db220c0..1ce2848 100644 --- a/tui/pyproject.toml +++ b/tui/pyproject.toml @@ -1,38 +1,38 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "skywalker-tui" -version = "0.1.0" -description = "Textual TUI for Genpix SkyWalker-1 DVB-S receiver" -requires-python = ">=3.11" -authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}] -dependencies = [ - "textual>=3.0", - "pyusb>=1.3", -] - -[project.scripts] -skywalker-tui = "skywalker_tui.app:main" - -[tool.hatch.build.targets.wheel] -packages = ["src/skywalker_tui"] -artifacts = ["src/skywalker_tui/assets/**"] - -[project.optional-dependencies] -dev = [ - "Pillow>=10.0", -] -test = [ - "pytest>=8.0", - "pytest-asyncio>=0.24", -] - -[tool.pytest.ini_options] -testpaths = ["tests"] -asyncio_mode = "auto" - -[tool.ruff] -target-version = "py311" -line-length = 100 +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "skywalker-tui" +version = "0.1.0" +description = "Textual TUI for Genpix SkyWalker-1 DVB-S receiver" +requires-python = ">=3.11" +authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}] +dependencies = [ + "textual>=3.0", + "pyusb>=1.3", +] + +[project.scripts] +skywalker-tui = "skywalker_tui.app:main" + +[tool.hatch.build.targets.wheel] +packages = ["src/skywalker_tui"] +artifacts = ["src/skywalker_tui/assets/**"] + +[project.optional-dependencies] +dev = [ + "Pillow>=10.0", +] +test = [ + "pytest>=8.0", + "pytest-asyncio>=0.24", +] + +[tool.pytest.ini_options] +testpaths = ["tests"] +asyncio_mode = "auto" + +[tool.ruff] +target-version = "py311" +line-length = 100 diff --git a/tui/scripts/fetch_art.py b/tui/scripts/fetch_art.py index f144244..8f257b3 100644 --- a/tui/scripts/fetch_art.py +++ b/tui/scripts/fetch_art.py @@ -1,99 +1,99 @@ -#!/usr/bin/env python3 -"""Download splash art from 16colo.rs for SkyWalker-1 TUI. - -Fetches pixel/teletext art from Mistigris packs on 16colo.rs and saves -them as bundled assets for the splash screen. - -Usage: - python scripts/fetch_art.py -""" - -import urllib.request -import sys -from pathlib import Path - -ASSETS_DIR = Path(__file__).resolve().parent.parent / "src" / "skywalker_tui" / "assets" / "splash" - -ART_SOURCES = [ - { - "url": "https://16colo.rs/pack/mist0717/raw/ILLARTERATE-SETI-100_02_SATELLITE.PNG", - "filename": "seti-satellite.png", - "artist": "Illarterate", - "title": "S.E.T.I. Satellite", - "pack": "mist0717", - }, - { - "url": "https://16colo.rs/pack/mist1119/raw/192.168.10.13-DIALTONE.JPG", - "filename": "dialtone.jpg", - "artist": "192.168.10.13", - "title": "Dialtone", - "pack": "mist1119", - }, - { - "url": "https://16colo.rs/pack/mist0523/raw/BLIPPYPIXEL-SO_FAR_AWAY.GIF", - "filename": "so-far-away.gif", - "artist": "Blippypixel", - "title": "So Far Away", - "pack": "mist0523", - }, - { - "url": "https://16colo.rs/pack/mist0121/raw/JELLICA_JAKE-PRODIGY.JPG", - "filename": "prodigy-out-of-space.jpg", - "artist": "Jellica Jake", - "title": "Prodigy / Out of Space", - "pack": "mist0121", - }, - { - "url": "https://16colo.rs/pack/mist1120/raw/BLIPPYPIXEL-SPACE_DOCKER.GIF", - "filename": "space-docker.gif", - "artist": "Blippypixel", - "title": "Space Docker", - "pack": "mist1120", - }, -] - - -def fetch_all(): - ASSETS_DIR.mkdir(parents=True, exist_ok=True) - downloaded = 0 - - for art in ART_SOURCES: - dest = ASSETS_DIR / art["filename"] - if dest.exists(): - print(f" exists: {art['filename']}", file=sys.stderr) - downloaded += 1 - continue - - print(f" fetch: {art['filename']} from {art['url']}", file=sys.stderr) - try: - req = urllib.request.Request(art["url"], headers={"User-Agent": "SkyWalker-1-TUI/0.1"}) - with urllib.request.urlopen(req, timeout=30) as resp: - data = resp.read() - dest.write_bytes(data) - print(f" {len(data):,} bytes -> {dest.name}", file=sys.stderr) - downloaded += 1 - except Exception as e: - print(f" FAIL: {art['filename']}: {e}", file=sys.stderr) - - # Write CREDITS.md - credits = ASSETS_DIR / "CREDITS.md" - lines = ["# Splash Art Credits\n", ""] - lines.append("All artwork sourced from [16colo.rs](https://16colo.rs) /") - lines.append("[Mistigris](https://mistigris.com) art packs.\n") - lines.append("| File | Artist | Title | Pack |") - lines.append("|------|--------|-------|------|") - for art in ART_SOURCES: - pack_url = f"https://16colo.rs/pack/{art['pack']}" - lines.append( - f"| `{art['filename']}` | {art['artist']} " - f"| {art['title']} | [{art['pack']}]({pack_url}) |" - ) - lines.append("") - credits.write_text("\n".join(lines)) - - print(f"\n {downloaded}/{len(ART_SOURCES)} images downloaded to {ASSETS_DIR}", file=sys.stderr) - return downloaded - - -if __name__ == "__main__": - fetch_all() +#!/usr/bin/env python3 +"""Download splash art from 16colo.rs for SkyWalker-1 TUI. + +Fetches pixel/teletext art from Mistigris packs on 16colo.rs and saves +them as bundled assets for the splash screen. + +Usage: + python scripts/fetch_art.py +""" + +import urllib.request +import sys +from pathlib import Path + +ASSETS_DIR = Path(__file__).resolve().parent.parent / "src" / "skywalker_tui" / "assets" / "splash" + +ART_SOURCES = [ + { + "url": "https://16colo.rs/pack/mist0717/raw/ILLARTERATE-SETI-100_02_SATELLITE.PNG", + "filename": "seti-satellite.png", + "artist": "Illarterate", + "title": "S.E.T.I. Satellite", + "pack": "mist0717", + }, + { + "url": "https://16colo.rs/pack/mist1119/raw/192.168.10.13-DIALTONE.JPG", + "filename": "dialtone.jpg", + "artist": "192.168.10.13", + "title": "Dialtone", + "pack": "mist1119", + }, + { + "url": "https://16colo.rs/pack/mist0523/raw/BLIPPYPIXEL-SO_FAR_AWAY.GIF", + "filename": "so-far-away.gif", + "artist": "Blippypixel", + "title": "So Far Away", + "pack": "mist0523", + }, + { + "url": "https://16colo.rs/pack/mist0121/raw/JELLICA_JAKE-PRODIGY.JPG", + "filename": "prodigy-out-of-space.jpg", + "artist": "Jellica Jake", + "title": "Prodigy / Out of Space", + "pack": "mist0121", + }, + { + "url": "https://16colo.rs/pack/mist1120/raw/BLIPPYPIXEL-SPACE_DOCKER.GIF", + "filename": "space-docker.gif", + "artist": "Blippypixel", + "title": "Space Docker", + "pack": "mist1120", + }, +] + + +def fetch_all(): + ASSETS_DIR.mkdir(parents=True, exist_ok=True) + downloaded = 0 + + for art in ART_SOURCES: + dest = ASSETS_DIR / art["filename"] + if dest.exists(): + print(f" exists: {art['filename']}", file=sys.stderr) + downloaded += 1 + continue + + print(f" fetch: {art['filename']} from {art['url']}", file=sys.stderr) + try: + req = urllib.request.Request(art["url"], headers={"User-Agent": "SkyWalker-1-TUI/0.1"}) + with urllib.request.urlopen(req, timeout=30) as resp: + data = resp.read() + dest.write_bytes(data) + print(f" {len(data):,} bytes -> {dest.name}", file=sys.stderr) + downloaded += 1 + except Exception as e: + print(f" FAIL: {art['filename']}: {e}", file=sys.stderr) + + # Write CREDITS.md + credits = ASSETS_DIR / "CREDITS.md" + lines = ["# Splash Art Credits\n", ""] + lines.append("All artwork sourced from [16colo.rs](https://16colo.rs) /") + lines.append("[Mistigris](https://mistigris.com) art packs.\n") + lines.append("| File | Artist | Title | Pack |") + lines.append("|------|--------|-------|------|") + for art in ART_SOURCES: + pack_url = f"https://16colo.rs/pack/{art['pack']}" + lines.append( + f"| `{art['filename']}` | {art['artist']} " + f"| {art['title']} | [{art['pack']}]({pack_url}) |" + ) + lines.append("") + credits.write_text("\n".join(lines)) + + print(f"\n {downloaded}/{len(ART_SOURCES)} images downloaded to {ASSETS_DIR}", file=sys.stderr) + return downloaded + + +if __name__ == "__main__": + fetch_all() diff --git a/tui/scripts/generate_screenshots.py b/tui/scripts/generate_screenshots.py index cee9162..8637a7a 100644 --- a/tui/scripts/generate_screenshots.py +++ b/tui/scripts/generate_screenshots.py @@ -1,247 +1,247 @@ -#!/usr/bin/env python3 -"""Generate SVG screenshots of every TUI screen for documentation. - -Uses Textual's headless run_test() + Pilot API to programmatically navigate -each screen and export SVG renders. Requires no hardware — runs entirely -with DemoDevice synthetic signal data. - -Output: ../site/src/assets/tui/*.svg (13 screenshots) - -Usage: - cd tui && uv run python scripts/generate_screenshots.py -""" - -import asyncio -import sys -from pathlib import Path - -# Ensure the src layout is importable when running from scripts/ -sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src")) - -from skywalker_tui.app import SkyWalkerApp -from skywalker_tui.bridge import USBBridge -from skywalker_tui.demo import DemoDevice - -OUTPUT_DIR = Path(__file__).resolve().parent.parent.parent / "site" / "src" / "assets" / "tui" - -# Terminal size for screenshots — wide enough for sidebar + content -TERM_SIZE = (120, 36) - -# Pause durations for rendering -MOUNT_PAUSE = 1.5 # initial mount + mode screen init -MODE_SWITCH_PAUSE = 0.8 # after F-key press -NOTIFY_PAUSE = 0.6 # for toast notifications -STARWARS_PAUSE = 12.0 # time for offline crawl to reach Star Destroyer frame - - -def _new_app(**kwargs) -> SkyWalkerApp: - """Create a fresh app instance with demo device.""" - return SkyWalkerApp(bridge=USBBridge(DemoDevice()), **kwargs) - - -def _save(svg: str, name: str) -> None: - path = OUTPUT_DIR / f"{name}.svg" - path.write_text(svg) - print(f" OK {name}.svg ({len(svg):,} bytes)") - - -async def capture_mode_screens() -> None: - """Capture F1-F5 RF mode screens.""" - app = _new_app(show_splash=False) - - async with app.run_test(size=TERM_SIZE, headless=True) as pilot: - await pilot.pause(MOUNT_PAUSE) - - modes = [ - ("f1", "spectrum", "Spectrum"), - ("f2", "scan", "Scan"), - ("f3", "monitor", "Monitor"), - ("f4", "lband", "L-Band"), - ("f5", "track", "Track"), - ] - - for key, filename, label in modes: - await pilot.press(key) - await pilot.pause(MODE_SWITCH_PAUSE) - svg = app.export_screenshot(title=f"SkyWalker-1 — {label}") - _save(svg, filename) - - -async def capture_device_screen() -> None: - """Capture F6 Device screen — show EEPROM tab with hex dump.""" - app = _new_app(show_splash=False) - - async with app.run_test(size=TERM_SIZE, headless=True) as pilot: - await pilot.pause(MOUNT_PAUSE) - - # Switch to Device screen - await pilot.press("f6") - await pilot.pause(MODE_SWITCH_PAUSE) - - # Wait for identity info to populate - await pilot.pause(1.0) - - # Capture Firmware tab (default) - svg = app.export_screenshot(title="SkyWalker-1 — Device") - _save(svg, "device") - - -async def capture_stream_screen() -> None: - """Capture F7 Stream screen — needs time for TS packets to accumulate.""" - app = _new_app(show_splash=False) - - async with app.run_test(size=TERM_SIZE, headless=True) as pilot: - await pilot.pause(MOUNT_PAUSE) - - # Switch to Stream screen (auto-starts in demo mode) - await pilot.press("f7") - await pilot.pause(MODE_SWITCH_PAUSE) - - # Wait for PID stats and PSI tree to populate - await pilot.pause(2.0) - - svg = app.export_screenshot(title="SkyWalker-1 — Stream") - _save(svg, "stream") - - -async def capture_config_screen() -> None: - """Capture F8 Config screen.""" - app = _new_app(show_splash=False) - - async with app.run_test(size=TERM_SIZE, headless=True) as pilot: - await pilot.pause(MOUNT_PAUSE) - - # Switch to Config screen - await pilot.press("f8") - await pilot.pause(MODE_SWITCH_PAUSE) - - # Wait for config status to load - await pilot.pause(0.5) - - svg = app.export_screenshot(title="SkyWalker-1 — Config") - _save(svg, "config") - - -async def capture_motor_screen() -> None: - """Capture F9 Motor screen — 3-column layout with signal gauge.""" - app = _new_app(show_splash=False) - - async with app.run_test(size=TERM_SIZE, headless=True) as pilot: - await pilot.pause(MOUNT_PAUSE) - - # Switch to Motor screen - await pilot.press("f9") - await pilot.pause(MODE_SWITCH_PAUSE) - - # Wait for signal gauge to populate - await pilot.pause(1.0) - - svg = app.export_screenshot(title="SkyWalker-1 — Motor Control") - _save(svg, "motor") - - -async def capture_survey_screen() -> None: - """Capture F10 Survey screen — Full Band tab with spectrum plot.""" - app = _new_app(show_splash=False) - - async with app.run_test(size=TERM_SIZE, headless=True) as pilot: - await pilot.pause(MOUNT_PAUSE) - - # Switch to Survey screen (Full Band tab is default) - await pilot.press("f10") - await pilot.pause(MODE_SWITCH_PAUSE) - - # Wait for demo spectrum data to render - await pilot.pause(1.5) - - svg = app.export_screenshot(title="SkyWalker-1 — Carrier Survey") - _save(svg, "survey") - - -async def capture_dark_mode() -> None: - """Capture dark-mode toggle with Star Wars notification toast.""" - app = _new_app(show_splash=False) - - async with app.run_test(size=TERM_SIZE, headless=True) as pilot: - await pilot.pause(MOUNT_PAUSE) - - # Toggle to light then back to dark to trigger "Dark Side" notification - await pilot.press("d") # -> light - await pilot.pause(0.3) - await pilot.press("d") # -> dark (shows "Dark Side" toast) - await pilot.pause(NOTIFY_PAUSE) - svg = app.export_screenshot(title="SkyWalker-1 — Dark Side") - _save(svg, "dark-mode") - - -async def capture_splash() -> None: - """Capture splash screen — needs show_splash=True and quick capture.""" - app = _new_app(show_splash=True) - - async with app.run_test(size=TERM_SIZE, headless=True) as pilot: - # Splash auto-dismisses after 5s, capture it quickly - await pilot.pause(MOUNT_PAUSE) - svg = app.export_screenshot(title="SkyWalker-1 — Splash") - _save(svg, "splash") - - -async def capture_starwars() -> None: - """Capture Star Wars easter egg — uses offline fallback crawl. - - The offline crawl plays through several frames: - 1. Black pause (2s) - 2. "A long time ago..." (3s) - 3. STAR WARS logo (4s) - 4. Episode info (3s) - 5. Opening crawl (per-line at 0.18s) - 6. Star Destroyer (3.5s) - 7. Credits (stays) - - We wait long enough to capture the Star Destroyer frame. - """ - app = _new_app(show_splash=False) - - async with app.run_test(size=TERM_SIZE, headless=True) as pilot: - await pilot.pause(MOUNT_PAUSE) - await pilot.press("ctrl+w") - await pilot.pause(STARWARS_PAUSE) - svg = app.export_screenshot(title="SkyWalker-1 — Star Wars") - _save(svg, "starwars") - - -async def main() -> None: - OUTPUT_DIR.mkdir(parents=True, exist_ok=True) - print(f"Generating TUI screenshots -> {OUTPUT_DIR}/\n") - - captures = [ - ("Mode screens (F1-F5)", capture_mode_screens), - ("Device screen (F6)", capture_device_screen), - ("Stream screen (F7)", capture_stream_screen), - ("Config screen (F8)", capture_config_screen), - ("Motor screen (F9)", capture_motor_screen), - ("Survey screen (F10)", capture_survey_screen), - ("Dark mode toggle", capture_dark_mode), - ("Splash screen", capture_splash), - ("Star Wars easter egg", capture_starwars), - ] - - failed = [] - for label, fn in captures: - print(f"── {label} ──") - try: - await fn() - except Exception as e: - print(f" FAIL {e}") - failed.append(label) - print() - - count = len(list(OUTPUT_DIR.glob("*.svg"))) - print(f"Done. {count} SVG screenshots generated.") - - if failed: - print(f"\nFailed: {', '.join(failed)}") - sys.exit(1) - - -if __name__ == "__main__": - asyncio.run(main()) +#!/usr/bin/env python3 +"""Generate SVG screenshots of every TUI screen for documentation. + +Uses Textual's headless run_test() + Pilot API to programmatically navigate +each screen and export SVG renders. Requires no hardware — runs entirely +with DemoDevice synthetic signal data. + +Output: ../site/src/assets/tui/*.svg (13 screenshots) + +Usage: + cd tui && uv run python scripts/generate_screenshots.py +""" + +import asyncio +import sys +from pathlib import Path + +# Ensure the src layout is importable when running from scripts/ +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src")) + +from skywalker_tui.app import SkyWalkerApp +from skywalker_tui.bridge import USBBridge +from skywalker_tui.demo import DemoDevice + +OUTPUT_DIR = Path(__file__).resolve().parent.parent.parent / "site" / "src" / "assets" / "tui" + +# Terminal size for screenshots — wide enough for sidebar + content +TERM_SIZE = (120, 36) + +# Pause durations for rendering +MOUNT_PAUSE = 1.5 # initial mount + mode screen init +MODE_SWITCH_PAUSE = 0.8 # after F-key press +NOTIFY_PAUSE = 0.6 # for toast notifications +STARWARS_PAUSE = 12.0 # time for offline crawl to reach Star Destroyer frame + + +def _new_app(**kwargs) -> SkyWalkerApp: + """Create a fresh app instance with demo device.""" + return SkyWalkerApp(bridge=USBBridge(DemoDevice()), **kwargs) + + +def _save(svg: str, name: str) -> None: + path = OUTPUT_DIR / f"{name}.svg" + path.write_text(svg) + print(f" OK {name}.svg ({len(svg):,} bytes)") + + +async def capture_mode_screens() -> None: + """Capture F1-F5 RF mode screens.""" + app = _new_app(show_splash=False) + + async with app.run_test(size=TERM_SIZE, headless=True) as pilot: + await pilot.pause(MOUNT_PAUSE) + + modes = [ + ("f1", "spectrum", "Spectrum"), + ("f2", "scan", "Scan"), + ("f3", "monitor", "Monitor"), + ("f4", "lband", "L-Band"), + ("f5", "track", "Track"), + ] + + for key, filename, label in modes: + await pilot.press(key) + await pilot.pause(MODE_SWITCH_PAUSE) + svg = app.export_screenshot(title=f"SkyWalker-1 — {label}") + _save(svg, filename) + + +async def capture_device_screen() -> None: + """Capture F6 Device screen — show EEPROM tab with hex dump.""" + app = _new_app(show_splash=False) + + async with app.run_test(size=TERM_SIZE, headless=True) as pilot: + await pilot.pause(MOUNT_PAUSE) + + # Switch to Device screen + await pilot.press("f6") + await pilot.pause(MODE_SWITCH_PAUSE) + + # Wait for identity info to populate + await pilot.pause(1.0) + + # Capture Firmware tab (default) + svg = app.export_screenshot(title="SkyWalker-1 — Device") + _save(svg, "device") + + +async def capture_stream_screen() -> None: + """Capture F7 Stream screen — needs time for TS packets to accumulate.""" + app = _new_app(show_splash=False) + + async with app.run_test(size=TERM_SIZE, headless=True) as pilot: + await pilot.pause(MOUNT_PAUSE) + + # Switch to Stream screen (auto-starts in demo mode) + await pilot.press("f7") + await pilot.pause(MODE_SWITCH_PAUSE) + + # Wait for PID stats and PSI tree to populate + await pilot.pause(2.0) + + svg = app.export_screenshot(title="SkyWalker-1 — Stream") + _save(svg, "stream") + + +async def capture_config_screen() -> None: + """Capture F8 Config screen.""" + app = _new_app(show_splash=False) + + async with app.run_test(size=TERM_SIZE, headless=True) as pilot: + await pilot.pause(MOUNT_PAUSE) + + # Switch to Config screen + await pilot.press("f8") + await pilot.pause(MODE_SWITCH_PAUSE) + + # Wait for config status to load + await pilot.pause(0.5) + + svg = app.export_screenshot(title="SkyWalker-1 — Config") + _save(svg, "config") + + +async def capture_motor_screen() -> None: + """Capture F9 Motor screen — 3-column layout with signal gauge.""" + app = _new_app(show_splash=False) + + async with app.run_test(size=TERM_SIZE, headless=True) as pilot: + await pilot.pause(MOUNT_PAUSE) + + # Switch to Motor screen + await pilot.press("f9") + await pilot.pause(MODE_SWITCH_PAUSE) + + # Wait for signal gauge to populate + await pilot.pause(1.0) + + svg = app.export_screenshot(title="SkyWalker-1 — Motor Control") + _save(svg, "motor") + + +async def capture_survey_screen() -> None: + """Capture F10 Survey screen — Full Band tab with spectrum plot.""" + app = _new_app(show_splash=False) + + async with app.run_test(size=TERM_SIZE, headless=True) as pilot: + await pilot.pause(MOUNT_PAUSE) + + # Switch to Survey screen (Full Band tab is default) + await pilot.press("f10") + await pilot.pause(MODE_SWITCH_PAUSE) + + # Wait for demo spectrum data to render + await pilot.pause(1.5) + + svg = app.export_screenshot(title="SkyWalker-1 — Carrier Survey") + _save(svg, "survey") + + +async def capture_dark_mode() -> None: + """Capture dark-mode toggle with Star Wars notification toast.""" + app = _new_app(show_splash=False) + + async with app.run_test(size=TERM_SIZE, headless=True) as pilot: + await pilot.pause(MOUNT_PAUSE) + + # Toggle to light then back to dark to trigger "Dark Side" notification + await pilot.press("d") # -> light + await pilot.pause(0.3) + await pilot.press("d") # -> dark (shows "Dark Side" toast) + await pilot.pause(NOTIFY_PAUSE) + svg = app.export_screenshot(title="SkyWalker-1 — Dark Side") + _save(svg, "dark-mode") + + +async def capture_splash() -> None: + """Capture splash screen — needs show_splash=True and quick capture.""" + app = _new_app(show_splash=True) + + async with app.run_test(size=TERM_SIZE, headless=True) as pilot: + # Splash auto-dismisses after 5s, capture it quickly + await pilot.pause(MOUNT_PAUSE) + svg = app.export_screenshot(title="SkyWalker-1 — Splash") + _save(svg, "splash") + + +async def capture_starwars() -> None: + """Capture Star Wars easter egg — uses offline fallback crawl. + + The offline crawl plays through several frames: + 1. Black pause (2s) + 2. "A long time ago..." (3s) + 3. STAR WARS logo (4s) + 4. Episode info (3s) + 5. Opening crawl (per-line at 0.18s) + 6. Star Destroyer (3.5s) + 7. Credits (stays) + + We wait long enough to capture the Star Destroyer frame. + """ + app = _new_app(show_splash=False) + + async with app.run_test(size=TERM_SIZE, headless=True) as pilot: + await pilot.pause(MOUNT_PAUSE) + await pilot.press("ctrl+w") + await pilot.pause(STARWARS_PAUSE) + svg = app.export_screenshot(title="SkyWalker-1 — Star Wars") + _save(svg, "starwars") + + +async def main() -> None: + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + print(f"Generating TUI screenshots -> {OUTPUT_DIR}/\n") + + captures = [ + ("Mode screens (F1-F5)", capture_mode_screens), + ("Device screen (F6)", capture_device_screen), + ("Stream screen (F7)", capture_stream_screen), + ("Config screen (F8)", capture_config_screen), + ("Motor screen (F9)", capture_motor_screen), + ("Survey screen (F10)", capture_survey_screen), + ("Dark mode toggle", capture_dark_mode), + ("Splash screen", capture_splash), + ("Star Wars easter egg", capture_starwars), + ] + + failed = [] + for label, fn in captures: + print(f"── {label} ──") + try: + await fn() + except Exception as e: + print(f" FAIL {e}") + failed.append(label) + print() + + count = len(list(OUTPUT_DIR.glob("*.svg"))) + print(f"Done. {count} SVG screenshots generated.") + + if failed: + print(f"\nFailed: {', '.join(failed)}") + sys.exit(1) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tui/scripts/prebake_splash.py b/tui/scripts/prebake_splash.py index 1217194..bdf56be 100644 --- a/tui/scripts/prebake_splash.py +++ b/tui/scripts/prebake_splash.py @@ -1,124 +1,124 @@ -#!/usr/bin/env python3 -"""Pre-render splash art as ANSI half-block text for instant display. - -Converts source images (PNG/JPG/GIF) into .ans files using Unicode half-block -characters with 24-bit ANSI color. This eliminates the runtime Pillow decode -and textual-image protocol detection, making splash display instant. - -Each terminal cell renders two vertical pixels using the upper-half-block -character (U+2580 '▀') with fg=top_pixel, bg=bottom_pixel for 2x vertical -resolution. - -Usage: - python scripts/prebake_splash.py [--width 80] -""" - -import argparse -from pathlib import Path - -from PIL import Image - -ASSETS_DIR = Path(__file__).resolve().parent.parent / "src" / "skywalker_tui" / "assets" / "splash" - -# Source images to pre-bake -SOURCE_FILES = [ - "seti-satellite.png", - "dialtone.jpg", - "so-far-away.gif", - "prodigy-out-of-space.jpg", - "space-docker.gif", -] - - -def image_to_ansi(img_path: Path, width: int = 80) -> str: - """Convert an image to ANSI half-block art. - - Uses ▀ (upper half block) with fg=top pixel, bg=bottom pixel to get - 2x vertical resolution. Emits 24-bit ANSI color escape sequences, - optimized to only change fg/bg when the color actually changes. - """ - img = Image.open(img_path) - - # Use first frame for animated GIFs - if hasattr(img, "n_frames") and img.n_frames > 1: - img.seek(0) - - img = img.convert("RGBA") - - # Resize maintaining aspect ratio, height rounded to even for half-blocks - ratio = img.height / img.width - pixel_height = int(width * ratio) - pixel_height += pixel_height % 2 # ensure even - img = img.resize((width, pixel_height), Image.LANCZOS) - - lines = [] - for y in range(0, pixel_height, 2): - chunks = [] - prev_fg = None - prev_bg = None - - for x in range(width): - tr, tg, tb, ta = img.getpixel((x, y)) - if y + 1 < pixel_height: - br, bg, bb, ba = img.getpixel((x, y + 1)) - else: - br, bg, bb, ba = 0, 0, 0, 0 - - # Treat near-transparent pixels as black - if ta < 128: - tr, tg, tb = 0, 0, 0 - if ba < 128: - br, bg, bb = 0, 0, 0 - - fg = (tr, tg, tb) - bk = (br, bg, bb) - - # Only emit escape codes when color changes - codes = [] - if fg != prev_fg: - codes.append(f"\033[38;2;{fg[0]};{fg[1]};{fg[2]}m") - prev_fg = fg - if bk != prev_bg: - codes.append(f"\033[48;2;{bk[0]};{bk[1]};{bk[2]}m") - prev_bg = bk - - chunks.append("".join(codes) + "\u2580") - - lines.append("".join(chunks) + "\033[0m") - - return "\n".join(lines) - - -def main(): - parser = argparse.ArgumentParser(description="Pre-bake splash art as ANSI half-block text") - parser.add_argument("--width", type=int, default=80, help="Output width in columns (default: 80)") - args = parser.parse_args() - - if not ASSETS_DIR.exists(): - print(f"Assets directory not found: {ASSETS_DIR}") - return - - for filename in SOURCE_FILES: - src = ASSETS_DIR / filename - if not src.exists(): - print(f" SKIP {filename} (not found)") - continue - - stem = src.stem - dst = ASSETS_DIR / f"{stem}.ans" - - print(f" BAKE {filename} -> {stem}.ans ({args.width} cols) ...", end=" ", flush=True) - ansi_text = image_to_ansi(src, width=args.width) - dst.write_text(ansi_text) - - # Stats - src_kb = src.stat().st_size / 1024 - dst_kb = dst.stat().st_size / 1024 - line_count = ansi_text.count("\n") + 1 - print(f"{src_kb:.1f}KB -> {dst_kb:.1f}KB ({line_count} lines)") - - print("\nDone. Pre-baked .ans files are ready for instant splash display.") - - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +"""Pre-render splash art as ANSI half-block text for instant display. + +Converts source images (PNG/JPG/GIF) into .ans files using Unicode half-block +characters with 24-bit ANSI color. This eliminates the runtime Pillow decode +and textual-image protocol detection, making splash display instant. + +Each terminal cell renders two vertical pixels using the upper-half-block +character (U+2580 '▀') with fg=top_pixel, bg=bottom_pixel for 2x vertical +resolution. + +Usage: + python scripts/prebake_splash.py [--width 80] +""" + +import argparse +from pathlib import Path + +from PIL import Image + +ASSETS_DIR = Path(__file__).resolve().parent.parent / "src" / "skywalker_tui" / "assets" / "splash" + +# Source images to pre-bake +SOURCE_FILES = [ + "seti-satellite.png", + "dialtone.jpg", + "so-far-away.gif", + "prodigy-out-of-space.jpg", + "space-docker.gif", +] + + +def image_to_ansi(img_path: Path, width: int = 80) -> str: + """Convert an image to ANSI half-block art. + + Uses ▀ (upper half block) with fg=top pixel, bg=bottom pixel to get + 2x vertical resolution. Emits 24-bit ANSI color escape sequences, + optimized to only change fg/bg when the color actually changes. + """ + img = Image.open(img_path) + + # Use first frame for animated GIFs + if hasattr(img, "n_frames") and img.n_frames > 1: + img.seek(0) + + img = img.convert("RGBA") + + # Resize maintaining aspect ratio, height rounded to even for half-blocks + ratio = img.height / img.width + pixel_height = int(width * ratio) + pixel_height += pixel_height % 2 # ensure even + img = img.resize((width, pixel_height), Image.LANCZOS) + + lines = [] + for y in range(0, pixel_height, 2): + chunks = [] + prev_fg = None + prev_bg = None + + for x in range(width): + tr, tg, tb, ta = img.getpixel((x, y)) + if y + 1 < pixel_height: + br, bg, bb, ba = img.getpixel((x, y + 1)) + else: + br, bg, bb, ba = 0, 0, 0, 0 + + # Treat near-transparent pixels as black + if ta < 128: + tr, tg, tb = 0, 0, 0 + if ba < 128: + br, bg, bb = 0, 0, 0 + + fg = (tr, tg, tb) + bk = (br, bg, bb) + + # Only emit escape codes when color changes + codes = [] + if fg != prev_fg: + codes.append(f"\033[38;2;{fg[0]};{fg[1]};{fg[2]}m") + prev_fg = fg + if bk != prev_bg: + codes.append(f"\033[48;2;{bk[0]};{bk[1]};{bk[2]}m") + prev_bg = bk + + chunks.append("".join(codes) + "\u2580") + + lines.append("".join(chunks) + "\033[0m") + + return "\n".join(lines) + + +def main(): + parser = argparse.ArgumentParser(description="Pre-bake splash art as ANSI half-block text") + parser.add_argument("--width", type=int, default=80, help="Output width in columns (default: 80)") + args = parser.parse_args() + + if not ASSETS_DIR.exists(): + print(f"Assets directory not found: {ASSETS_DIR}") + return + + for filename in SOURCE_FILES: + src = ASSETS_DIR / filename + if not src.exists(): + print(f" SKIP {filename} (not found)") + continue + + stem = src.stem + dst = ASSETS_DIR / f"{stem}.ans" + + print(f" BAKE {filename} -> {stem}.ans ({args.width} cols) ...", end=" ", flush=True) + ansi_text = image_to_ansi(src, width=args.width) + dst.write_text(ansi_text) + + # Stats + src_kb = src.stat().st_size / 1024 + dst_kb = dst.stat().st_size / 1024 + line_count = ansi_text.count("\n") + 1 + print(f"{src_kb:.1f}KB -> {dst_kb:.1f}KB ({line_count} lines)") + + print("\nDone. Pre-baked .ans files are ready for instant splash display.") + + +if __name__ == "__main__": + main() diff --git a/tui/src/skywalker_tui/__init__.py b/tui/src/skywalker_tui/__init__.py index 11f52c0..0953a24 100644 --- a/tui/src/skywalker_tui/__init__.py +++ b/tui/src/skywalker_tui/__init__.py @@ -1,12 +1,12 @@ -"""Textual TUI for Genpix SkyWalker-1 DVB-S receiver.""" - -import sys -from pathlib import Path - -__version__ = "0.1.0" - -# Ensure skywalker_lib (in sibling tools/ directory) is importable. -# Consolidated here so app.py and status_bar.py don't duplicate this. -_tools_dir = Path(__file__).resolve().parent.parent.parent.parent / "tools" -if _tools_dir.is_dir() and str(_tools_dir) not in sys.path: - sys.path.insert(0, str(_tools_dir)) +"""Textual TUI for Genpix SkyWalker-1 DVB-S receiver.""" + +import sys +from pathlib import Path + +__version__ = "0.1.0" + +# Ensure skywalker_lib (in sibling tools/ directory) is importable. +# Consolidated here so app.py and status_bar.py don't duplicate this. +_tools_dir = Path(__file__).resolve().parent.parent.parent.parent / "tools" +if _tools_dir.is_dir() and str(_tools_dir) not in sys.path: + sys.path.insert(0, str(_tools_dir)) diff --git a/tui/src/skywalker_tui/app.py b/tui/src/skywalker_tui/app.py index 83aa5eb..203ec1f 100644 --- a/tui/src/skywalker_tui/app.py +++ b/tui/src/skywalker_tui/app.py @@ -1,222 +1,222 @@ -"""SkyWalker-1 TUI — main application. - -Provides mode switching between 8 operating modes via a sidebar and F-key -shortcuts. Each mode is a Container subclass that manages its own workers. - -Note: We use "rf_mode" terminology for our operating modes to avoid colliding -with Textual's built-in App.mode / _current_mode / _screen_stacks system. -""" - -import argparse -import sys - -from textual.app import App, ComposeResult -from textual.binding import Binding -from textual.containers import Horizontal, Vertical -from textual.widgets import Header, Footer, Button, Label, Static, ContentSwitcher - -from skywalker_tui.bridge import USBBridge -from skywalker_tui.demo import DemoDevice -from skywalker_tui.widgets.status_bar import DeviceStatusBar - -from skywalker_tui.screens.spectrum import SpectrumScreen -from skywalker_tui.screens.scan import ScanScreen -from skywalker_tui.screens.monitor import MonitorScreen -from skywalker_tui.screens.lband import LBandScreen -from skywalker_tui.screens.track import TrackScreen -from skywalker_tui.screens.device import DeviceScreen -from skywalker_tui.screens.stream import StreamScreen -from skywalker_tui.screens.config import ConfigScreen -from skywalker_tui.screens.motor import MotorScreen -from skywalker_tui.screens.survey import SurveyScreen - - -MODES = { - "spectrum": ("F1 Spectrum", SpectrumScreen), - "scan": ("F2 Scan", ScanScreen), - "monitor": ("F3 Monitor", MonitorScreen), - "lband": ("F4 L-Band", LBandScreen), - "track": ("F5 Track", TrackScreen), - "device": ("F6 Device", DeviceScreen), - "stream": ("F7 Stream", StreamScreen), - "config": ("F8 Config", ConfigScreen), - "motor": ("F9 Motor", MotorScreen), - "survey": ("F10 Survey", SurveyScreen), -} - - -class SkyWalkerApp(App): - """Textual TUI for Genpix SkyWalker-1 DVB-S receiver.""" - - TITLE = "SkyWalker-1" - SUB_TITLE = "DVB-S RF Tool" - CSS_PATH = "theme.tcss" - - BINDINGS = [ - Binding("f1", "rf_mode('spectrum')", "Spectrum", show=True), - Binding("f2", "rf_mode('scan')", "Scan", show=True), - Binding("f3", "rf_mode('monitor')", "Monitor", show=True), - Binding("f4", "rf_mode('lband')", "L-Band", show=True), - Binding("f5", "rf_mode('track')", "Track", show=True), - Binding("f6", "rf_mode('device')", "Device", show=True), - Binding("f7", "rf_mode('stream')", "Stream", show=True), - Binding("f8", "rf_mode('config')", "Config", show=True), - Binding("f9", "rf_mode('motor')", "Motor", show=True), - Binding("f10", "rf_mode('survey')", "Survey", show=True), - Binding("q", "quit", "Quit", show=True), - Binding("d", "toggle_dark", "Theme", show=True), - Binding("ctrl+w", "starwars", "Star Wars", show=False), - ] - - def __init__(self, bridge: USBBridge, initial_mode: str = "spectrum", - show_splash: bool = True): - super().__init__() - self._bridge = bridge - self._initial_rf_mode = initial_mode - self._active_rf_mode = initial_mode - self._rf_screens: dict[str, object] = {} - self._show_splash = show_splash - - def compose(self) -> ComposeResult: - yield Header() - with Horizontal(): - with Vertical(id="sidebar"): - yield Label("[bold #00d4aa]SkyWalker-1[/]", classes="sidebar-heading") - yield Label("[#506878]DVB-S RF Tool[/]", classes="sidebar-heading") - yield Static("") - for mode_key, (label, _cls) in MODES.items(): - yield Button(label, id=f"btn-{mode_key}", classes="mode-button") - yield Static("") - yield DeviceStatusBar(self._bridge) - yield ContentSwitcher(id="content-area") - yield Footer() - - def on_mount(self) -> None: - # Initialize status bar (lightweight) - status = self.query_one(DeviceStatusBar) - status.update_status(self._bridge) - - if self._show_splash: - # Push splash FIRST, then init mode screens behind it. - # Two-tick chain: tick 1 = splash renders, tick 2 = heavy work. - self.call_later(self._push_splash) - else: - self.call_later(self._init_mode_screens) - - def _push_splash(self) -> None: - """Push splash screen, then defer heavy mode screen init.""" - from skywalker_tui.screens.splash import SplashScreen - try: - self.push_screen(SplashScreen()) - except Exception: - pass - # Mode screens mount behind the splash overlay — pre-baked ANSI art - # renders instantly so no delay needed before heavy work starts - self.call_later(self._init_mode_screens) - - def _init_mode_screens(self) -> None: - """Mount all 8 mode screens into the content switcher.""" - switcher = self.query_one("#content-area", ContentSwitcher) - for mode_key, (_label, cls) in MODES.items(): - screen = cls(self._bridge, id=f"screen-{mode_key}") - self._rf_screens[mode_key] = screen - switcher.mount(screen) - self.action_rf_mode(self._initial_rf_mode) - - def action_rf_mode(self, mode: str) -> None: - """Switch to a different RF operating mode.""" - if mode not in MODES: - return - - self._active_rf_mode = mode - switcher = self.query_one("#content-area", ContentSwitcher) - switcher.current = f"screen-{mode}" - - # Update sidebar button highlights - for mode_key in MODES: - btn = self.query_one(f"#btn-{mode_key}", Button) - btn.remove_class("-active") - self.query_one(f"#btn-{mode}", Button).add_class("-active") - - self.sub_title = f"DVB-S RF Tool \u2014 {MODES[mode][0]}" - - def on_button_pressed(self, event: Button.Pressed) -> None: - """Handle sidebar mode button clicks.""" - btn_id = event.button.id or "" - if btn_id.startswith("btn-"): - mode = btn_id[4:] - if mode in MODES: - self.action_rf_mode(mode) - - def action_toggle_dark(self) -> None: - self.theme = ( - "textual-dark" if self.theme == "textual-light" else "textual-light" - ) - if self.current_theme.dark: - self.notify( - "Welcome to the Dark Side.", - title="The Force is strong with this one", - severity="warning", - timeout=4, - ) - else: - self.notify( - "The Force awakens.", - title="A New Hope", - severity="information", - timeout=4, - ) - - def action_starwars(self) -> None: - """Easter egg: stream ASCII Star Wars from telnet.""" - from skywalker_tui.screens.starwars import StarWarsScreen - self.push_screen(StarWarsScreen()) - - -def main(): - parser = argparse.ArgumentParser( - prog="skywalker-tui", - description="Textual TUI for Genpix SkyWalker-1 DVB-S receiver", - ) - parser.add_argument( - "--demo", action="store_true", - help="Use synthetic signal data (no hardware required)", - ) - parser.add_argument( - "--no-splash", action="store_true", - help="Skip the splash screen on startup", - ) - parser.add_argument( - "mode", nargs="?", default="spectrum", - choices=list(MODES.keys()), - help="Initial mode (default: spectrum)", - ) - parser.add_argument( - "-v", "--verbose", action="store_true", - help="Verbose USB logging (hardware mode only)", - ) - args = parser.parse_args() - - if args.demo: - device = DemoDevice() - bridge = USBBridge(device) - else: - try: - from skywalker_lib import SkyWalker1 - device = SkyWalker1(verbose=args.verbose) - device.open() - bridge = USBBridge(device) - except Exception as e: - print(f"Cannot open SkyWalker-1: {e}", file=sys.stderr) - print("Use --demo for synthetic signal data.", file=sys.stderr) - sys.exit(1) - - app = SkyWalkerApp( - bridge=bridge, - initial_mode=args.mode, - show_splash=not args.no_splash, - ) - try: - app.run() - finally: - bridge.close() +"""SkyWalker-1 TUI — main application. + +Provides mode switching between 8 operating modes via a sidebar and F-key +shortcuts. Each mode is a Container subclass that manages its own workers. + +Note: We use "rf_mode" terminology for our operating modes to avoid colliding +with Textual's built-in App.mode / _current_mode / _screen_stacks system. +""" + +import argparse +import sys + +from textual.app import App, ComposeResult +from textual.binding import Binding +from textual.containers import Horizontal, Vertical +from textual.widgets import Header, Footer, Button, Label, Static, ContentSwitcher + +from skywalker_tui.bridge import USBBridge +from skywalker_tui.demo import DemoDevice +from skywalker_tui.widgets.status_bar import DeviceStatusBar + +from skywalker_tui.screens.spectrum import SpectrumScreen +from skywalker_tui.screens.scan import ScanScreen +from skywalker_tui.screens.monitor import MonitorScreen +from skywalker_tui.screens.lband import LBandScreen +from skywalker_tui.screens.track import TrackScreen +from skywalker_tui.screens.device import DeviceScreen +from skywalker_tui.screens.stream import StreamScreen +from skywalker_tui.screens.config import ConfigScreen +from skywalker_tui.screens.motor import MotorScreen +from skywalker_tui.screens.survey import SurveyScreen + + +MODES = { + "spectrum": ("F1 Spectrum", SpectrumScreen), + "scan": ("F2 Scan", ScanScreen), + "monitor": ("F3 Monitor", MonitorScreen), + "lband": ("F4 L-Band", LBandScreen), + "track": ("F5 Track", TrackScreen), + "device": ("F6 Device", DeviceScreen), + "stream": ("F7 Stream", StreamScreen), + "config": ("F8 Config", ConfigScreen), + "motor": ("F9 Motor", MotorScreen), + "survey": ("F10 Survey", SurveyScreen), +} + + +class SkyWalkerApp(App): + """Textual TUI for Genpix SkyWalker-1 DVB-S receiver.""" + + TITLE = "SkyWalker-1" + SUB_TITLE = "DVB-S RF Tool" + CSS_PATH = "theme.tcss" + + BINDINGS = [ + Binding("f1", "rf_mode('spectrum')", "Spectrum", show=True), + Binding("f2", "rf_mode('scan')", "Scan", show=True), + Binding("f3", "rf_mode('monitor')", "Monitor", show=True), + Binding("f4", "rf_mode('lband')", "L-Band", show=True), + Binding("f5", "rf_mode('track')", "Track", show=True), + Binding("f6", "rf_mode('device')", "Device", show=True), + Binding("f7", "rf_mode('stream')", "Stream", show=True), + Binding("f8", "rf_mode('config')", "Config", show=True), + Binding("f9", "rf_mode('motor')", "Motor", show=True), + Binding("f10", "rf_mode('survey')", "Survey", show=True), + Binding("q", "quit", "Quit", show=True), + Binding("d", "toggle_dark", "Theme", show=True), + Binding("ctrl+w", "starwars", "Star Wars", show=False), + ] + + def __init__(self, bridge: USBBridge, initial_mode: str = "spectrum", + show_splash: bool = True): + super().__init__() + self._bridge = bridge + self._initial_rf_mode = initial_mode + self._active_rf_mode = initial_mode + self._rf_screens: dict[str, object] = {} + self._show_splash = show_splash + + def compose(self) -> ComposeResult: + yield Header() + with Horizontal(): + with Vertical(id="sidebar"): + yield Label("[bold #00d4aa]SkyWalker-1[/]", classes="sidebar-heading") + yield Label("[#506878]DVB-S RF Tool[/]", classes="sidebar-heading") + yield Static("") + for mode_key, (label, _cls) in MODES.items(): + yield Button(label, id=f"btn-{mode_key}", classes="mode-button") + yield Static("") + yield DeviceStatusBar(self._bridge) + yield ContentSwitcher(id="content-area") + yield Footer() + + def on_mount(self) -> None: + # Initialize status bar (lightweight) + status = self.query_one(DeviceStatusBar) + status.update_status(self._bridge) + + if self._show_splash: + # Push splash FIRST, then init mode screens behind it. + # Two-tick chain: tick 1 = splash renders, tick 2 = heavy work. + self.call_later(self._push_splash) + else: + self.call_later(self._init_mode_screens) + + def _push_splash(self) -> None: + """Push splash screen, then defer heavy mode screen init.""" + from skywalker_tui.screens.splash import SplashScreen + try: + self.push_screen(SplashScreen()) + except Exception: + pass + # Mode screens mount behind the splash overlay — pre-baked ANSI art + # renders instantly so no delay needed before heavy work starts + self.call_later(self._init_mode_screens) + + def _init_mode_screens(self) -> None: + """Mount all 8 mode screens into the content switcher.""" + switcher = self.query_one("#content-area", ContentSwitcher) + for mode_key, (_label, cls) in MODES.items(): + screen = cls(self._bridge, id=f"screen-{mode_key}") + self._rf_screens[mode_key] = screen + switcher.mount(screen) + self.action_rf_mode(self._initial_rf_mode) + + def action_rf_mode(self, mode: str) -> None: + """Switch to a different RF operating mode.""" + if mode not in MODES: + return + + self._active_rf_mode = mode + switcher = self.query_one("#content-area", ContentSwitcher) + switcher.current = f"screen-{mode}" + + # Update sidebar button highlights + for mode_key in MODES: + btn = self.query_one(f"#btn-{mode_key}", Button) + btn.remove_class("-active") + self.query_one(f"#btn-{mode}", Button).add_class("-active") + + self.sub_title = f"DVB-S RF Tool \u2014 {MODES[mode][0]}" + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handle sidebar mode button clicks.""" + btn_id = event.button.id or "" + if btn_id.startswith("btn-"): + mode = btn_id[4:] + if mode in MODES: + self.action_rf_mode(mode) + + def action_toggle_dark(self) -> None: + self.theme = ( + "textual-dark" if self.theme == "textual-light" else "textual-light" + ) + if self.current_theme.dark: + self.notify( + "Welcome to the Dark Side.", + title="The Force is strong with this one", + severity="warning", + timeout=4, + ) + else: + self.notify( + "The Force awakens.", + title="A New Hope", + severity="information", + timeout=4, + ) + + def action_starwars(self) -> None: + """Easter egg: stream ASCII Star Wars from telnet.""" + from skywalker_tui.screens.starwars import StarWarsScreen + self.push_screen(StarWarsScreen()) + + +def main(): + parser = argparse.ArgumentParser( + prog="skywalker-tui", + description="Textual TUI for Genpix SkyWalker-1 DVB-S receiver", + ) + parser.add_argument( + "--demo", action="store_true", + help="Use synthetic signal data (no hardware required)", + ) + parser.add_argument( + "--no-splash", action="store_true", + help="Skip the splash screen on startup", + ) + parser.add_argument( + "mode", nargs="?", default="spectrum", + choices=list(MODES.keys()), + help="Initial mode (default: spectrum)", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", + help="Verbose USB logging (hardware mode only)", + ) + args = parser.parse_args() + + if args.demo: + device = DemoDevice() + bridge = USBBridge(device) + else: + try: + from skywalker_lib import SkyWalker1 + device = SkyWalker1(verbose=args.verbose) + device.open() + bridge = USBBridge(device) + except Exception as e: + print(f"Cannot open SkyWalker-1: {e}", file=sys.stderr) + print("Use --demo for synthetic signal data.", file=sys.stderr) + sys.exit(1) + + app = SkyWalkerApp( + bridge=bridge, + initial_mode=args.mode, + show_splash=not args.no_splash, + ) + try: + app.run() + finally: + bridge.close() diff --git a/tui/src/skywalker_tui/bridge.py b/tui/src/skywalker_tui/bridge.py index b4f994d..eed051c 100644 --- a/tui/src/skywalker_tui/bridge.py +++ b/tui/src/skywalker_tui/bridge.py @@ -1,254 +1,254 @@ -"""Thread-safe bridge between Textual async event loop and blocking pyusb calls. - -All SkyWalker1 (or DemoDevice) methods are blocking I/O — they perform USB control -transfers that can take 2-200ms each. Textual's event loop is asyncio-based, so -calling these directly would freeze the UI. - -The USBBridge wraps every device method behind a threading.Lock to prevent concurrent -USB access (the BCM4500 can't handle overlapping control transfers) and exposes them -as plain synchronous methods meant to be called from Textual @work(thread=True) workers. -""" - -import threading - - -class USBBridge: - """Thread-safe wrapper around SkyWalker1 or DemoDevice.""" - - def __init__(self, device): - self._dev = device - self._lock = threading.RLock() - - @property - def is_demo(self) -> bool: - return hasattr(self._dev, "_demo") - - def open(self): - with self._lock: - if hasattr(self._dev, "open"): - self._dev.open() - - def close(self): - with self._lock: - if hasattr(self._dev, "close"): - self._dev.close() - - def get_fw_version(self) -> dict: - with self._lock: - return self._dev.get_fw_version() - - def get_config(self) -> int: - with self._lock: - return self._dev.get_config() - - def ensure_booted(self): - with self._lock: - self._dev.ensure_booted() - - def signal_monitor(self) -> dict: - with self._lock: - return self._dev.signal_monitor() - - def sweep_spectrum(self, start_mhz: float, stop_mhz: float, - step_mhz: float = 5.0, dwell_ms: int = 10, - sr_ksps: int = 20000, mod_index: int = 0, - fec_index: int = 5, callback=None) -> tuple: - with self._lock: - return self._dev.sweep_spectrum( - start_mhz, stop_mhz, step_mhz, dwell_ms, - sr_ksps, mod_index, fec_index, callback, - ) - - def tune_monitor(self, symbol_rate_sps: int, freq_khz: int, - mod_index: int, fec_index: int, - dwell_ms: int = 10) -> dict: - with self._lock: - return self._dev.tune_monitor( - symbol_rate_sps, freq_khz, mod_index, fec_index, dwell_ms, - ) - - def tune(self, symbol_rate_sps: int, freq_khz: int, - mod_index: int, fec_index: int): - with self._lock: - self._dev.tune(symbol_rate_sps, freq_khz, mod_index, fec_index) - - def set_lnb_voltage(self, high: bool): - with self._lock: - self._dev.set_lnb_voltage(high) - - def set_22khz_tone(self, on: bool): - with self._lock: - self._dev.set_22khz_tone(on) - - def configure_lnb(self, pol=None, band=None, lnb_lo=None, - disable_lnb=False) -> float: - with self._lock: - return self._dev.configure_lnb(pol, band, lnb_lo, disable_lnb) - - def blind_scan(self, freq_khz: int, sr_min: int, sr_max: int, - sr_step: int) -> dict | None: - """Run blind scan at a single frequency. Returns result dict or None.""" - with self._lock: - if hasattr(self._dev, "blind_scan"): - return self._dev.blind_scan(freq_khz, sr_min, sr_max, sr_step) - return None - - # -- Device info (extended) -- - - def get_serial_number(self) -> bytes: - with self._lock: - return self._dev.get_serial_number() - - def get_usb_speed(self) -> int: - with self._lock: - return self._dev.get_usb_speed() - - def get_vendor_string(self) -> str: - with self._lock: - return self._dev.get_vendor_string() - - def get_product_string(self) -> str: - with self._lock: - return self._dev.get_product_string() - - # -- FX2 RAM -- - - def fx2_ram_read(self, addr: int, length: int) -> bytes: - with self._lock: - return self._dev.fx2_ram_read(addr, length) - - def fx2_ram_write(self, addr: int, data: bytes) -> int: - with self._lock: - return self._dev.fx2_ram_write(addr, data) - - def fx2_cpu_halt(self) -> None: - with self._lock: - self._dev.fx2_cpu_halt() - - def fx2_cpu_start(self) -> None: - with self._lock: - self._dev.fx2_cpu_start() - - # -- EEPROM -- - - def eeprom_read(self, offset: int, length: int = 64) -> bytes: - with self._lock: - return self._dev.eeprom_read(offset, length) - - def eeprom_write_page(self, offset: int, data: bytes) -> int: - with self._lock: - return self._dev.eeprom_write_page(offset, data) - - def eeprom_read_all(self, size: int = 16384) -> bytes: - with self._lock: - return self._dev.eeprom_read_all(size) - - # -- Diagnostics -- - - def boot_debug(self, mode: int) -> dict: - with self._lock: - return self._dev.boot_debug(mode) - - def i2c_bus_scan(self) -> list[int]: - with self._lock: - return self._dev.i2c_bus_scan() - - def i2c_raw_read(self, slave: int, reg: int) -> int: - with self._lock: - return self._dev.i2c_raw_read(slave, reg) - - # -- Streaming -- - - def arm_transfer(self, on: bool) -> None: - with self._lock: - self._dev.arm_transfer(on) - - def read_stream(self, size: int = 8192, timeout: int = 1000) -> bytes: - with self._lock: - return self._dev.read_stream(size, timeout) - - # -- Config -- - - def send_diseqc_message(self, msg: bytes) -> None: - with self._lock: - self._dev.send_diseqc_message(msg) - - def send_diseqc_tone_burst(self, mini_cmd: int) -> None: - with self._lock: - self._dev.send_diseqc_tone_burst(mini_cmd) - - def start_intersil(self, on: bool = True) -> int: - with self._lock: - return self._dev.start_intersil(on) - - def set_extra_voltage(self, on: bool) -> None: - with self._lock: - self._dev.set_extra_voltage(on) - - def boot(self, on: bool = True) -> int: - with self._lock: - return self._dev.boot(on) - - def get_signal_lock(self) -> bool: - with self._lock: - return self._dev.get_signal_lock() - - def get_signal_strength(self) -> dict: - with self._lock: - return self._dev.get_signal_strength() - - def multi_reg_read(self, start_reg: int, count: int) -> bytes: - with self._lock: - return self._dev.multi_reg_read(start_reg, count) - - # -- Motor control (v3.03+) -- - - def motor_halt(self) -> None: - with self._lock: - self._dev.motor_halt() - - def motor_drive_east(self, steps: int = 0) -> None: - with self._lock: - self._dev.motor_drive_east(steps) - - def motor_drive_west(self, steps: int = 0) -> None: - with self._lock: - self._dev.motor_drive_west(steps) - - def motor_store_position(self, slot: int) -> None: - with self._lock: - self._dev.motor_store_position(slot) - - def motor_goto_position(self, slot: int) -> None: - with self._lock: - self._dev.motor_goto_position(slot) - - def motor_goto_x(self, observer_lon: float, sat_lon: float) -> None: - with self._lock: - self._dev.motor_goto_x(observer_lon, sat_lon) - - def motor_set_limit(self, direction: str) -> None: - with self._lock: - self._dev.motor_set_limit(direction) - - def motor_disable_limits(self) -> None: - with self._lock: - self._dev.motor_disable_limits() - - def get_last_error(self) -> int: - with self._lock: - return self._dev.get_last_error() - - def get_last_error_str(self) -> str: - with self._lock: - return self._dev.get_last_error_str() - - def get_stream_diag(self, reset: bool = False) -> dict: - with self._lock: - return self._dev.get_stream_diag(reset=reset) - - def get_hotplug_status(self, reset: bool = False, - force_scan: bool = False) -> dict: - with self._lock: - return self._dev.get_hotplug_status(reset=reset, - force_scan=force_scan) +"""Thread-safe bridge between Textual async event loop and blocking pyusb calls. + +All SkyWalker1 (or DemoDevice) methods are blocking I/O — they perform USB control +transfers that can take 2-200ms each. Textual's event loop is asyncio-based, so +calling these directly would freeze the UI. + +The USBBridge wraps every device method behind a threading.Lock to prevent concurrent +USB access (the BCM4500 can't handle overlapping control transfers) and exposes them +as plain synchronous methods meant to be called from Textual @work(thread=True) workers. +""" + +import threading + + +class USBBridge: + """Thread-safe wrapper around SkyWalker1 or DemoDevice.""" + + def __init__(self, device): + self._dev = device + self._lock = threading.RLock() + + @property + def is_demo(self) -> bool: + return hasattr(self._dev, "_demo") + + def open(self): + with self._lock: + if hasattr(self._dev, "open"): + self._dev.open() + + def close(self): + with self._lock: + if hasattr(self._dev, "close"): + self._dev.close() + + def get_fw_version(self) -> dict: + with self._lock: + return self._dev.get_fw_version() + + def get_config(self) -> int: + with self._lock: + return self._dev.get_config() + + def ensure_booted(self): + with self._lock: + self._dev.ensure_booted() + + def signal_monitor(self) -> dict: + with self._lock: + return self._dev.signal_monitor() + + def sweep_spectrum(self, start_mhz: float, stop_mhz: float, + step_mhz: float = 5.0, dwell_ms: int = 10, + sr_ksps: int = 20000, mod_index: int = 0, + fec_index: int = 5, callback=None) -> tuple: + with self._lock: + return self._dev.sweep_spectrum( + start_mhz, stop_mhz, step_mhz, dwell_ms, + sr_ksps, mod_index, fec_index, callback, + ) + + def tune_monitor(self, symbol_rate_sps: int, freq_khz: int, + mod_index: int, fec_index: int, + dwell_ms: int = 10) -> dict: + with self._lock: + return self._dev.tune_monitor( + symbol_rate_sps, freq_khz, mod_index, fec_index, dwell_ms, + ) + + def tune(self, symbol_rate_sps: int, freq_khz: int, + mod_index: int, fec_index: int): + with self._lock: + self._dev.tune(symbol_rate_sps, freq_khz, mod_index, fec_index) + + def set_lnb_voltage(self, high: bool): + with self._lock: + self._dev.set_lnb_voltage(high) + + def set_22khz_tone(self, on: bool): + with self._lock: + self._dev.set_22khz_tone(on) + + def configure_lnb(self, pol=None, band=None, lnb_lo=None, + disable_lnb=False) -> float: + with self._lock: + return self._dev.configure_lnb(pol, band, lnb_lo, disable_lnb) + + def blind_scan(self, freq_khz: int, sr_min: int, sr_max: int, + sr_step: int) -> dict | None: + """Run blind scan at a single frequency. Returns result dict or None.""" + with self._lock: + if hasattr(self._dev, "blind_scan"): + return self._dev.blind_scan(freq_khz, sr_min, sr_max, sr_step) + return None + + # -- Device info (extended) -- + + def get_serial_number(self) -> bytes: + with self._lock: + return self._dev.get_serial_number() + + def get_usb_speed(self) -> int: + with self._lock: + return self._dev.get_usb_speed() + + def get_vendor_string(self) -> str: + with self._lock: + return self._dev.get_vendor_string() + + def get_product_string(self) -> str: + with self._lock: + return self._dev.get_product_string() + + # -- FX2 RAM -- + + def fx2_ram_read(self, addr: int, length: int) -> bytes: + with self._lock: + return self._dev.fx2_ram_read(addr, length) + + def fx2_ram_write(self, addr: int, data: bytes) -> int: + with self._lock: + return self._dev.fx2_ram_write(addr, data) + + def fx2_cpu_halt(self) -> None: + with self._lock: + self._dev.fx2_cpu_halt() + + def fx2_cpu_start(self) -> None: + with self._lock: + self._dev.fx2_cpu_start() + + # -- EEPROM -- + + def eeprom_read(self, offset: int, length: int = 64) -> bytes: + with self._lock: + return self._dev.eeprom_read(offset, length) + + def eeprom_write_page(self, offset: int, data: bytes) -> int: + with self._lock: + return self._dev.eeprom_write_page(offset, data) + + def eeprom_read_all(self, size: int = 16384) -> bytes: + with self._lock: + return self._dev.eeprom_read_all(size) + + # -- Diagnostics -- + + def boot_debug(self, mode: int) -> dict: + with self._lock: + return self._dev.boot_debug(mode) + + def i2c_bus_scan(self) -> list[int]: + with self._lock: + return self._dev.i2c_bus_scan() + + def i2c_raw_read(self, slave: int, reg: int) -> int: + with self._lock: + return self._dev.i2c_raw_read(slave, reg) + + # -- Streaming -- + + def arm_transfer(self, on: bool) -> None: + with self._lock: + self._dev.arm_transfer(on) + + def read_stream(self, size: int = 8192, timeout: int = 1000) -> bytes: + with self._lock: + return self._dev.read_stream(size, timeout) + + # -- Config -- + + def send_diseqc_message(self, msg: bytes) -> None: + with self._lock: + self._dev.send_diseqc_message(msg) + + def send_diseqc_tone_burst(self, mini_cmd: int) -> None: + with self._lock: + self._dev.send_diseqc_tone_burst(mini_cmd) + + def start_intersil(self, on: bool = True) -> int: + with self._lock: + return self._dev.start_intersil(on) + + def set_extra_voltage(self, on: bool) -> None: + with self._lock: + self._dev.set_extra_voltage(on) + + def boot(self, on: bool = True) -> int: + with self._lock: + return self._dev.boot(on) + + def get_signal_lock(self) -> bool: + with self._lock: + return self._dev.get_signal_lock() + + def get_signal_strength(self) -> dict: + with self._lock: + return self._dev.get_signal_strength() + + def multi_reg_read(self, start_reg: int, count: int) -> bytes: + with self._lock: + return self._dev.multi_reg_read(start_reg, count) + + # -- Motor control (v3.03+) -- + + def motor_halt(self) -> None: + with self._lock: + self._dev.motor_halt() + + def motor_drive_east(self, steps: int = 0) -> None: + with self._lock: + self._dev.motor_drive_east(steps) + + def motor_drive_west(self, steps: int = 0) -> None: + with self._lock: + self._dev.motor_drive_west(steps) + + def motor_store_position(self, slot: int) -> None: + with self._lock: + self._dev.motor_store_position(slot) + + def motor_goto_position(self, slot: int) -> None: + with self._lock: + self._dev.motor_goto_position(slot) + + def motor_goto_x(self, observer_lon: float, sat_lon: float) -> None: + with self._lock: + self._dev.motor_goto_x(observer_lon, sat_lon) + + def motor_set_limit(self, direction: str) -> None: + with self._lock: + self._dev.motor_set_limit(direction) + + def motor_disable_limits(self) -> None: + with self._lock: + self._dev.motor_disable_limits() + + def get_last_error(self) -> int: + with self._lock: + return self._dev.get_last_error() + + def get_last_error_str(self) -> str: + with self._lock: + return self._dev.get_last_error_str() + + def get_stream_diag(self, reset: bool = False) -> dict: + with self._lock: + return self._dev.get_stream_diag(reset=reset) + + def get_hotplug_status(self, reset: bool = False, + force_scan: bool = False) -> dict: + with self._lock: + return self._dev.get_hotplug_status(reset=reset, + force_scan=force_scan) diff --git a/tui/src/skywalker_tui/demo.py b/tui/src/skywalker_tui/demo.py index c8f835e..7101f05 100644 --- a/tui/src/skywalker_tui/demo.py +++ b/tui/src/skywalker_tui/demo.py @@ -1,664 +1,664 @@ -"""Synthetic signal generator for --demo mode. - -DemoDevice mimics the SkyWalker1 API without any USB hardware. It generates -realistic-looking signal data with: - - Gaussian peaks at known satellite transponder positions - - Thermal noise floor with Gaussian jitter - - AGC values inversely correlated to signal strength - - Occasional lock/unlock transitions based on SNR threshold - - Slow drift in SNR to simulate atmospheric effects - -This enables full TUI development and testing without hardware. -""" - -import math -import random -import struct -import time - - -# Simulated transponder positions (IF MHz) and relative strengths -_TRANSPONDERS = [ - (1050, -12.0, 27500), # strong Ku-band TP - (1220, -18.0, 22000), # moderate TP - (1480, -25.0, 30000), # weak TP - (1750, -15.0, 20000), # moderate TP - (1950, -22.0, 13000), # weaker TP -] - -# QO-100 transponders (IF MHz for 9361 LO: RF 10491-10499 -> IF 1130-1138) -_QO100_TRANSPONDERS = [ - (1130.5, -20.0, 1500), # BATC beacon - (1131.0, -24.0, 1000), # DATV station - (1132.0, -28.0, 500), # low-power DATV - (1133.0, -30.0, 333), # minimum viable DVB-S -] - -_NOISE_FLOOR = -35.0 -_LOCK_THRESHOLD_DB = 3.5 - -# Simulated PIDs for synthetic TS packets -_DEMO_PIDS = [0x0000, 0x0100, 0x0101, 0x0102, 0x1FFF] -_DEMO_PID_WEIGHTS = [1, 5, 40, 20, 34] # rough % distribution - - -def _build_demo_eeprom() -> bytearray: - """Build a valid C2 boot EEPROM image for demo mode.""" - img = bytearray() - # C2 header: magic, VID_L, VID_H, PID_L, PID_H, DID_L, DID_H, CONFIG - img.append(0xC2) - img.extend(struct.pack('HH', code_len, 0x0000)) - img.extend(bytes(range(256)) * 2) # repeating pattern - - # END marker: 0x8001 + entry point 0x0000 - img.extend(struct.pack('>HH', 0x8001, 0x0000)) - - # Pad to 16KB with 0xFF (like a real blank EEPROM region) - img.extend(b'\xFF' * (16384 - len(img))) - return img - - -class DemoDevice: - """Drop-in replacement for SkyWalker1 that generates synthetic data.""" - - _demo = True # marker for bridge.is_demo - - def __init__(self): - self._booted = False - self._lnb_on = False - self._lnb_voltage_high = False - self._tone_22khz = False - self._extra_voltage = False - self._tuned_freq_khz = 0 - self._tuned_sr_sps = 0 - self._armed = False - self._start_time = time.monotonic() - self._sample_count = 0 - self._eeprom = _build_demo_eeprom() - self._cc_counters: dict[int, int] = {pid: 0 for pid in _DEMO_PIDS} - # Motor simulation state - self._motor_position_deg = 0.0 - self._motor_target_deg = 0.0 - self._motor_moving = False - self._motor_direction = 0 # -1=west, 0=stopped, 1=east - self._motor_speed_dps = 1.5 # degrees per second - self._motor_last_update = time.monotonic() - self._motor_stored: dict[int, float] = {} - self._motor_east_limit = 75.0 - self._motor_west_limit = -75.0 - self._last_error = 0x00 - - def open(self): - pass - - def close(self): - pass - - def __enter__(self): - return self - - def __exit__(self, *exc): - pass - - def get_fw_version(self) -> dict: - return { - "major": 3, - "minor": 3, - "patch": 0, - "version": "3.03.0", - "date": "2026-02-15", - } - - def get_config(self) -> int: - bits = 0 - if self._booted: - bits |= 0x01 # 8PSK started - bits |= 0x02 # FW loaded - if self._lnb_on: - bits |= 0x04 # LNB power - if self._lnb_voltage_high: - bits |= 0x20 # 18V - if self._tone_22khz: - bits |= 0x10 # 22 kHz - if self._tuned_freq_khz > 0: - bits |= 0x40 # tuned - return bits - - def ensure_booted(self): - self._booted = True - self._lnb_on = True - - def set_lnb_voltage(self, high: bool): - self._lnb_voltage_high = high - - def set_22khz_tone(self, on: bool): - self._tone_22khz = on - - def configure_lnb(self, pol=None, band=None, lnb_lo=None, - disable_lnb=False) -> float: - if disable_lnb: - self._lnb_on = False - return 0.0 - if pol: - self._lnb_voltage_high = pol.upper() in ("H", "L") - if band: - self._tone_22khz = band == "high" - if lnb_lo is not None: - return lnb_lo - elif band == "high": - return 10600.0 - else: - return 9750.0 - - def start_intersil(self, on: bool = True): - self._lnb_on = on - - def set_extra_voltage(self, on: bool): - self._extra_voltage = on - - def tune(self, symbol_rate_sps: int, freq_khz: int, - mod_index: int, fec_index: int): - self._tuned_freq_khz = freq_khz - self._tuned_sr_sps = symbol_rate_sps - - def signal_monitor(self) -> dict: - """Generate synthetic signal data at the currently tuned frequency.""" - self._sample_count += 1 - elapsed = time.monotonic() - self._start_time - - freq_mhz = self._tuned_freq_khz / 1000.0 if self._tuned_freq_khz else 1200.0 - power_db = self._power_at(freq_mhz, elapsed) - snr_db = max(0.0, power_db - _NOISE_FLOOR + random.gauss(0, 0.3)) - locked = snr_db > _LOCK_THRESHOLD_DB - - snr_raw = int(snr_db * 256) - agc1, agc2 = self._power_to_agc(power_db) - - return { - "snr_raw": snr_raw, - "snr_db": snr_db, - "snr_pct": min(100.0, snr_raw * 17 / 65535 * 100), - "agc1": agc1, - "agc2": agc2, - "power_db": power_db, - "lock": 0x20 if locked else 0x00, - "locked": locked, - "status": 0x01 if self._booted else 0x00, - } - - def tune_monitor(self, symbol_rate_sps: int, freq_khz: int, - mod_index: int, fec_index: int, - dwell_ms: int = 10) -> dict: - """Simulate a tune + measure at a single frequency.""" - # Simulate dwell time (scaled down for responsiveness) - time.sleep(min(dwell_ms, 5) / 1000.0) - - freq_mhz = freq_khz / 1000.0 - elapsed = time.monotonic() - self._start_time - power_db = self._power_at(freq_mhz, elapsed) - snr_db = max(0.0, power_db - _NOISE_FLOOR + random.gauss(0, 0.2)) - locked = snr_db > _LOCK_THRESHOLD_DB - - snr_raw = int(snr_db * 256) - agc1, agc2 = self._power_to_agc(power_db) - - return { - "snr_raw": snr_raw, - "snr_db": snr_db, - "agc1": agc1, - "agc2": agc2, - "power_db": power_db, - "lock": 0x20 if locked else 0x00, - "locked": locked, - "status": 0x01, - "dwell_ms": dwell_ms, - } - - def sweep_spectrum(self, start_mhz: float, stop_mhz: float, - step_mhz: float = 5.0, dwell_ms: int = 10, - sr_ksps: int = 20000, mod_index: int = 0, - fec_index: int = 5, callback=None) -> tuple: - """Sweep with synthetic data. Same return signature as SkyWalker1.""" - sr_sps = sr_ksps * 1000 - freqs = [] - powers = [] - results = [] - - freq = start_mhz - steps = max(1, int((stop_mhz - start_mhz) / step_mhz) + 1) - step_num = 0 - - while freq <= stop_mhz: - freq_khz = int(freq * 1000) - result = self.tune_monitor(sr_sps, freq_khz, mod_index, fec_index, dwell_ms) - freqs.append(freq) - powers.append(result["power_db"]) - results.append(result) - - if callback: - callback(freq, step_num, steps, result) - - step_num += 1 - freq += step_mhz - - return freqs, powers, results - - def blind_scan(self, freq_khz: int, sr_min: int, sr_max: int, - sr_step: int) -> dict | None: - """Simulate blind scan — lock onto nearby transponders.""" - freq_mhz = freq_khz / 1000.0 - for tp_freq, tp_power, tp_sr in _TRANSPONDERS: - if abs(freq_mhz - tp_freq) < 15: - sr_sps = tp_sr * 1000 - if sr_min <= sr_sps <= sr_max: - return { - "freq_khz": int(tp_freq * 1000), - "sr_sps": sr_sps, - "locked": True, - } - return None - - # --- Device info (extended) --- - - def get_serial_number(self) -> bytes: - time.sleep(0.005) - return b'DEMO0001' - - def get_usb_speed(self) -> int: - time.sleep(0.002) - return 2 # High speed - - def get_vendor_string(self) -> str: - time.sleep(0.005) - return "Genpix Electronics" - - def get_product_string(self) -> str: - time.sleep(0.005) - return "SkyWalker-1 DVB-S" - - # --- FX2 RAM access --- - - def fx2_ram_read(self, addr: int, length: int) -> bytes: - time.sleep(0.003) - # Return pseudo-random but deterministic bytes based on address - rng = random.Random(addr) - return bytes(rng.randint(0, 255) for _ in range(length)) - - def fx2_ram_write(self, addr: int, data: bytes) -> int: - time.sleep(0.003) - return len(data) - - def fx2_cpu_halt(self) -> None: - time.sleep(0.005) - - def fx2_cpu_start(self) -> None: - time.sleep(0.005) - - # --- EEPROM access --- - - def eeprom_read(self, offset: int, length: int = 64) -> bytes: - time.sleep(0.005) - end = min(offset + length, len(self._eeprom)) - return bytes(self._eeprom[offset:end]) - - def eeprom_write_page(self, offset: int, data: bytes) -> int: - time.sleep(0.012) # simulated write cycle - for i, b in enumerate(data): - if offset + i < len(self._eeprom): - self._eeprom[offset + i] = b - return len(data) - - def eeprom_read_all(self, size: int = 16384) -> bytes: - chunk_size = 64 - result = bytearray() - for offset in range(0, size, chunk_size): - remaining = min(chunk_size, size - offset) - result.extend(self.eeprom_read(offset, remaining)) - return bytes(result) - - # --- Diagnostics --- - - _BOOT_STAGES = { - 0x80: {"stage": 0x00, "result": 0x00, "detail": 0x00}, # no-op - 0x81: {"stage": 0x01, "result": 0x01, "detail": 0x00}, # GPIO OK - 0x82: {"stage": 0x02, "result": 0x01, "detail": 0x02}, # I2C probe OK - 0x83: {"stage": 0x03, "result": 0x01, "detail": 0x00}, # BCM4500 reset OK - 0x84: {"stage": 0x04, "result": 0x01, "detail": 0x00}, # FW load OK - 0x85: {"stage": 0x05, "result": 0x01, "detail": 0x00}, # full boot OK - } - - def boot_debug(self, mode: int) -> dict: - time.sleep(0.05) - return dict(self._BOOT_STAGES.get(mode, { - "stage": mode & 0x0F, "result": 0x00, "detail": 0xFF, - })) - - def i2c_bus_scan(self) -> list[int]: - time.sleep(0.1) - return [0x08, 0x51] # BCM4500 at 0x08, EEPROM at 0x51 - - def i2c_raw_read(self, slave: int, reg: int) -> int: - time.sleep(0.01) - return random.randint(0, 255) - - # --- Streaming --- - - def arm_transfer(self, on: bool) -> None: - self._armed = on - time.sleep(0.01) - - def read_stream(self, size: int = 8192, timeout: int = 1000) -> bytes: - """Generate synthetic TS packets with proper structure.""" - if not self._armed: - return b'' - time.sleep(0.02) - - packets = bytearray() - num_packets = size // 188 - - for _ in range(num_packets): - # Pick a PID based on weights - pid = random.choices(_DEMO_PIDS, weights=_DEMO_PID_WEIGHTS, k=1)[0] - cc = self._cc_counters[pid] - self._cc_counters[pid] = (cc + 1) & 0x0F - - # Occasionally inject a CC error (~0.1%) - if random.random() < 0.001 and pid != 0x1FFF: - self._cc_counters[pid] = (self._cc_counters[pid] + 1) & 0x0F - - # Build 188-byte TS packet - pkt = bytearray(188) - pkt[0] = 0x47 # sync byte - pkt[1] = (pid >> 8) & 0x1F - pkt[2] = pid & 0xFF - pkt[3] = 0x10 | (cc & 0x0F) # payload only + CC - - if pid == 0x0000: - # PAT packet with PUSI - pkt[1] |= 0x40 # PUSI - pkt[4] = 0x00 # pointer field - # PAT section: table_id=0x00, one program - pat = bytearray([ - 0x00, # table_id - 0xB0, 0x0D, # section_syntax + length=13 - 0x00, 0x01, # transport_stream_id - 0xC1, # version=0, current - 0x00, 0x00, # section_number, last_section - 0x00, 0x01, # program_number=1 - 0xE1, 0x00, # PMT PID=0x0100 - # CRC32 placeholder - 0x00, 0x00, 0x00, 0x00, - ]) - pkt[5:5 + len(pat)] = pat - - elif pid == 0x0100: - # PMT packet with PUSI - pkt[1] |= 0x40 - pkt[4] = 0x00 - pmt = bytearray([ - 0x02, # table_id - 0xB0, 0x17, # section_syntax + length=23 - 0x00, 0x01, # program_number=1 - 0xC1, # version=0 - 0x00, 0x00, # section_number - 0xE1, 0x01, # PCR PID=0x0101 - 0xF0, 0x00, # program info length=0 - # Stream 1: MPEG-2 Video on PID 0x0101 - 0x02, 0xE1, 0x01, 0xF0, 0x00, - # Stream 2: MPEG-1 Audio on PID 0x0102 - 0x03, 0xE1, 0x02, 0xF0, 0x00, - # CRC32 placeholder - 0x00, 0x00, 0x00, 0x00, - ]) - pkt[5:5 + len(pmt)] = pmt - - else: - # Fill payload with pseudo-random data - for i in range(4, 188): - pkt[i] = random.randint(0, 255) - pkt[3] = 0x10 | (cc & 0x0F) - - packets.extend(pkt) - - return bytes(packets) - - # --- DiSEqC --- - - def send_diseqc_tone_burst(self, mini_cmd: int) -> None: - time.sleep(0.05) - - def send_diseqc_message(self, msg: bytes) -> None: - time.sleep(0.05) - - # --- Motor simulation --- - - def _motor_tick(self): - """Update simulated motor position based on elapsed time.""" - now = time.monotonic() - dt = now - self._motor_last_update - self._motor_last_update = now - - if self._motor_direction != 0: - delta = self._motor_speed_dps * dt * self._motor_direction - self._motor_position_deg += delta - # Clamp to limits - self._motor_position_deg = max( - self._motor_west_limit, - min(self._motor_east_limit, self._motor_position_deg) - ) - # Stop at limits - if (self._motor_position_deg >= self._motor_east_limit or - self._motor_position_deg <= self._motor_west_limit): - self._motor_direction = 0 - self._motor_moving = False - - # Slew to target (for goto commands) - if self._motor_moving and self._motor_direction == 0: - diff = self._motor_target_deg - self._motor_position_deg - if abs(diff) < 0.1: - self._motor_position_deg = self._motor_target_deg - self._motor_moving = False - else: - step = min(abs(diff), self._motor_speed_dps * dt) - self._motor_position_deg += step if diff > 0 else -step - - def motor_halt(self) -> None: - self._motor_tick() - self._motor_direction = 0 - self._motor_moving = False - time.sleep(0.02) - - def motor_drive_east(self, steps: int = 0) -> None: - self._motor_tick() - if steps > 0: - self._motor_target_deg = self._motor_position_deg + steps * 0.1 - self._motor_target_deg = min(self._motor_target_deg, self._motor_east_limit) - self._motor_moving = True - self._motor_direction = 0 - else: - self._motor_direction = 1 - self._motor_moving = True - time.sleep(0.02) - - def motor_drive_west(self, steps: int = 0) -> None: - self._motor_tick() - if steps > 0: - self._motor_target_deg = self._motor_position_deg - steps * 0.1 - self._motor_target_deg = max(self._motor_target_deg, self._motor_west_limit) - self._motor_moving = True - self._motor_direction = 0 - else: - self._motor_direction = -1 - self._motor_moving = True - time.sleep(0.02) - - def motor_store_position(self, slot: int) -> None: - self._motor_tick() - self._motor_stored[slot] = self._motor_position_deg - time.sleep(0.02) - - def motor_goto_position(self, slot: int) -> None: - self._motor_tick() - if slot == 0: - self._motor_target_deg = 0.0 - elif slot in self._motor_stored: - self._motor_target_deg = self._motor_stored[slot] - else: - return - self._motor_moving = True - self._motor_direction = 0 - time.sleep(0.02) - - def motor_goto_x(self, observer_lon: float, sat_lon: float) -> None: - # Simplified USALS angle calculation - angle = math.degrees(math.atan2( - math.sin(math.radians(sat_lon - observer_lon)), - math.cos(math.radians(sat_lon - observer_lon)) - 6378.0 / (6378.0 + 35786.0) - )) - self._motor_tick() - self._motor_target_deg = angle - self._motor_moving = True - self._motor_direction = 0 - time.sleep(0.02) - - def motor_set_limit(self, direction: str) -> None: - self._motor_tick() - if direction.lower() == "east": - self._motor_east_limit = self._motor_position_deg - else: - self._motor_west_limit = self._motor_position_deg - time.sleep(0.02) - - def motor_disable_limits(self) -> None: - self._motor_east_limit = 75.0 - self._motor_west_limit = -75.0 - time.sleep(0.02) - - def get_last_error(self) -> int: - return self._last_error - - def get_last_error_str(self) -> str: - names = {0: "OK", 1: "I2C timeout", 2: "I2C NAK", - 3: "I2C arb lost", 4: "BCM not ready", 5: "BCM timeout"} - return names.get(self._last_error, f"Unknown (0x{self._last_error:02X})") - - @property - def motor_position(self) -> float: - """Current simulated motor position in degrees.""" - self._motor_tick() - return self._motor_position_deg - - @property - def motor_is_moving(self) -> bool: - self._motor_tick() - return self._motor_moving or self._motor_direction != 0 - - def get_signal_lock(self) -> bool: - sig = self.signal_monitor() - return sig["locked"] - - def get_signal_strength(self) -> dict: - sig = self.signal_monitor() - return { - "snr_raw": sig["snr_raw"], - "snr_db": sig["snr_db"], - "snr_pct": sig["snr_pct"], - "raw_bytes": "00 00 00 00 00 00", - } - - def multi_reg_read(self, start_reg: int, count: int) -> bytes: - time.sleep(0.01) - rng = random.Random(start_reg) - return bytes(rng.randint(0, 255) for _ in range(count)) - - def get_stream_diag(self, reset: bool = False) -> dict: - """Simulated streaming diagnostics.""" - self._sd_poll_count = getattr(self, '_sd_poll_count', 0) + random.randint(50, 200) - self._sd_overflow_count = getattr(self, '_sd_overflow_count', 0) - if random.random() < 0.02: # 2% chance of overflow per call - self._sd_overflow_count += 1 - sig = self.signal_monitor() - is_locked = sig["locked"] - result = { - "poll_count": self._sd_poll_count, - "overflow_count": self._sd_overflow_count, - "sync_loss": getattr(self, '_sd_sync_loss', 0), - "last_status": 0x42, - "last_lock": 0x20 if is_locked else 0x00, - "armed": True, - "had_sync": is_locked, - } - if reset: - self._sd_poll_count = 0 - self._sd_overflow_count = 0 - self._sd_sync_loss = 0 - return result - - def get_hotplug_status(self, reset: bool = False, - force_scan: bool = False) -> dict: - """Simulated I2C hot-plug detection.""" - # Standard SkyWalker-1 I2C devices confirmed by bus scan (0xB4): - # BCM4500 demod (0x08), tuner/LNB (0x10), EEPROM (0x51) - bitmap = bytearray(16) - for addr in [0x08, 0x10, 0x51]: - bitmap[addr >> 3] |= (1 << (addr & 0x07)) - current = bytes(bitmap) - previous = current # no changes in demo - addrs = [0x08, 0x10, 0x51] - result = { - "current_bitmap": current, - "previous_bitmap": previous, - "changes": 0, - "added": 0, - "removed": 0, - "current_devices": addrs, - "previous_devices": addrs, - } - return result - - # --- Internal signal model --- - - def _power_at(self, freq_mhz: float, elapsed: float) -> float: - """Calculate synthetic power at a given frequency and time.""" - # Start with noise floor + jitter - power = _NOISE_FLOOR + random.gauss(0, 0.5) - - # Add Gaussian peaks for each simulated transponder - all_tps = list(_TRANSPONDERS) + list(_QO100_TRANSPONDERS) - for tp_freq, tp_peak, _sr in all_tps: - # Bandwidth ~15 MHz sigma for broadcast, ~3 MHz for QO-100 - sigma = 3.0 if tp_freq > 1100 and tp_freq < 1145 else 12.0 - dist = (freq_mhz - tp_freq) - gauss = math.exp(-(dist ** 2) / (2 * sigma ** 2)) - # Slow atmospheric drift: +-2 dB over 30s period - drift = 2.0 * math.sin(elapsed / 30.0 * 2 * math.pi + tp_freq / 100.0) - power += (tp_peak - _NOISE_FLOOR + drift) * gauss - - # Motor position affects signal strength (simulates dish alignment) - # Peak signal at position 0 (reference), degrades with offset - if hasattr(self, '_motor_position_deg'): - self._motor_tick() - offset = abs(self._motor_position_deg) - if offset > 2.0: - # Signal drops ~3 dB per degree off-axis beyond ±2° - power -= (offset - 2.0) * 3.0 - - return power - - @staticmethod - def _power_to_agc(power_db: float) -> tuple[int, int]: - """Convert power_db back to simulated AGC register values.""" - # Invert the agc_to_power_db formula: power = -40 * (combined / 65535) - # combined = power / -40 * 65535 - if power_db >= 0: - combined = 0 - else: - combined = int(min(65535, abs(power_db) / 40.0 * 65535)) - agc1 = min(65535, combined) - agc2 = random.randint(0, 255) << 4 # fine adjustment noise - return agc1, agc2 +"""Synthetic signal generator for --demo mode. + +DemoDevice mimics the SkyWalker1 API without any USB hardware. It generates +realistic-looking signal data with: + - Gaussian peaks at known satellite transponder positions + - Thermal noise floor with Gaussian jitter + - AGC values inversely correlated to signal strength + - Occasional lock/unlock transitions based on SNR threshold + - Slow drift in SNR to simulate atmospheric effects + +This enables full TUI development and testing without hardware. +""" + +import math +import random +import struct +import time + + +# Simulated transponder positions (IF MHz) and relative strengths +_TRANSPONDERS = [ + (1050, -12.0, 27500), # strong Ku-band TP + (1220, -18.0, 22000), # moderate TP + (1480, -25.0, 30000), # weak TP + (1750, -15.0, 20000), # moderate TP + (1950, -22.0, 13000), # weaker TP +] + +# QO-100 transponders (IF MHz for 9361 LO: RF 10491-10499 -> IF 1130-1138) +_QO100_TRANSPONDERS = [ + (1130.5, -20.0, 1500), # BATC beacon + (1131.0, -24.0, 1000), # DATV station + (1132.0, -28.0, 500), # low-power DATV + (1133.0, -30.0, 333), # minimum viable DVB-S +] + +_NOISE_FLOOR = -35.0 +_LOCK_THRESHOLD_DB = 3.5 + +# Simulated PIDs for synthetic TS packets +_DEMO_PIDS = [0x0000, 0x0100, 0x0101, 0x0102, 0x1FFF] +_DEMO_PID_WEIGHTS = [1, 5, 40, 20, 34] # rough % distribution + + +def _build_demo_eeprom() -> bytearray: + """Build a valid C2 boot EEPROM image for demo mode.""" + img = bytearray() + # C2 header: magic, VID_L, VID_H, PID_L, PID_H, DID_L, DID_H, CONFIG + img.append(0xC2) + img.extend(struct.pack('HH', code_len, 0x0000)) + img.extend(bytes(range(256)) * 2) # repeating pattern + + # END marker: 0x8001 + entry point 0x0000 + img.extend(struct.pack('>HH', 0x8001, 0x0000)) + + # Pad to 16KB with 0xFF (like a real blank EEPROM region) + img.extend(b'\xFF' * (16384 - len(img))) + return img + + +class DemoDevice: + """Drop-in replacement for SkyWalker1 that generates synthetic data.""" + + _demo = True # marker for bridge.is_demo + + def __init__(self): + self._booted = False + self._lnb_on = False + self._lnb_voltage_high = False + self._tone_22khz = False + self._extra_voltage = False + self._tuned_freq_khz = 0 + self._tuned_sr_sps = 0 + self._armed = False + self._start_time = time.monotonic() + self._sample_count = 0 + self._eeprom = _build_demo_eeprom() + self._cc_counters: dict[int, int] = {pid: 0 for pid in _DEMO_PIDS} + # Motor simulation state + self._motor_position_deg = 0.0 + self._motor_target_deg = 0.0 + self._motor_moving = False + self._motor_direction = 0 # -1=west, 0=stopped, 1=east + self._motor_speed_dps = 1.5 # degrees per second + self._motor_last_update = time.monotonic() + self._motor_stored: dict[int, float] = {} + self._motor_east_limit = 75.0 + self._motor_west_limit = -75.0 + self._last_error = 0x00 + + def open(self): + pass + + def close(self): + pass + + def __enter__(self): + return self + + def __exit__(self, *exc): + pass + + def get_fw_version(self) -> dict: + return { + "major": 3, + "minor": 3, + "patch": 0, + "version": "3.03.0", + "date": "2026-02-15", + } + + def get_config(self) -> int: + bits = 0 + if self._booted: + bits |= 0x01 # 8PSK started + bits |= 0x02 # FW loaded + if self._lnb_on: + bits |= 0x04 # LNB power + if self._lnb_voltage_high: + bits |= 0x20 # 18V + if self._tone_22khz: + bits |= 0x10 # 22 kHz + if self._tuned_freq_khz > 0: + bits |= 0x40 # tuned + return bits + + def ensure_booted(self): + self._booted = True + self._lnb_on = True + + def set_lnb_voltage(self, high: bool): + self._lnb_voltage_high = high + + def set_22khz_tone(self, on: bool): + self._tone_22khz = on + + def configure_lnb(self, pol=None, band=None, lnb_lo=None, + disable_lnb=False) -> float: + if disable_lnb: + self._lnb_on = False + return 0.0 + if pol: + self._lnb_voltage_high = pol.upper() in ("H", "L") + if band: + self._tone_22khz = band == "high" + if lnb_lo is not None: + return lnb_lo + elif band == "high": + return 10600.0 + else: + return 9750.0 + + def start_intersil(self, on: bool = True): + self._lnb_on = on + + def set_extra_voltage(self, on: bool): + self._extra_voltage = on + + def tune(self, symbol_rate_sps: int, freq_khz: int, + mod_index: int, fec_index: int): + self._tuned_freq_khz = freq_khz + self._tuned_sr_sps = symbol_rate_sps + + def signal_monitor(self) -> dict: + """Generate synthetic signal data at the currently tuned frequency.""" + self._sample_count += 1 + elapsed = time.monotonic() - self._start_time + + freq_mhz = self._tuned_freq_khz / 1000.0 if self._tuned_freq_khz else 1200.0 + power_db = self._power_at(freq_mhz, elapsed) + snr_db = max(0.0, power_db - _NOISE_FLOOR + random.gauss(0, 0.3)) + locked = snr_db > _LOCK_THRESHOLD_DB + + snr_raw = int(snr_db * 256) + agc1, agc2 = self._power_to_agc(power_db) + + return { + "snr_raw": snr_raw, + "snr_db": snr_db, + "snr_pct": min(100.0, snr_raw * 17 / 65535 * 100), + "agc1": agc1, + "agc2": agc2, + "power_db": power_db, + "lock": 0x20 if locked else 0x00, + "locked": locked, + "status": 0x01 if self._booted else 0x00, + } + + def tune_monitor(self, symbol_rate_sps: int, freq_khz: int, + mod_index: int, fec_index: int, + dwell_ms: int = 10) -> dict: + """Simulate a tune + measure at a single frequency.""" + # Simulate dwell time (scaled down for responsiveness) + time.sleep(min(dwell_ms, 5) / 1000.0) + + freq_mhz = freq_khz / 1000.0 + elapsed = time.monotonic() - self._start_time + power_db = self._power_at(freq_mhz, elapsed) + snr_db = max(0.0, power_db - _NOISE_FLOOR + random.gauss(0, 0.2)) + locked = snr_db > _LOCK_THRESHOLD_DB + + snr_raw = int(snr_db * 256) + agc1, agc2 = self._power_to_agc(power_db) + + return { + "snr_raw": snr_raw, + "snr_db": snr_db, + "agc1": agc1, + "agc2": agc2, + "power_db": power_db, + "lock": 0x20 if locked else 0x00, + "locked": locked, + "status": 0x01, + "dwell_ms": dwell_ms, + } + + def sweep_spectrum(self, start_mhz: float, stop_mhz: float, + step_mhz: float = 5.0, dwell_ms: int = 10, + sr_ksps: int = 20000, mod_index: int = 0, + fec_index: int = 5, callback=None) -> tuple: + """Sweep with synthetic data. Same return signature as SkyWalker1.""" + sr_sps = sr_ksps * 1000 + freqs = [] + powers = [] + results = [] + + freq = start_mhz + steps = max(1, int((stop_mhz - start_mhz) / step_mhz) + 1) + step_num = 0 + + while freq <= stop_mhz: + freq_khz = int(freq * 1000) + result = self.tune_monitor(sr_sps, freq_khz, mod_index, fec_index, dwell_ms) + freqs.append(freq) + powers.append(result["power_db"]) + results.append(result) + + if callback: + callback(freq, step_num, steps, result) + + step_num += 1 + freq += step_mhz + + return freqs, powers, results + + def blind_scan(self, freq_khz: int, sr_min: int, sr_max: int, + sr_step: int) -> dict | None: + """Simulate blind scan — lock onto nearby transponders.""" + freq_mhz = freq_khz / 1000.0 + for tp_freq, tp_power, tp_sr in _TRANSPONDERS: + if abs(freq_mhz - tp_freq) < 15: + sr_sps = tp_sr * 1000 + if sr_min <= sr_sps <= sr_max: + return { + "freq_khz": int(tp_freq * 1000), + "sr_sps": sr_sps, + "locked": True, + } + return None + + # --- Device info (extended) --- + + def get_serial_number(self) -> bytes: + time.sleep(0.005) + return b'DEMO0001' + + def get_usb_speed(self) -> int: + time.sleep(0.002) + return 2 # High speed + + def get_vendor_string(self) -> str: + time.sleep(0.005) + return "Genpix Electronics" + + def get_product_string(self) -> str: + time.sleep(0.005) + return "SkyWalker-1 DVB-S" + + # --- FX2 RAM access --- + + def fx2_ram_read(self, addr: int, length: int) -> bytes: + time.sleep(0.003) + # Return pseudo-random but deterministic bytes based on address + rng = random.Random(addr) + return bytes(rng.randint(0, 255) for _ in range(length)) + + def fx2_ram_write(self, addr: int, data: bytes) -> int: + time.sleep(0.003) + return len(data) + + def fx2_cpu_halt(self) -> None: + time.sleep(0.005) + + def fx2_cpu_start(self) -> None: + time.sleep(0.005) + + # --- EEPROM access --- + + def eeprom_read(self, offset: int, length: int = 64) -> bytes: + time.sleep(0.005) + end = min(offset + length, len(self._eeprom)) + return bytes(self._eeprom[offset:end]) + + def eeprom_write_page(self, offset: int, data: bytes) -> int: + time.sleep(0.012) # simulated write cycle + for i, b in enumerate(data): + if offset + i < len(self._eeprom): + self._eeprom[offset + i] = b + return len(data) + + def eeprom_read_all(self, size: int = 16384) -> bytes: + chunk_size = 64 + result = bytearray() + for offset in range(0, size, chunk_size): + remaining = min(chunk_size, size - offset) + result.extend(self.eeprom_read(offset, remaining)) + return bytes(result) + + # --- Diagnostics --- + + _BOOT_STAGES = { + 0x80: {"stage": 0x00, "result": 0x00, "detail": 0x00}, # no-op + 0x81: {"stage": 0x01, "result": 0x01, "detail": 0x00}, # GPIO OK + 0x82: {"stage": 0x02, "result": 0x01, "detail": 0x02}, # I2C probe OK + 0x83: {"stage": 0x03, "result": 0x01, "detail": 0x00}, # BCM4500 reset OK + 0x84: {"stage": 0x04, "result": 0x01, "detail": 0x00}, # FW load OK + 0x85: {"stage": 0x05, "result": 0x01, "detail": 0x00}, # full boot OK + } + + def boot_debug(self, mode: int) -> dict: + time.sleep(0.05) + return dict(self._BOOT_STAGES.get(mode, { + "stage": mode & 0x0F, "result": 0x00, "detail": 0xFF, + })) + + def i2c_bus_scan(self) -> list[int]: + time.sleep(0.1) + return [0x08, 0x51] # BCM4500 at 0x08, EEPROM at 0x51 + + def i2c_raw_read(self, slave: int, reg: int) -> int: + time.sleep(0.01) + return random.randint(0, 255) + + # --- Streaming --- + + def arm_transfer(self, on: bool) -> None: + self._armed = on + time.sleep(0.01) + + def read_stream(self, size: int = 8192, timeout: int = 1000) -> bytes: + """Generate synthetic TS packets with proper structure.""" + if not self._armed: + return b'' + time.sleep(0.02) + + packets = bytearray() + num_packets = size // 188 + + for _ in range(num_packets): + # Pick a PID based on weights + pid = random.choices(_DEMO_PIDS, weights=_DEMO_PID_WEIGHTS, k=1)[0] + cc = self._cc_counters[pid] + self._cc_counters[pid] = (cc + 1) & 0x0F + + # Occasionally inject a CC error (~0.1%) + if random.random() < 0.001 and pid != 0x1FFF: + self._cc_counters[pid] = (self._cc_counters[pid] + 1) & 0x0F + + # Build 188-byte TS packet + pkt = bytearray(188) + pkt[0] = 0x47 # sync byte + pkt[1] = (pid >> 8) & 0x1F + pkt[2] = pid & 0xFF + pkt[3] = 0x10 | (cc & 0x0F) # payload only + CC + + if pid == 0x0000: + # PAT packet with PUSI + pkt[1] |= 0x40 # PUSI + pkt[4] = 0x00 # pointer field + # PAT section: table_id=0x00, one program + pat = bytearray([ + 0x00, # table_id + 0xB0, 0x0D, # section_syntax + length=13 + 0x00, 0x01, # transport_stream_id + 0xC1, # version=0, current + 0x00, 0x00, # section_number, last_section + 0x00, 0x01, # program_number=1 + 0xE1, 0x00, # PMT PID=0x0100 + # CRC32 placeholder + 0x00, 0x00, 0x00, 0x00, + ]) + pkt[5:5 + len(pat)] = pat + + elif pid == 0x0100: + # PMT packet with PUSI + pkt[1] |= 0x40 + pkt[4] = 0x00 + pmt = bytearray([ + 0x02, # table_id + 0xB0, 0x17, # section_syntax + length=23 + 0x00, 0x01, # program_number=1 + 0xC1, # version=0 + 0x00, 0x00, # section_number + 0xE1, 0x01, # PCR PID=0x0101 + 0xF0, 0x00, # program info length=0 + # Stream 1: MPEG-2 Video on PID 0x0101 + 0x02, 0xE1, 0x01, 0xF0, 0x00, + # Stream 2: MPEG-1 Audio on PID 0x0102 + 0x03, 0xE1, 0x02, 0xF0, 0x00, + # CRC32 placeholder + 0x00, 0x00, 0x00, 0x00, + ]) + pkt[5:5 + len(pmt)] = pmt + + else: + # Fill payload with pseudo-random data + for i in range(4, 188): + pkt[i] = random.randint(0, 255) + pkt[3] = 0x10 | (cc & 0x0F) + + packets.extend(pkt) + + return bytes(packets) + + # --- DiSEqC --- + + def send_diseqc_tone_burst(self, mini_cmd: int) -> None: + time.sleep(0.05) + + def send_diseqc_message(self, msg: bytes) -> None: + time.sleep(0.05) + + # --- Motor simulation --- + + def _motor_tick(self): + """Update simulated motor position based on elapsed time.""" + now = time.monotonic() + dt = now - self._motor_last_update + self._motor_last_update = now + + if self._motor_direction != 0: + delta = self._motor_speed_dps * dt * self._motor_direction + self._motor_position_deg += delta + # Clamp to limits + self._motor_position_deg = max( + self._motor_west_limit, + min(self._motor_east_limit, self._motor_position_deg) + ) + # Stop at limits + if (self._motor_position_deg >= self._motor_east_limit or + self._motor_position_deg <= self._motor_west_limit): + self._motor_direction = 0 + self._motor_moving = False + + # Slew to target (for goto commands) + if self._motor_moving and self._motor_direction == 0: + diff = self._motor_target_deg - self._motor_position_deg + if abs(diff) < 0.1: + self._motor_position_deg = self._motor_target_deg + self._motor_moving = False + else: + step = min(abs(diff), self._motor_speed_dps * dt) + self._motor_position_deg += step if diff > 0 else -step + + def motor_halt(self) -> None: + self._motor_tick() + self._motor_direction = 0 + self._motor_moving = False + time.sleep(0.02) + + def motor_drive_east(self, steps: int = 0) -> None: + self._motor_tick() + if steps > 0: + self._motor_target_deg = self._motor_position_deg + steps * 0.1 + self._motor_target_deg = min(self._motor_target_deg, self._motor_east_limit) + self._motor_moving = True + self._motor_direction = 0 + else: + self._motor_direction = 1 + self._motor_moving = True + time.sleep(0.02) + + def motor_drive_west(self, steps: int = 0) -> None: + self._motor_tick() + if steps > 0: + self._motor_target_deg = self._motor_position_deg - steps * 0.1 + self._motor_target_deg = max(self._motor_target_deg, self._motor_west_limit) + self._motor_moving = True + self._motor_direction = 0 + else: + self._motor_direction = -1 + self._motor_moving = True + time.sleep(0.02) + + def motor_store_position(self, slot: int) -> None: + self._motor_tick() + self._motor_stored[slot] = self._motor_position_deg + time.sleep(0.02) + + def motor_goto_position(self, slot: int) -> None: + self._motor_tick() + if slot == 0: + self._motor_target_deg = 0.0 + elif slot in self._motor_stored: + self._motor_target_deg = self._motor_stored[slot] + else: + return + self._motor_moving = True + self._motor_direction = 0 + time.sleep(0.02) + + def motor_goto_x(self, observer_lon: float, sat_lon: float) -> None: + # Simplified USALS angle calculation + angle = math.degrees(math.atan2( + math.sin(math.radians(sat_lon - observer_lon)), + math.cos(math.radians(sat_lon - observer_lon)) - 6378.0 / (6378.0 + 35786.0) + )) + self._motor_tick() + self._motor_target_deg = angle + self._motor_moving = True + self._motor_direction = 0 + time.sleep(0.02) + + def motor_set_limit(self, direction: str) -> None: + self._motor_tick() + if direction.lower() == "east": + self._motor_east_limit = self._motor_position_deg + else: + self._motor_west_limit = self._motor_position_deg + time.sleep(0.02) + + def motor_disable_limits(self) -> None: + self._motor_east_limit = 75.0 + self._motor_west_limit = -75.0 + time.sleep(0.02) + + def get_last_error(self) -> int: + return self._last_error + + def get_last_error_str(self) -> str: + names = {0: "OK", 1: "I2C timeout", 2: "I2C NAK", + 3: "I2C arb lost", 4: "BCM not ready", 5: "BCM timeout"} + return names.get(self._last_error, f"Unknown (0x{self._last_error:02X})") + + @property + def motor_position(self) -> float: + """Current simulated motor position in degrees.""" + self._motor_tick() + return self._motor_position_deg + + @property + def motor_is_moving(self) -> bool: + self._motor_tick() + return self._motor_moving or self._motor_direction != 0 + + def get_signal_lock(self) -> bool: + sig = self.signal_monitor() + return sig["locked"] + + def get_signal_strength(self) -> dict: + sig = self.signal_monitor() + return { + "snr_raw": sig["snr_raw"], + "snr_db": sig["snr_db"], + "snr_pct": sig["snr_pct"], + "raw_bytes": "00 00 00 00 00 00", + } + + def multi_reg_read(self, start_reg: int, count: int) -> bytes: + time.sleep(0.01) + rng = random.Random(start_reg) + return bytes(rng.randint(0, 255) for _ in range(count)) + + def get_stream_diag(self, reset: bool = False) -> dict: + """Simulated streaming diagnostics.""" + self._sd_poll_count = getattr(self, '_sd_poll_count', 0) + random.randint(50, 200) + self._sd_overflow_count = getattr(self, '_sd_overflow_count', 0) + if random.random() < 0.02: # 2% chance of overflow per call + self._sd_overflow_count += 1 + sig = self.signal_monitor() + is_locked = sig["locked"] + result = { + "poll_count": self._sd_poll_count, + "overflow_count": self._sd_overflow_count, + "sync_loss": getattr(self, '_sd_sync_loss', 0), + "last_status": 0x42, + "last_lock": 0x20 if is_locked else 0x00, + "armed": True, + "had_sync": is_locked, + } + if reset: + self._sd_poll_count = 0 + self._sd_overflow_count = 0 + self._sd_sync_loss = 0 + return result + + def get_hotplug_status(self, reset: bool = False, + force_scan: bool = False) -> dict: + """Simulated I2C hot-plug detection.""" + # Standard SkyWalker-1 I2C devices confirmed by bus scan (0xB4): + # BCM4500 demod (0x08), tuner/LNB (0x10), EEPROM (0x51) + bitmap = bytearray(16) + for addr in [0x08, 0x10, 0x51]: + bitmap[addr >> 3] |= (1 << (addr & 0x07)) + current = bytes(bitmap) + previous = current # no changes in demo + addrs = [0x08, 0x10, 0x51] + result = { + "current_bitmap": current, + "previous_bitmap": previous, + "changes": 0, + "added": 0, + "removed": 0, + "current_devices": addrs, + "previous_devices": addrs, + } + return result + + # --- Internal signal model --- + + def _power_at(self, freq_mhz: float, elapsed: float) -> float: + """Calculate synthetic power at a given frequency and time.""" + # Start with noise floor + jitter + power = _NOISE_FLOOR + random.gauss(0, 0.5) + + # Add Gaussian peaks for each simulated transponder + all_tps = list(_TRANSPONDERS) + list(_QO100_TRANSPONDERS) + for tp_freq, tp_peak, _sr in all_tps: + # Bandwidth ~15 MHz sigma for broadcast, ~3 MHz for QO-100 + sigma = 3.0 if tp_freq > 1100 and tp_freq < 1145 else 12.0 + dist = (freq_mhz - tp_freq) + gauss = math.exp(-(dist ** 2) / (2 * sigma ** 2)) + # Slow atmospheric drift: +-2 dB over 30s period + drift = 2.0 * math.sin(elapsed / 30.0 * 2 * math.pi + tp_freq / 100.0) + power += (tp_peak - _NOISE_FLOOR + drift) * gauss + + # Motor position affects signal strength (simulates dish alignment) + # Peak signal at position 0 (reference), degrades with offset + if hasattr(self, '_motor_position_deg'): + self._motor_tick() + offset = abs(self._motor_position_deg) + if offset > 2.0: + # Signal drops ~3 dB per degree off-axis beyond ±2° + power -= (offset - 2.0) * 3.0 + + return power + + @staticmethod + def _power_to_agc(power_db: float) -> tuple[int, int]: + """Convert power_db back to simulated AGC register values.""" + # Invert the agc_to_power_db formula: power = -40 * (combined / 65535) + # combined = power / -40 * 65535 + if power_db >= 0: + combined = 0 + else: + combined = int(min(65535, abs(power_db) / 40.0 * 65535)) + agc1 = min(65535, combined) + agc2 = random.randint(0, 255) << 4 # fine adjustment noise + return agc1, agc2 diff --git a/tui/src/skywalker_tui/screens/__init__.py b/tui/src/skywalker_tui/screens/__init__.py index 4934449..1056ea2 100644 --- a/tui/src/skywalker_tui/screens/__init__.py +++ b/tui/src/skywalker_tui/screens/__init__.py @@ -1 +1 @@ -"""Mode screens for SkyWalker-1 TUI.""" +"""Mode screens for SkyWalker-1 TUI.""" diff --git a/tui/src/skywalker_tui/screens/config.py b/tui/src/skywalker_tui/screens/config.py index dbddb14..aafe15b 100644 --- a/tui/src/skywalker_tui/screens/config.py +++ b/tui/src/skywalker_tui/screens/config.py @@ -1,742 +1,742 @@ -"""Config screen — LNB power, DiSEqC switching, and modulation/FEC setup. - -Manages the hardware configuration layer: LNB voltage/tone control, DiSEqC -port switching (committed commands, tone burst, raw hex), and the tuning -parameter set (modulation, FEC, symbol rate, frequency). Bottom status bar -shows live config register state from the device. -""" - -from textual.app import ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import Label, Input, Button, Static, Select -from textual import work -from textual.worker import Worker - -from skywalker_lib import MODULATIONS, FEC_RATES, MOD_FEC_GROUP, format_config_bits - - -def _fec_options_for_mod(mod_key: str) -> list[tuple[str, str]]: - """Return (label, value) tuples for the FEC Select dropdown.""" - group = MOD_FEC_GROUP.get(mod_key, "dvbs") - rates = FEC_RATES.get(group, {}) - return [(rate_name, rate_name) for rate_name in rates] - - -def _mod_options() -> list[tuple[str, str]]: - """Return (label, value) tuples for the Modulation Select dropdown.""" - return [(desc, key) for key, (_idx, desc) in MODULATIONS.items()] - - -class ConfigScreen(Container): - """LNB, DiSEqC, and modulation/FEC configuration panel.""" - - DEFAULT_CSS = """ - ConfigScreen { - layout: vertical; - } - ConfigScreen #cfg-main { - height: 1fr; - layout: horizontal; - padding: 1 2; - } - - /* --- Three column panels --- */ - - ConfigScreen .cfg-panel { - width: 1fr; - background: #0e1420; - border: round #1a2a3a; - padding: 1 2; - margin: 0 1 0 0; - layout: vertical; - } - ConfigScreen .cfg-panel:last-of-type { - margin: 0; - } - ConfigScreen .cfg-panel-title { - color: #00d4aa; - text-style: bold; - margin: 0 0 1 0; - height: 1; - } - - /* --- Buttons within panels --- */ - - ConfigScreen .cfg-btn-row { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - ConfigScreen .cfg-btn-row Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - min-width: 12; - } - ConfigScreen .cfg-btn-row Button { - margin: 0 1 0 0; - background: #1a2a40; - color: #c8d0d8; - border: round #1a3050; - } - ConfigScreen .cfg-btn-row Button:hover { - background: #00d4aa; - color: #0a0a12; - } - ConfigScreen .cfg-btn-row Button.-active-setting { - background: #0a3a3a; - color: #00d4aa; - border: round #00d4aa; - text-style: bold; - } - - /* --- Warning label --- */ - - ConfigScreen .cfg-warning { - color: #e8a020; - margin: 1 0 0 0; - text-style: italic; - height: auto; - } - - /* --- DiSEqC port buttons --- */ - - ConfigScreen #cfg-diseqc-ports { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - ConfigScreen #cfg-diseqc-ports Button { - margin: 0 1 0 0; - min-width: 10; - background: #1a2a40; - color: #c8d0d8; - border: round #1a3050; - } - ConfigScreen #cfg-diseqc-ports Button:hover { - background: #00d4aa; - color: #0a0a12; - } - ConfigScreen #cfg-diseqc-ports Button.-active-setting { - background: #0a3a3a; - color: #00d4aa; - border: round #00d4aa; - text-style: bold; - } - - /* --- Raw DiSEqC input row --- */ - - ConfigScreen #cfg-diseqc-raw { - height: auto; - layout: horizontal; - margin: 1 0 0 0; - } - ConfigScreen #cfg-diseqc-raw Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - ConfigScreen #cfg-diseqc-raw Input { - width: 1fr; - margin: 0 1 0 0; - background: #121c2a; - border: round #1a3050; - color: #c8d0d8; - } - ConfigScreen #cfg-diseqc-raw Input:focus { - border: round #00d4aa; - } - ConfigScreen #cfg-diseqc-raw Button { - margin: 0; - background: #1a2a40; - color: #c8d0d8; - border: round #1a3050; - } - - /* --- Modulation/FEC panel --- */ - - ConfigScreen .cfg-field-row { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - ConfigScreen .cfg-field-row Label { - width: auto; - min-width: 12; - margin: 1 1 0 0; - color: #506878; - } - ConfigScreen .cfg-field-row Select { - width: 1fr; - } - ConfigScreen .cfg-field-row Input { - width: 1fr; - background: #121c2a; - border: round #1a3050; - color: #c8d0d8; - } - ConfigScreen .cfg-field-row Input:focus { - border: round #00d4aa; - } - ConfigScreen #cfg-tune-btn { - margin: 1 0 0 0; - width: 100%; - } - - /* --- Bottom status bar --- */ - - ConfigScreen #cfg-status-bar { - height: auto; - min-height: 3; - padding: 1 2; - background: #0e1018; - border-top: solid #1a2a3a; - } - ConfigScreen #cfg-result { - height: auto; - min-height: 2; - padding: 0 2; - background: #0e1018; - } - """ - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._refresh_worker: Worker | None = None - self._active_port: int | None = None - self._lnb_power = False - self._lnb_voltage_high = False - self._tone_22khz = False - self._extra_volt = False - - def compose(self) -> ComposeResult: - with Horizontal(id="cfg-main"): - # --- LNB Control --- - with Vertical(classes="cfg-panel"): - yield Static("[#00d4aa bold]LNB Control[/]", classes="cfg-panel-title") - - with Horizontal(classes="cfg-btn-row"): - yield Label("Power:") - yield Button("On", id="cfg-lnb-on", variant="success") - yield Button("Off", id="cfg-lnb-off", variant="error") - - with Horizontal(classes="cfg-btn-row"): - yield Label("Voltage:") - yield Button("13V", id="cfg-volt-13") - yield Button("18V", id="cfg-volt-18") - - with Horizontal(classes="cfg-btn-row"): - yield Label("22kHz Tone:") - yield Button("On", id="cfg-tone-on") - yield Button("Off", id="cfg-tone-off") - - with Horizontal(classes="cfg-btn-row"): - yield Label("Extra +1V:") - yield Button("On", id="cfg-extra-on") - yield Button("Off", id="cfg-extra-off") - - yield Static( - "[#e8a020]Max 450mA continuous load[/]", - classes="cfg-warning", - ) - - # --- DiSEqC Switch --- - with Vertical(classes="cfg-panel"): - yield Static("[#00d4aa bold]DiSEqC Switch[/]", classes="cfg-panel-title") - - yield Label("Committed Port:", classes="cfg-section-label") - with Horizontal(id="cfg-diseqc-ports"): - yield Button("Port 1", id="cfg-port-1") - yield Button("Port 2", id="cfg-port-2") - yield Button("Port 3", id="cfg-port-3") - yield Button("Port 4", id="cfg-port-4") - - with Horizontal(classes="cfg-btn-row"): - yield Label("Tone Burst:") - yield Button("A", id="cfg-burst-a") - yield Button("B", id="cfg-burst-b") - - with Horizontal(id="cfg-diseqc-raw"): - yield Label("Raw Hex:") - yield Input( - placeholder="E0 10 38 F0", - id="cfg-diseqc-hex", - ) - yield Button("Send", id="cfg-diseqc-send") - - # --- Modulation / FEC --- - with Vertical(classes="cfg-panel"): - yield Static( - "[#00d4aa bold]Modulation / FEC[/]", - classes="cfg-panel-title", - ) - - with Horizontal(classes="cfg-field-row"): - yield Label("Modulation:") - yield Select( - _mod_options(), - value="qpsk", - id="cfg-mod-select", - allow_blank=False, - ) - - with Horizontal(classes="cfg-field-row"): - yield Label("FEC Rate:") - yield Select( - _fec_options_for_mod("qpsk"), - value="auto", - id="cfg-fec-select", - allow_blank=False, - ) - - with Horizontal(classes="cfg-field-row"): - yield Label("Symbol Rate:") - yield Input("20000", id="cfg-sr-input") - yield Label("ksps") - - with Horizontal(classes="cfg-field-row"): - yield Label("Frequency:") - yield Input("1200", id="cfg-freq-input") - yield Label("MHz") - - yield Button( - "Tune", - id="cfg-tune-btn", - variant="success", - ) - - yield Static("", id="cfg-result") - yield Static("[#506878]Config: reading...[/]", id="cfg-status-bar") - - # ── Lifecycle ── - - def on_show(self) -> None: - self._refresh_config() - - def on_hide(self) -> None: - if self._refresh_worker is not None: - self._refresh_worker.cancel() - self._refresh_worker = None - - # ── Event handlers ── - - def on_button_pressed(self, event: Button.Pressed) -> None: - btn = event.button.id - if btn is None: - return - - # LNB control - if btn == "cfg-lnb-on": - self._do_lnb_power(True) - elif btn == "cfg-lnb-off": - self._do_lnb_power(False) - elif btn == "cfg-volt-13": - self._do_lnb_voltage(high=False) - elif btn == "cfg-volt-18": - self._do_lnb_voltage(high=True) - elif btn == "cfg-tone-on": - self._do_22khz_tone(on=True) - elif btn == "cfg-tone-off": - self._do_22khz_tone(on=False) - elif btn == "cfg-extra-on": - self._do_extra_voltage(on=True) - elif btn == "cfg-extra-off": - self._do_extra_voltage(on=False) - - # DiSEqC ports - elif btn == "cfg-port-1": - self._do_diseqc_port(1) - elif btn == "cfg-port-2": - self._do_diseqc_port(2) - elif btn == "cfg-port-3": - self._do_diseqc_port(3) - elif btn == "cfg-port-4": - self._do_diseqc_port(4) - - # Tone burst - elif btn == "cfg-burst-a": - self._do_tone_burst(0) - elif btn == "cfg-burst-b": - self._do_tone_burst(1) - - # Raw DiSEqC - elif btn == "cfg-diseqc-send": - self._do_diseqc_raw() - - # Tune - elif btn == "cfg-tune-btn": - self._do_tune() - - def on_select_changed(self, event: Select.Changed) -> None: - if event.select.id == "cfg-mod-select": - mod_key = str(event.value) - self._update_fec_options(mod_key) - - # ── LNB operations ── - - @work(thread=True) - def _do_lnb_power(self, on: bool) -> None: - try: - self._bridge.start_intersil(on) - self._lnb_power = on - label = "[#00d4aa]ON[/]" if on else "[#e04040]OFF[/]" - self.app.call_from_thread( - self._show_result, - f"[#c8d0d8]LNB power:[/] {label}", - ) - self.app.call_from_thread(self._highlight_lnb_power, on) - except Exception as e: - self.app.call_from_thread( - self._show_result, - f"[#e04040]LNB power error: {e}[/]", - ) - self.app.call_from_thread(self._refresh_config) - - @work(thread=True) - def _do_lnb_voltage(self, high: bool) -> None: - try: - self._bridge.set_lnb_voltage(high) - self._lnb_voltage_high = high - volts = "18V" if high else "13V" - self.app.call_from_thread( - self._show_result, - f"[#c8d0d8]LNB voltage set to[/] [#00d4aa]{volts}[/]", - ) - self.app.call_from_thread(self._highlight_voltage, high) - except Exception as e: - self.app.call_from_thread( - self._show_result, - f"[#e04040]Voltage error: {e}[/]", - ) - self.app.call_from_thread(self._refresh_config) - - @work(thread=True) - def _do_22khz_tone(self, on: bool) -> None: - try: - self._bridge.set_22khz_tone(on) - self._tone_22khz = on - label = "[#00d4aa]ON[/]" if on else "[#e04040]OFF[/]" - self.app.call_from_thread( - self._show_result, - f"[#c8d0d8]22kHz tone:[/] {label}", - ) - self.app.call_from_thread(self._highlight_tone, on) - except Exception as e: - self.app.call_from_thread( - self._show_result, - f"[#e04040]22kHz tone error: {e}[/]", - ) - self.app.call_from_thread(self._refresh_config) - - @work(thread=True) - def _do_extra_voltage(self, on: bool) -> None: - try: - self._bridge.set_extra_voltage(on) - self._extra_volt = on - label = "[#00d4aa]ON[/]" if on else "[#e04040]OFF[/]" - self.app.call_from_thread( - self._show_result, - f"[#c8d0d8]Extra +1V:[/] {label}", - ) - self.app.call_from_thread(self._highlight_extra, on) - except Exception as e: - self.app.call_from_thread( - self._show_result, - f"[#e04040]Extra voltage error: {e}[/]", - ) - self.app.call_from_thread(self._refresh_config) - - # ── DiSEqC operations ── - - _DISEQC_PORT_CMDS = { - 1: bytes([0xE0, 0x10, 0x38, 0xF0]), - 2: bytes([0xE0, 0x10, 0x38, 0xF4]), - 3: bytes([0xE0, 0x10, 0x38, 0xF8]), - 4: bytes([0xE0, 0x10, 0x38, 0xFC]), - } - - @work(thread=True) - def _do_diseqc_port(self, port: int) -> None: - cmd = self._DISEQC_PORT_CMDS[port] - try: - self._bridge.send_diseqc_message(cmd) - self._active_port = port - self.app.call_from_thread( - self._show_result, - f"[#c8d0d8]DiSEqC port[/] [#00d4aa]{port}[/]" - f" [#506878]({cmd.hex(' ')})[/]", - ) - self.app.call_from_thread(self._highlight_port, port) - except Exception as e: - self.app.call_from_thread( - self._show_result, - f"[#e04040]DiSEqC port {port} error: {e}[/]", - ) - - @work(thread=True) - def _do_tone_burst(self, mini_cmd: int) -> None: - label = "A" if mini_cmd == 0 else "B" - try: - self._bridge.send_diseqc_tone_burst(mini_cmd) - self.app.call_from_thread( - self._show_result, - f"[#c8d0d8]Tone burst[/] [#00d4aa]{label}[/]", - ) - except Exception as e: - self.app.call_from_thread( - self._show_result, - f"[#e04040]Tone burst error: {e}[/]", - ) - - @work(thread=True) - def _do_diseqc_raw(self) -> None: - try: - hex_input = self.app.call_from_thread(self._get_diseqc_hex) - except Exception: - return - - if not hex_input: - self.app.call_from_thread( - self._show_result, - "[#e8a020]Enter hex bytes (e.g. E0 10 38 F0)[/]", - ) - return - - try: - raw = bytes.fromhex(hex_input.replace(",", " ")) - except ValueError: - self.app.call_from_thread( - self._show_result, - f"[#e04040]Invalid hex: {hex_input}[/]", - ) - return - - if len(raw) < 3 or len(raw) > 6: - self.app.call_from_thread( - self._show_result, - f"[#e04040]DiSEqC message must be 3-6 bytes, got {len(raw)}[/]", - ) - return - - try: - self._bridge.send_diseqc_message(raw) - self.app.call_from_thread( - self._show_result, - f"[#c8d0d8]DiSEqC sent:[/] [#00d4aa]{raw.hex(' ')}[/]", - ) - except Exception as e: - self.app.call_from_thread( - self._show_result, - f"[#e04040]DiSEqC send error: {e}[/]", - ) - - def _get_diseqc_hex(self) -> str: - """Read the raw hex input value (must be called from UI thread).""" - return self.query_one("#cfg-diseqc-hex", Input).value.strip() - - # ── Tune operation ── - - @work(thread=True) - def _do_tune(self) -> None: - # Read inputs from UI thread - try: - params = self.app.call_from_thread(self._read_tune_params) - except Exception: - return - - if params is None: - return - - mod_key, fec_key, sr_ksps, freq_mhz = params - - # Validate - if not (256 <= sr_ksps <= 30000): - self.app.call_from_thread( - self._show_result, - "[#e04040]Symbol rate out of range (256-30000 ksps)[/]", - ) - return - - if not (950 <= freq_mhz <= 2150): - self.app.call_from_thread( - self._show_result, - "[#e04040]Frequency out of range (950-2150 MHz)[/]", - ) - return - - mod_index = MODULATIONS[mod_key][0] - fec_group = MOD_FEC_GROUP.get(mod_key, "dvbs") - fec_rates = FEC_RATES.get(fec_group, {}) - fec_index = fec_rates.get(fec_key, 0) - - sr_sps = sr_ksps * 1000 - freq_khz = int(freq_mhz * 1000) - - try: - self._bridge.ensure_booted() - self._bridge.tune(sr_sps, freq_khz, mod_index, fec_index) - mod_desc = MODULATIONS[mod_key][1] - self.app.call_from_thread( - self._show_result, - f"[#00d4aa]Tuned:[/] [#c8d0d8]{freq_mhz:.1f} MHz " - f"{sr_ksps} ksps {mod_desc} FEC {fec_key}[/]", - ) - except Exception as e: - self.app.call_from_thread( - self._show_result, - f"[#e04040]Tune error: {e}[/]", - ) - self.app.call_from_thread(self._refresh_config) - - def _read_tune_params(self) -> tuple | None: - """Read tune parameters from UI widgets (must be called from UI thread).""" - mod_select = self.query_one("#cfg-mod-select", Select) - fec_select = self.query_one("#cfg-fec-select", Select) - sr_input = self.query_one("#cfg-sr-input", Input) - freq_input = self.query_one("#cfg-freq-input", Input) - - mod_key = str(mod_select.value) if mod_select.value is not None else "qpsk" - fec_key = str(fec_select.value) if fec_select.value is not None else "auto" - - try: - sr_ksps = int(float(sr_input.value or "20000")) - except ValueError: - self._show_result("[#e04040]Invalid symbol rate[/]") - return None - - try: - freq_mhz = float(freq_input.value or "1200") - except ValueError: - self._show_result("[#e04040]Invalid frequency[/]") - return None - - return (mod_key, fec_key, sr_ksps, freq_mhz) - - # ── FEC dropdown update ── - - def _update_fec_options(self, mod_key: str) -> None: - """Rebuild the FEC dropdown when modulation changes.""" - fec_select = self.query_one("#cfg-fec-select", Select) - options = _fec_options_for_mod(mod_key) - fec_select.set_options(options) - # Default to "auto" if available, otherwise first option - auto_keys = [v for (_l, v) in options if v == "auto"] - if auto_keys: - fec_select.value = "auto" - elif options: - fec_select.value = options[0][1] - - # ── Config status display ── - - @work(thread=True) - def _refresh_config(self) -> None: - """Read config register and update the status bar.""" - try: - status = self._bridge.get_config() - bits = format_config_bits(status) - self.app.call_from_thread(self._display_config, status, bits) - except Exception as e: - self.app.call_from_thread( - self._display_config_error, str(e), - ) - - def _display_config(self, status: int, bits: list) -> None: - """Render config bits in the status bar (UI thread).""" - if not self.is_mounted: - return - - parts = [] - for name, is_set in bits: - if is_set: - parts.append(f"[#00d4aa]{name}[/]") - else: - parts.append(f"[#303840]{name}[/]") - - text = ( - f"[#506878]Config 0x{status:02X}:[/] " - + " ".join(parts) - ) - self.query_one("#cfg-status-bar", Static).update(text) - - # Sync button highlights from config bits - self._lnb_power = bool(status & 0x04) - self._lnb_voltage_high = bool(status & 0x20) - self._tone_22khz = bool(status & 0x10) - - self._highlight_lnb_power(self._lnb_power) - self._highlight_voltage(self._lnb_voltage_high) - self._highlight_tone(self._tone_22khz) - - def _display_config_error(self, error: str) -> None: - if not self.is_mounted: - return - self.query_one("#cfg-status-bar", Static).update( - f"[#e04040]Config read error: {error}[/]" - ) - - # ── UI highlight helpers ── - - def _show_result(self, markup: str) -> None: - """Display operation result text.""" - if not self.is_mounted: - return - self.query_one("#cfg-result", Static).update(markup) - - def _highlight_lnb_power(self, on: bool) -> None: - if not self.is_mounted: - return - btn_on = self.query_one("#cfg-lnb-on", Button) - btn_off = self.query_one("#cfg-lnb-off", Button) - if on: - btn_on.add_class("-active-setting") - btn_off.remove_class("-active-setting") - else: - btn_off.add_class("-active-setting") - btn_on.remove_class("-active-setting") - - def _highlight_voltage(self, high: bool) -> None: - if not self.is_mounted: - return - btn_13 = self.query_one("#cfg-volt-13", Button) - btn_18 = self.query_one("#cfg-volt-18", Button) - if high: - btn_18.add_class("-active-setting") - btn_13.remove_class("-active-setting") - else: - btn_13.add_class("-active-setting") - btn_18.remove_class("-active-setting") - - def _highlight_tone(self, on: bool) -> None: - if not self.is_mounted: - return - btn_on = self.query_one("#cfg-tone-on", Button) - btn_off = self.query_one("#cfg-tone-off", Button) - if on: - btn_on.add_class("-active-setting") - btn_off.remove_class("-active-setting") - else: - btn_off.add_class("-active-setting") - btn_on.remove_class("-active-setting") - - def _highlight_extra(self, on: bool) -> None: - if not self.is_mounted: - return - btn_on = self.query_one("#cfg-extra-on", Button) - btn_off = self.query_one("#cfg-extra-off", Button) - if on: - btn_on.add_class("-active-setting") - btn_off.remove_class("-active-setting") - else: - btn_off.add_class("-active-setting") - btn_on.remove_class("-active-setting") - - def _highlight_port(self, port: int) -> None: - if not self.is_mounted: - return - for p in range(1, 5): - btn = self.query_one(f"#cfg-port-{p}", Button) - if p == port: - btn.add_class("-active-setting") - else: - btn.remove_class("-active-setting") +"""Config screen — LNB power, DiSEqC switching, and modulation/FEC setup. + +Manages the hardware configuration layer: LNB voltage/tone control, DiSEqC +port switching (committed commands, tone burst, raw hex), and the tuning +parameter set (modulation, FEC, symbol rate, frequency). Bottom status bar +shows live config register state from the device. +""" + +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, Vertical +from textual.widgets import Label, Input, Button, Static, Select +from textual import work +from textual.worker import Worker + +from skywalker_lib import MODULATIONS, FEC_RATES, MOD_FEC_GROUP, format_config_bits + + +def _fec_options_for_mod(mod_key: str) -> list[tuple[str, str]]: + """Return (label, value) tuples for the FEC Select dropdown.""" + group = MOD_FEC_GROUP.get(mod_key, "dvbs") + rates = FEC_RATES.get(group, {}) + return [(rate_name, rate_name) for rate_name in rates] + + +def _mod_options() -> list[tuple[str, str]]: + """Return (label, value) tuples for the Modulation Select dropdown.""" + return [(desc, key) for key, (_idx, desc) in MODULATIONS.items()] + + +class ConfigScreen(Container): + """LNB, DiSEqC, and modulation/FEC configuration panel.""" + + DEFAULT_CSS = """ + ConfigScreen { + layout: vertical; + } + ConfigScreen #cfg-main { + height: 1fr; + layout: horizontal; + padding: 1 2; + } + + /* --- Three column panels --- */ + + ConfigScreen .cfg-panel { + width: 1fr; + background: #0e1420; + border: round #1a2a3a; + padding: 1 2; + margin: 0 1 0 0; + layout: vertical; + } + ConfigScreen .cfg-panel:last-of-type { + margin: 0; + } + ConfigScreen .cfg-panel-title { + color: #00d4aa; + text-style: bold; + margin: 0 0 1 0; + height: 1; + } + + /* --- Buttons within panels --- */ + + ConfigScreen .cfg-btn-row { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + ConfigScreen .cfg-btn-row Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + min-width: 12; + } + ConfigScreen .cfg-btn-row Button { + margin: 0 1 0 0; + background: #1a2a40; + color: #c8d0d8; + border: round #1a3050; + } + ConfigScreen .cfg-btn-row Button:hover { + background: #00d4aa; + color: #0a0a12; + } + ConfigScreen .cfg-btn-row Button.-active-setting { + background: #0a3a3a; + color: #00d4aa; + border: round #00d4aa; + text-style: bold; + } + + /* --- Warning label --- */ + + ConfigScreen .cfg-warning { + color: #e8a020; + margin: 1 0 0 0; + text-style: italic; + height: auto; + } + + /* --- DiSEqC port buttons --- */ + + ConfigScreen #cfg-diseqc-ports { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + ConfigScreen #cfg-diseqc-ports Button { + margin: 0 1 0 0; + min-width: 10; + background: #1a2a40; + color: #c8d0d8; + border: round #1a3050; + } + ConfigScreen #cfg-diseqc-ports Button:hover { + background: #00d4aa; + color: #0a0a12; + } + ConfigScreen #cfg-diseqc-ports Button.-active-setting { + background: #0a3a3a; + color: #00d4aa; + border: round #00d4aa; + text-style: bold; + } + + /* --- Raw DiSEqC input row --- */ + + ConfigScreen #cfg-diseqc-raw { + height: auto; + layout: horizontal; + margin: 1 0 0 0; + } + ConfigScreen #cfg-diseqc-raw Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + ConfigScreen #cfg-diseqc-raw Input { + width: 1fr; + margin: 0 1 0 0; + background: #121c2a; + border: round #1a3050; + color: #c8d0d8; + } + ConfigScreen #cfg-diseqc-raw Input:focus { + border: round #00d4aa; + } + ConfigScreen #cfg-diseqc-raw Button { + margin: 0; + background: #1a2a40; + color: #c8d0d8; + border: round #1a3050; + } + + /* --- Modulation/FEC panel --- */ + + ConfigScreen .cfg-field-row { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + ConfigScreen .cfg-field-row Label { + width: auto; + min-width: 12; + margin: 1 1 0 0; + color: #506878; + } + ConfigScreen .cfg-field-row Select { + width: 1fr; + } + ConfigScreen .cfg-field-row Input { + width: 1fr; + background: #121c2a; + border: round #1a3050; + color: #c8d0d8; + } + ConfigScreen .cfg-field-row Input:focus { + border: round #00d4aa; + } + ConfigScreen #cfg-tune-btn { + margin: 1 0 0 0; + width: 100%; + } + + /* --- Bottom status bar --- */ + + ConfigScreen #cfg-status-bar { + height: auto; + min-height: 3; + padding: 1 2; + background: #0e1018; + border-top: solid #1a2a3a; + } + ConfigScreen #cfg-result { + height: auto; + min-height: 2; + padding: 0 2; + background: #0e1018; + } + """ + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._refresh_worker: Worker | None = None + self._active_port: int | None = None + self._lnb_power = False + self._lnb_voltage_high = False + self._tone_22khz = False + self._extra_volt = False + + def compose(self) -> ComposeResult: + with Horizontal(id="cfg-main"): + # --- LNB Control --- + with Vertical(classes="cfg-panel"): + yield Static("[#00d4aa bold]LNB Control[/]", classes="cfg-panel-title") + + with Horizontal(classes="cfg-btn-row"): + yield Label("Power:") + yield Button("On", id="cfg-lnb-on", variant="success") + yield Button("Off", id="cfg-lnb-off", variant="error") + + with Horizontal(classes="cfg-btn-row"): + yield Label("Voltage:") + yield Button("13V", id="cfg-volt-13") + yield Button("18V", id="cfg-volt-18") + + with Horizontal(classes="cfg-btn-row"): + yield Label("22kHz Tone:") + yield Button("On", id="cfg-tone-on") + yield Button("Off", id="cfg-tone-off") + + with Horizontal(classes="cfg-btn-row"): + yield Label("Extra +1V:") + yield Button("On", id="cfg-extra-on") + yield Button("Off", id="cfg-extra-off") + + yield Static( + "[#e8a020]Max 450mA continuous load[/]", + classes="cfg-warning", + ) + + # --- DiSEqC Switch --- + with Vertical(classes="cfg-panel"): + yield Static("[#00d4aa bold]DiSEqC Switch[/]", classes="cfg-panel-title") + + yield Label("Committed Port:", classes="cfg-section-label") + with Horizontal(id="cfg-diseqc-ports"): + yield Button("Port 1", id="cfg-port-1") + yield Button("Port 2", id="cfg-port-2") + yield Button("Port 3", id="cfg-port-3") + yield Button("Port 4", id="cfg-port-4") + + with Horizontal(classes="cfg-btn-row"): + yield Label("Tone Burst:") + yield Button("A", id="cfg-burst-a") + yield Button("B", id="cfg-burst-b") + + with Horizontal(id="cfg-diseqc-raw"): + yield Label("Raw Hex:") + yield Input( + placeholder="E0 10 38 F0", + id="cfg-diseqc-hex", + ) + yield Button("Send", id="cfg-diseqc-send") + + # --- Modulation / FEC --- + with Vertical(classes="cfg-panel"): + yield Static( + "[#00d4aa bold]Modulation / FEC[/]", + classes="cfg-panel-title", + ) + + with Horizontal(classes="cfg-field-row"): + yield Label("Modulation:") + yield Select( + _mod_options(), + value="qpsk", + id="cfg-mod-select", + allow_blank=False, + ) + + with Horizontal(classes="cfg-field-row"): + yield Label("FEC Rate:") + yield Select( + _fec_options_for_mod("qpsk"), + value="auto", + id="cfg-fec-select", + allow_blank=False, + ) + + with Horizontal(classes="cfg-field-row"): + yield Label("Symbol Rate:") + yield Input("20000", id="cfg-sr-input") + yield Label("ksps") + + with Horizontal(classes="cfg-field-row"): + yield Label("Frequency:") + yield Input("1200", id="cfg-freq-input") + yield Label("MHz") + + yield Button( + "Tune", + id="cfg-tune-btn", + variant="success", + ) + + yield Static("", id="cfg-result") + yield Static("[#506878]Config: reading...[/]", id="cfg-status-bar") + + # ── Lifecycle ── + + def on_show(self) -> None: + self._refresh_config() + + def on_hide(self) -> None: + if self._refresh_worker is not None: + self._refresh_worker.cancel() + self._refresh_worker = None + + # ── Event handlers ── + + def on_button_pressed(self, event: Button.Pressed) -> None: + btn = event.button.id + if btn is None: + return + + # LNB control + if btn == "cfg-lnb-on": + self._do_lnb_power(True) + elif btn == "cfg-lnb-off": + self._do_lnb_power(False) + elif btn == "cfg-volt-13": + self._do_lnb_voltage(high=False) + elif btn == "cfg-volt-18": + self._do_lnb_voltage(high=True) + elif btn == "cfg-tone-on": + self._do_22khz_tone(on=True) + elif btn == "cfg-tone-off": + self._do_22khz_tone(on=False) + elif btn == "cfg-extra-on": + self._do_extra_voltage(on=True) + elif btn == "cfg-extra-off": + self._do_extra_voltage(on=False) + + # DiSEqC ports + elif btn == "cfg-port-1": + self._do_diseqc_port(1) + elif btn == "cfg-port-2": + self._do_diseqc_port(2) + elif btn == "cfg-port-3": + self._do_diseqc_port(3) + elif btn == "cfg-port-4": + self._do_diseqc_port(4) + + # Tone burst + elif btn == "cfg-burst-a": + self._do_tone_burst(0) + elif btn == "cfg-burst-b": + self._do_tone_burst(1) + + # Raw DiSEqC + elif btn == "cfg-diseqc-send": + self._do_diseqc_raw() + + # Tune + elif btn == "cfg-tune-btn": + self._do_tune() + + def on_select_changed(self, event: Select.Changed) -> None: + if event.select.id == "cfg-mod-select": + mod_key = str(event.value) + self._update_fec_options(mod_key) + + # ── LNB operations ── + + @work(thread=True) + def _do_lnb_power(self, on: bool) -> None: + try: + self._bridge.start_intersil(on) + self._lnb_power = on + label = "[#00d4aa]ON[/]" if on else "[#e04040]OFF[/]" + self.app.call_from_thread( + self._show_result, + f"[#c8d0d8]LNB power:[/] {label}", + ) + self.app.call_from_thread(self._highlight_lnb_power, on) + except Exception as e: + self.app.call_from_thread( + self._show_result, + f"[#e04040]LNB power error: {e}[/]", + ) + self.app.call_from_thread(self._refresh_config) + + @work(thread=True) + def _do_lnb_voltage(self, high: bool) -> None: + try: + self._bridge.set_lnb_voltage(high) + self._lnb_voltage_high = high + volts = "18V" if high else "13V" + self.app.call_from_thread( + self._show_result, + f"[#c8d0d8]LNB voltage set to[/] [#00d4aa]{volts}[/]", + ) + self.app.call_from_thread(self._highlight_voltage, high) + except Exception as e: + self.app.call_from_thread( + self._show_result, + f"[#e04040]Voltage error: {e}[/]", + ) + self.app.call_from_thread(self._refresh_config) + + @work(thread=True) + def _do_22khz_tone(self, on: bool) -> None: + try: + self._bridge.set_22khz_tone(on) + self._tone_22khz = on + label = "[#00d4aa]ON[/]" if on else "[#e04040]OFF[/]" + self.app.call_from_thread( + self._show_result, + f"[#c8d0d8]22kHz tone:[/] {label}", + ) + self.app.call_from_thread(self._highlight_tone, on) + except Exception as e: + self.app.call_from_thread( + self._show_result, + f"[#e04040]22kHz tone error: {e}[/]", + ) + self.app.call_from_thread(self._refresh_config) + + @work(thread=True) + def _do_extra_voltage(self, on: bool) -> None: + try: + self._bridge.set_extra_voltage(on) + self._extra_volt = on + label = "[#00d4aa]ON[/]" if on else "[#e04040]OFF[/]" + self.app.call_from_thread( + self._show_result, + f"[#c8d0d8]Extra +1V:[/] {label}", + ) + self.app.call_from_thread(self._highlight_extra, on) + except Exception as e: + self.app.call_from_thread( + self._show_result, + f"[#e04040]Extra voltage error: {e}[/]", + ) + self.app.call_from_thread(self._refresh_config) + + # ── DiSEqC operations ── + + _DISEQC_PORT_CMDS = { + 1: bytes([0xE0, 0x10, 0x38, 0xF0]), + 2: bytes([0xE0, 0x10, 0x38, 0xF4]), + 3: bytes([0xE0, 0x10, 0x38, 0xF8]), + 4: bytes([0xE0, 0x10, 0x38, 0xFC]), + } + + @work(thread=True) + def _do_diseqc_port(self, port: int) -> None: + cmd = self._DISEQC_PORT_CMDS[port] + try: + self._bridge.send_diseqc_message(cmd) + self._active_port = port + self.app.call_from_thread( + self._show_result, + f"[#c8d0d8]DiSEqC port[/] [#00d4aa]{port}[/]" + f" [#506878]({cmd.hex(' ')})[/]", + ) + self.app.call_from_thread(self._highlight_port, port) + except Exception as e: + self.app.call_from_thread( + self._show_result, + f"[#e04040]DiSEqC port {port} error: {e}[/]", + ) + + @work(thread=True) + def _do_tone_burst(self, mini_cmd: int) -> None: + label = "A" if mini_cmd == 0 else "B" + try: + self._bridge.send_diseqc_tone_burst(mini_cmd) + self.app.call_from_thread( + self._show_result, + f"[#c8d0d8]Tone burst[/] [#00d4aa]{label}[/]", + ) + except Exception as e: + self.app.call_from_thread( + self._show_result, + f"[#e04040]Tone burst error: {e}[/]", + ) + + @work(thread=True) + def _do_diseqc_raw(self) -> None: + try: + hex_input = self.app.call_from_thread(self._get_diseqc_hex) + except Exception: + return + + if not hex_input: + self.app.call_from_thread( + self._show_result, + "[#e8a020]Enter hex bytes (e.g. E0 10 38 F0)[/]", + ) + return + + try: + raw = bytes.fromhex(hex_input.replace(",", " ")) + except ValueError: + self.app.call_from_thread( + self._show_result, + f"[#e04040]Invalid hex: {hex_input}[/]", + ) + return + + if len(raw) < 3 or len(raw) > 6: + self.app.call_from_thread( + self._show_result, + f"[#e04040]DiSEqC message must be 3-6 bytes, got {len(raw)}[/]", + ) + return + + try: + self._bridge.send_diseqc_message(raw) + self.app.call_from_thread( + self._show_result, + f"[#c8d0d8]DiSEqC sent:[/] [#00d4aa]{raw.hex(' ')}[/]", + ) + except Exception as e: + self.app.call_from_thread( + self._show_result, + f"[#e04040]DiSEqC send error: {e}[/]", + ) + + def _get_diseqc_hex(self) -> str: + """Read the raw hex input value (must be called from UI thread).""" + return self.query_one("#cfg-diseqc-hex", Input).value.strip() + + # ── Tune operation ── + + @work(thread=True) + def _do_tune(self) -> None: + # Read inputs from UI thread + try: + params = self.app.call_from_thread(self._read_tune_params) + except Exception: + return + + if params is None: + return + + mod_key, fec_key, sr_ksps, freq_mhz = params + + # Validate + if not (256 <= sr_ksps <= 30000): + self.app.call_from_thread( + self._show_result, + "[#e04040]Symbol rate out of range (256-30000 ksps)[/]", + ) + return + + if not (950 <= freq_mhz <= 2150): + self.app.call_from_thread( + self._show_result, + "[#e04040]Frequency out of range (950-2150 MHz)[/]", + ) + return + + mod_index = MODULATIONS[mod_key][0] + fec_group = MOD_FEC_GROUP.get(mod_key, "dvbs") + fec_rates = FEC_RATES.get(fec_group, {}) + fec_index = fec_rates.get(fec_key, 0) + + sr_sps = sr_ksps * 1000 + freq_khz = int(freq_mhz * 1000) + + try: + self._bridge.ensure_booted() + self._bridge.tune(sr_sps, freq_khz, mod_index, fec_index) + mod_desc = MODULATIONS[mod_key][1] + self.app.call_from_thread( + self._show_result, + f"[#00d4aa]Tuned:[/] [#c8d0d8]{freq_mhz:.1f} MHz " + f"{sr_ksps} ksps {mod_desc} FEC {fec_key}[/]", + ) + except Exception as e: + self.app.call_from_thread( + self._show_result, + f"[#e04040]Tune error: {e}[/]", + ) + self.app.call_from_thread(self._refresh_config) + + def _read_tune_params(self) -> tuple | None: + """Read tune parameters from UI widgets (must be called from UI thread).""" + mod_select = self.query_one("#cfg-mod-select", Select) + fec_select = self.query_one("#cfg-fec-select", Select) + sr_input = self.query_one("#cfg-sr-input", Input) + freq_input = self.query_one("#cfg-freq-input", Input) + + mod_key = str(mod_select.value) if mod_select.value is not None else "qpsk" + fec_key = str(fec_select.value) if fec_select.value is not None else "auto" + + try: + sr_ksps = int(float(sr_input.value or "20000")) + except ValueError: + self._show_result("[#e04040]Invalid symbol rate[/]") + return None + + try: + freq_mhz = float(freq_input.value or "1200") + except ValueError: + self._show_result("[#e04040]Invalid frequency[/]") + return None + + return (mod_key, fec_key, sr_ksps, freq_mhz) + + # ── FEC dropdown update ── + + def _update_fec_options(self, mod_key: str) -> None: + """Rebuild the FEC dropdown when modulation changes.""" + fec_select = self.query_one("#cfg-fec-select", Select) + options = _fec_options_for_mod(mod_key) + fec_select.set_options(options) + # Default to "auto" if available, otherwise first option + auto_keys = [v for (_l, v) in options if v == "auto"] + if auto_keys: + fec_select.value = "auto" + elif options: + fec_select.value = options[0][1] + + # ── Config status display ── + + @work(thread=True) + def _refresh_config(self) -> None: + """Read config register and update the status bar.""" + try: + status = self._bridge.get_config() + bits = format_config_bits(status) + self.app.call_from_thread(self._display_config, status, bits) + except Exception as e: + self.app.call_from_thread( + self._display_config_error, str(e), + ) + + def _display_config(self, status: int, bits: list) -> None: + """Render config bits in the status bar (UI thread).""" + if not self.is_mounted: + return + + parts = [] + for name, is_set in bits: + if is_set: + parts.append(f"[#00d4aa]{name}[/]") + else: + parts.append(f"[#303840]{name}[/]") + + text = ( + f"[#506878]Config 0x{status:02X}:[/] " + + " ".join(parts) + ) + self.query_one("#cfg-status-bar", Static).update(text) + + # Sync button highlights from config bits + self._lnb_power = bool(status & 0x04) + self._lnb_voltage_high = bool(status & 0x20) + self._tone_22khz = bool(status & 0x10) + + self._highlight_lnb_power(self._lnb_power) + self._highlight_voltage(self._lnb_voltage_high) + self._highlight_tone(self._tone_22khz) + + def _display_config_error(self, error: str) -> None: + if not self.is_mounted: + return + self.query_one("#cfg-status-bar", Static).update( + f"[#e04040]Config read error: {error}[/]" + ) + + # ── UI highlight helpers ── + + def _show_result(self, markup: str) -> None: + """Display operation result text.""" + if not self.is_mounted: + return + self.query_one("#cfg-result", Static).update(markup) + + def _highlight_lnb_power(self, on: bool) -> None: + if not self.is_mounted: + return + btn_on = self.query_one("#cfg-lnb-on", Button) + btn_off = self.query_one("#cfg-lnb-off", Button) + if on: + btn_on.add_class("-active-setting") + btn_off.remove_class("-active-setting") + else: + btn_off.add_class("-active-setting") + btn_on.remove_class("-active-setting") + + def _highlight_voltage(self, high: bool) -> None: + if not self.is_mounted: + return + btn_13 = self.query_one("#cfg-volt-13", Button) + btn_18 = self.query_one("#cfg-volt-18", Button) + if high: + btn_18.add_class("-active-setting") + btn_13.remove_class("-active-setting") + else: + btn_13.add_class("-active-setting") + btn_18.remove_class("-active-setting") + + def _highlight_tone(self, on: bool) -> None: + if not self.is_mounted: + return + btn_on = self.query_one("#cfg-tone-on", Button) + btn_off = self.query_one("#cfg-tone-off", Button) + if on: + btn_on.add_class("-active-setting") + btn_off.remove_class("-active-setting") + else: + btn_off.add_class("-active-setting") + btn_on.remove_class("-active-setting") + + def _highlight_extra(self, on: bool) -> None: + if not self.is_mounted: + return + btn_on = self.query_one("#cfg-extra-on", Button) + btn_off = self.query_one("#cfg-extra-off", Button) + if on: + btn_on.add_class("-active-setting") + btn_off.remove_class("-active-setting") + else: + btn_off.add_class("-active-setting") + btn_on.remove_class("-active-setting") + + def _highlight_port(self, port: int) -> None: + if not self.is_mounted: + return + for p in range(1, 5): + btn = self.query_one(f"#cfg-port-{p}", Button) + if p == port: + btn.add_class("-active-setting") + else: + btn.remove_class("-active-setting") diff --git a/tui/src/skywalker_tui/screens/device.py b/tui/src/skywalker_tui/screens/device.py index 00c4c02..5b6ea85 100644 --- a/tui/src/skywalker_tui/screens/device.py +++ b/tui/src/skywalker_tui/screens/device.py @@ -1,1235 +1,1235 @@ -"""Device screen -- firmware management, EEPROM operations, and diagnostics. - -This is the most complex screen in the TUI. It provides three tabs: - - Firmware: FX2 RAM read/write, CPU halt/start - EEPROM: Read, backup, and flash (safety-critical!) C2 firmware images - Diagnostics: Boot stage tests, I2C bus scan, BCM4500 register dump - -The EEPROM flash operation follows a strict state machine to prevent -accidental writes to the boot EEPROM, which would brick the device: - - idle -> file_selected -> validated -> backup -> countdown -> writing -> verifying -> done - |-> invalid (red) |-> error (persistent) - -A corrupted EEPROM means no USB enumeration until an external programmer -is used. Every safety gate is mandatory and cannot be bypassed via the UI. -""" - -import os -import time -from datetime import datetime - -from textual.app import ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import ( - Label, Input, Button, Static, Select, ProgressBar, -) -from textual import work -from textual.worker import Worker - -from eeprom_write import parse_c2_header, parse_records - -from skywalker_tui.widgets.hex_view import HexView -from skywalker_tui.widgets.countdown_timer import CountdownTimer -from skywalker_tui.widgets.config_bits import ConfigBitsDisplay - - -# EEPROM constants (must match eeprom_write.py and skywalker_lib.py) -EEPROM_PAGE_SIZE = 16 -EEPROM_WRITE_CYCLE_MS = 10 -MAX_EEPROM_SIZE = 16384 -VENDOR_ID = 0x09C0 -PRODUCT_ID = 0x0203 - - -class DeviceScreen(Container): - """Firmware management, EEPROM flash, and hardware diagnostics.""" - - DEFAULT_CSS = """ - DeviceScreen { - layout: vertical; - } - - /* --- Identity panel (always visible) --- */ - DeviceScreen #dev-identity { - height: auto; - padding: 1 2; - background: #0e1420; - border-bottom: solid #1a2a3a; - } - DeviceScreen #dev-identity-title { - color: #00d4aa; - text-style: bold; - margin: 0 0 1 0; - } - DeviceScreen #dev-identity-info { - color: #c8d0d8; - } - DeviceScreen #dev-demo-badge { - color: #e8a020; - text-style: bold; - margin: 0 0 0 0; - } - - /* --- Tab row --- */ - DeviceScreen #dev-tab-row { - height: auto; - padding: 0 2; - background: #0e1018; - layout: horizontal; - } - DeviceScreen .dev-tab-btn { - margin: 0 1 0 0; - min-width: 16; - } - DeviceScreen .dev-tab-btn.-active { - background: #0a2a3a; - color: #00d4aa; - border: round #00d4aa; - text-style: bold; - } - - /* --- Tab content panels --- */ - DeviceScreen #dev-tab-content { - height: 1fr; - } - DeviceScreen .dev-tab-panel { - height: 1fr; - layout: vertical; - padding: 1 2; - } - - /* --- Firmware tab --- */ - DeviceScreen #dev-fw-controls { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - DeviceScreen #dev-fw-controls Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - DeviceScreen #dev-fw-controls Input { - width: 12; - margin: 0 1; - } - DeviceScreen #dev-fw-controls Button { - margin: 0 1; - } - DeviceScreen #dev-fw-cpu-controls { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - DeviceScreen #dev-fw-cpu-controls Button { - margin: 0 1; - } - DeviceScreen #dev-fw-status { - height: auto; - color: #506878; - margin: 0 0 1 0; - } - - /* --- EEPROM tab --- */ - DeviceScreen #dev-ee-read-row { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - DeviceScreen #dev-ee-read-row Button { - margin: 0 1; - } - DeviceScreen #dev-ee-flash-section { - height: auto; - background: #0e1018; - border: round #1a2a3a; - padding: 1; - margin: 0 0 1 0; - } - DeviceScreen #dev-ee-flash-title { - color: #e8a020; - text-style: bold; - margin: 0 0 1 0; - } - DeviceScreen #dev-ee-flash-controls { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - DeviceScreen #dev-ee-flash-controls Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - DeviceScreen #dev-ee-flash-controls Input { - width: 1fr; - margin: 0 1; - } - DeviceScreen #dev-ee-flash-controls Button { - margin: 0 1; - } - DeviceScreen #dev-ee-validation { - height: auto; - margin: 0 0 1 0; - } - DeviceScreen #dev-ee-progress-row { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - DeviceScreen #dev-ee-progress-label { - width: auto; - margin: 0 1 0 0; - color: #506878; - } - DeviceScreen #dev-ee-pbar { - width: 1fr; - } - DeviceScreen #dev-ee-status { - height: auto; - color: #506878; - margin: 0 0 1 0; - } - DeviceScreen #dev-ee-persistent-warn { - height: auto; - color: #e04040; - text-style: bold; - margin: 0 0 1 0; - } - - /* --- Diagnostics tab --- */ - DeviceScreen #dev-diag-boot-row { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - DeviceScreen #dev-diag-boot-row Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - DeviceScreen #dev-diag-boot-row Button { - margin: 0 1; - } - DeviceScreen #dev-diag-boot-result { - height: auto; - color: #c8d0d8; - margin: 0 0 1 0; - } - DeviceScreen #dev-diag-i2c-row { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - DeviceScreen #dev-diag-i2c-row Button { - margin: 0 1; - } - DeviceScreen #dev-diag-i2c-result { - height: auto; - color: #c8d0d8; - margin: 0 0 1 0; - } - DeviceScreen #dev-diag-reg-row { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - DeviceScreen #dev-diag-reg-row Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - DeviceScreen #dev-diag-reg-row Input { - width: 10; - margin: 0 1; - } - DeviceScreen #dev-diag-reg-row Button { - margin: 0 1; - } - """ - - # Boot diagnostic mode values - _BOOT_MODES = [ - ("0x80 No-op", 0x80), - ("0x81 GPIO init", 0x81), - ("0x82 I2C probe", 0x82), - ("0x83 BCM4500 reset", 0x83), - ("0x84 FW load", 0x84), - ("0x85 Full boot", 0x85), - ] - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._active_tab = "firmware" - self._workers: list[Worker] = [] - - # EEPROM flash state machine - self._flash_state = "idle" - self._flash_image: bytes = b'' - self._flash_header: dict | None = None - self._flash_records: list | None = None - self._eeprom_backup: bytes = b'' - self._verify_failed = False - - def compose(self) -> ComposeResult: - # Identity panel (always visible) - with Vertical(id="dev-identity"): - yield Static("[#00d4aa bold]Device Identity[/]", id="dev-identity-title") - if self._bridge.is_demo: - yield Static("[#e8a020 bold]DEMO MODE \u2014 no hardware writes[/]", - id="dev-demo-badge") - yield Static("[#506878]Loading...[/]", id="dev-identity-info") - yield ConfigBitsDisplay(id="dev-config-bits") - - # Tab buttons - with Horizontal(id="dev-tab-row"): - yield Button("Firmware", id="dev-tab-firmware", - classes="dev-tab-btn -active") - yield Button("EEPROM", id="dev-tab-eeprom", classes="dev-tab-btn") - yield Button("Diagnostics", id="dev-tab-diag", classes="dev-tab-btn") - - # Tab content area -- we toggle visibility manually - with Vertical(id="dev-tab-content"): - # === Firmware tab === - with Vertical(id="dev-panel-firmware", classes="dev-tab-panel"): - with Horizontal(id="dev-fw-controls"): - yield Label("Address (hex):") - yield Input("0x0000", id="dev-fw-addr") - yield Label("Length:") - yield Input("64", id="dev-fw-len") - yield Button("Read RAM", id="dev-fw-read", variant="primary") - with Horizontal(id="dev-fw-cpu-controls"): - yield Button("Halt CPU", id="dev-fw-halt", variant="error") - yield Button("Start CPU", id="dev-fw-start", variant="success") - yield Static("[#506878]Ready[/]", id="dev-fw-status") - yield HexView(id="dev-fw-hex") - - # === EEPROM tab === - with Vertical(id="dev-panel-eeprom", classes="dev-tab-panel"): - with Horizontal(id="dev-ee-read-row"): - yield Button("Read All", id="dev-ee-read", variant="primary") - yield Button("Backup to File", id="dev-ee-backup") - yield Static("", id="dev-ee-persistent-warn") - - # Flash section - with Vertical(id="dev-ee-flash-section"): - yield Static( - "[#e8a020 bold]Flash Firmware Image[/]", - id="dev-ee-flash-title", - ) - with Horizontal(id="dev-ee-flash-controls"): - yield Label("Image file:") - yield Input("", id="dev-ee-filepath", - placeholder="/path/to/firmware.bin") - yield Button("Validate", id="dev-ee-validate") - yield Button("Flash", id="dev-ee-flash", disabled=True, - variant="error") - yield Static("", id="dev-ee-validation") - - with Horizontal(id="dev-ee-progress-row"): - yield Static("[#506878]Idle[/]", id="dev-ee-progress-label") - yield ProgressBar(total=100, show_eta=False, id="dev-ee-pbar") - yield Static("[#506878]Ready[/]", id="dev-ee-status") - yield HexView(id="dev-ee-hex") - - # === Diagnostics tab === - with Vertical(id="dev-panel-diag", classes="dev-tab-panel"): - # Boot test - yield Static("[#00d4aa bold]Boot Diagnostic[/]") - with Horizontal(id="dev-diag-boot-row"): - yield Label("Mode:") - yield Select( - [(label, val) for label, val in self._BOOT_MODES], - value=0x80, - id="dev-diag-boot-mode", - ) - yield Button("Run", id="dev-diag-boot-run", variant="primary") - yield Static("", id="dev-diag-boot-result") - - # I2C scan - yield Static("[#00d4aa bold]I2C Bus Scan[/]") - with Horizontal(id="dev-diag-i2c-row"): - yield Button("Scan", id="dev-diag-i2c-scan", variant="primary") - yield Static("", id="dev-diag-i2c-result") - - # BCM4500 register dump - yield Static("[#00d4aa bold]BCM4500 Register Dump[/]") - with Horizontal(id="dev-diag-reg-row"): - yield Label("Start reg:") - yield Input("0x00", id="dev-diag-reg-start") - yield Label("Count:") - yield Input("32", id="dev-diag-reg-count") - yield Button("Read", id="dev-diag-reg-read", variant="primary") - yield HexView(id="dev-diag-hex") - - # --- Lifecycle --- - - def on_show(self) -> None: - """Read device identity when the screen becomes visible.""" - self._read_identity() - # Hide inactive tabs - self._apply_tab_visibility() - - def on_hide(self) -> None: - """Cancel all running workers when leaving the screen.""" - for w in self._workers: - try: - w.cancel() - except Exception: - pass - self._workers.clear() - - # --- Tab switching --- - - def _switch_tab(self, tab: str) -> None: - """Switch between firmware / eeprom / diag tabs.""" - self._active_tab = tab - # Update button highlights - for name in ("firmware", "eeprom", "diag"): - btn = self.query_one(f"#dev-tab-{name}", Button) - if name == tab: - btn.add_class("-active") - else: - btn.remove_class("-active") - self._apply_tab_visibility() - - def _apply_tab_visibility(self) -> None: - """Show/hide tab panels based on active tab.""" - tab_map = { - "firmware": "dev-panel-firmware", - "eeprom": "dev-panel-eeprom", - "diag": "dev-panel-diag", - } - for name, panel_id in tab_map.items(): - try: - panel = self.query_one(f"#{panel_id}") - panel.display = (name == self._active_tab) - except Exception: - pass - - # --- Button dispatch --- - - def on_button_pressed(self, event: Button.Pressed) -> None: - btn_id = event.button.id or "" - - # Tab buttons - if btn_id == "dev-tab-firmware": - self._switch_tab("firmware") - elif btn_id == "dev-tab-eeprom": - self._switch_tab("eeprom") - elif btn_id == "dev-tab-diag": - self._switch_tab("diag") - - # Firmware tab - elif btn_id == "dev-fw-read": - self._fw_ram_read() - elif btn_id == "dev-fw-halt": - self._fw_cpu_halt() - elif btn_id == "dev-fw-start": - self._fw_cpu_start() - - # EEPROM tab - elif btn_id == "dev-ee-read": - self._ee_read_all() - elif btn_id == "dev-ee-backup": - self._ee_backup() - elif btn_id == "dev-ee-validate": - self._ee_validate_file() - elif btn_id == "dev-ee-flash": - self._ee_begin_flash() - - # Diagnostics tab - elif btn_id == "dev-diag-boot-run": - self._diag_boot_test() - elif btn_id == "dev-diag-i2c-scan": - self._diag_i2c_scan() - elif btn_id == "dev-diag-reg-read": - self._diag_reg_read() - - # --- Countdown timer messages --- - - def on_countdown_timer_completed(self, _msg: CountdownTimer.Completed) -> None: - """Countdown finished -- proceed with EEPROM write.""" - self._remove_countdown() - self._ee_do_write() - - def on_countdown_timer_aborted(self, _msg: CountdownTimer.Aborted) -> None: - """Operator aborted the countdown.""" - self._remove_countdown() - self._flash_state = "idle" - self._set_ee_status("[#e8a020]Flash aborted by operator[/]") - self.query_one("#dev-ee-flash", Button).disabled = False - - def _remove_countdown(self) -> None: - """Remove the countdown widget from the flash section.""" - try: - timer = self.query_one(CountdownTimer) - timer.remove() - except Exception: - pass - - # --- Identity panel --- - - @work(thread=True) - def _read_identity(self) -> None: - """Read device info in a background thread and update the identity panel.""" - try: - fw = self._bridge.get_fw_version() - serial = self._bridge.get_serial_number() - speed = self._bridge.get_usb_speed() - config = self._bridge.get_config() - vendor = self._bridge.get_vendor_string() - product = self._bridge.get_product_string() - except Exception as e: - self.app.call_from_thread( - self._update_identity_error, str(e) - ) - return - - speed_str = {0: "Unknown", 1: "Full (12 Mbps)", 2: "High (480 Mbps)"}.get( - speed, f"0x{speed:02X}" - ) - serial_hex = serial.hex(' ') if isinstance(serial, (bytes, bytearray)) else str(serial) - fw_str = fw.get("version", "?") - fw_date = fw.get("date", "?") - - info_lines = [ - f"[#506878]Firmware:[/] [#c8d0d8]{fw_str}[/] [#506878]({fw_date})[/]", - f"[#506878]Serial:[/] [#c8d0d8]{serial_hex}[/]", - f"[#506878]USB:[/] [#c8d0d8]{speed_str}[/]", - f"[#506878]Vendor:[/] [#c8d0d8]{vendor}[/]", - f"[#506878]Product:[/] [#c8d0d8]{product}[/]", - ] - self.app.call_from_thread(self._update_identity, "\n".join(info_lines), config) - - def _update_identity(self, info_text: str, config: int) -> None: - if not self.is_mounted: - return - self.query_one("#dev-identity-info", Static).update(info_text) - self.query_one("#dev-config-bits", ConfigBitsDisplay).update_config(config) - - def _update_identity_error(self, error: str) -> None: - if not self.is_mounted: - return - self.query_one("#dev-identity-info", Static).update( - f"[bold #e04040]Error reading device info:[/] [#e04040]{error}[/]" - ) - - # ========================================================================== - # Firmware tab operations - # ========================================================================== - - @work(thread=True) - def _fw_ram_read(self) -> None: - """Read FX2 RAM region and display in hex view.""" - try: - addr_str = self.app.call_from_thread( - lambda: self.query_one("#dev-fw-addr", Input).value - ) - len_str = self.app.call_from_thread( - lambda: self.query_one("#dev-fw-len", Input).value - ) - addr = int(addr_str, 0) - length = int(len_str, 0) - length = max(1, min(length, 4096)) - except (ValueError, TypeError): - self.app.call_from_thread( - self._set_fw_status, "[bold #e04040]Invalid address or length[/]" - ) - return - - self.app.call_from_thread( - self._set_fw_status, f"[#506878]Reading 0x{addr:04X}...[/]" - ) - - try: - # Read in chunks of 64 bytes (USB control transfer limit) - data = bytearray() - remaining = length - offset = addr - while remaining > 0: - chunk_size = min(64, remaining) - chunk = self._bridge.fx2_ram_read(offset, chunk_size) - data.extend(chunk) - offset += chunk_size - remaining -= chunk_size - except Exception as e: - self.app.call_from_thread( - self._set_fw_status, - f"[bold #e04040]RAM read failed:[/] [#e04040]{e}[/]", - ) - return - - self.app.call_from_thread(self._show_fw_data, bytes(data), addr, length) - - def _show_fw_data(self, data: bytes, addr: int, length: int) -> None: - if not self.is_mounted: - return - self.query_one("#dev-fw-hex", HexView).set_data(data) - self._set_fw_status( - f"[#00d4aa]Read {len(data)} bytes from 0x{addr:04X}[/]" - ) - - @work(thread=True) - def _fw_cpu_halt(self) -> None: - self.app.call_from_thread( - self._set_fw_status, "[#e8a020]Halting FX2 CPU...[/]" - ) - try: - self._bridge.fx2_cpu_halt() - self.app.call_from_thread( - self._set_fw_status, "[#e04040]CPU halted (CPUCS=0x01)[/]" - ) - except Exception as e: - self.app.call_from_thread( - self._set_fw_status, - f"[bold #e04040]Halt failed:[/] [#e04040]{e}[/]", - ) - - @work(thread=True) - def _fw_cpu_start(self) -> None: - self.app.call_from_thread( - self._set_fw_status, "[#e8a020]Starting FX2 CPU...[/]" - ) - try: - self._bridge.fx2_cpu_start() - self.app.call_from_thread( - self._set_fw_status, "[#00e060]CPU started (CPUCS=0x00)[/]" - ) - except Exception as e: - self.app.call_from_thread( - self._set_fw_status, - f"[bold #e04040]Start failed:[/] [#e04040]{e}[/]", - ) - - def _set_fw_status(self, text: str) -> None: - if not self.is_mounted: - return - self.query_one("#dev-fw-status", Static).update(text) - - # ========================================================================== - # EEPROM tab operations - # ========================================================================== - - @work(thread=True) - def _ee_read_all(self) -> None: - """Read entire EEPROM and display in hex view.""" - self.app.call_from_thread( - self._set_ee_status, "[#506878]Reading EEPROM (16 KB)...[/]" - ) - self.app.call_from_thread(self._set_ee_progress, "Reading...", 0) - - try: - data = bytearray() - chunk_size = 64 - total = MAX_EEPROM_SIZE - for offset in range(0, total, chunk_size): - remaining = min(chunk_size, total - offset) - chunk = self._bridge.eeprom_read(offset, remaining) - data.extend(chunk) - pct = (offset + remaining) / total * 100 - self.app.call_from_thread(self._set_ee_progress, "Reading...", pct) - except Exception as e: - self.app.call_from_thread( - self._set_ee_status, - f"[bold #e04040]EEPROM read failed:[/] [#e04040]{e}[/]", - ) - return - - self.app.call_from_thread(self._show_ee_data, bytes(data)) - - def _show_ee_data(self, data: bytes) -> None: - if not self.is_mounted: - return - self.query_one("#dev-ee-hex", HexView).set_data(data) - self._set_ee_progress("Complete", 100) - # Parse and display C2 header info - header = parse_c2_header(data) - if header: - self._set_ee_status( - f"[#00d4aa]Read {len(data)} bytes[/] \u2014 " - f"C2 VID=0x{header['vid']:04X} PID=0x{header['pid']:04X} " - f"Config=0x{header['config']:02X}" - ) - else: - self._set_ee_status( - f"[#e8a020]Read {len(data)} bytes \u2014 not a valid C2 image[/]" - ) - - @work(thread=True) - def _ee_backup(self) -> None: - """Read EEPROM and save to timestamped .bin file.""" - ts = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = f"skywalker1_eeprom_{ts}.bin" - - self.app.call_from_thread( - self._set_ee_status, f"[#506878]Backing up to {filename}...[/]" - ) - self.app.call_from_thread(self._set_ee_progress, "Backup...", 0) - - try: - data = bytearray() - chunk_size = 64 - total = MAX_EEPROM_SIZE - for offset in range(0, total, chunk_size): - remaining = min(chunk_size, total - offset) - chunk = self._bridge.eeprom_read(offset, remaining) - data.extend(chunk) - pct = (offset + remaining) / total * 100 - self.app.call_from_thread(self._set_ee_progress, "Backup...", pct) - - with open(filename, 'wb') as f: - f.write(data) - except Exception as e: - self.app.call_from_thread( - self._set_ee_status, - f"[bold #e04040]Backup failed:[/] [#e04040]{e}[/]", - ) - return - - abs_path = os.path.abspath(filename) - self.app.call_from_thread( - self._set_ee_status, - f"[#00d4aa]Backup saved:[/] [#c8d0d8]{abs_path}[/] " - f"[#506878]({len(data)} bytes)[/]", - ) - self.app.call_from_thread(self._set_ee_progress, "Complete", 100) - - # --- C2 image validation --- - - def _ee_validate_file(self) -> None: - """Validate the selected C2 firmware image file.""" - # Block re-entry during active flash operations - if self._flash_state in ("backup", "countdown", "writing", "verifying"): - return - filepath = self.query_one("#dev-ee-filepath", Input).value.strip() - validation = self.query_one("#dev-ee-validation", Static) - flash_btn = self.query_one("#dev-ee-flash", Button) - - if not filepath: - validation.update("[bold #e04040]No file path entered[/]") - flash_btn.disabled = True - self._flash_state = "idle" - return - - if not os.path.exists(filepath): - validation.update(f"[bold #e04040]File not found:[/] [#e04040]{filepath}[/]") - flash_btn.disabled = True - self._flash_state = "idle" - return - - try: - with open(filepath, 'rb') as f: - image = f.read() - except OSError as e: - validation.update(f"[bold #e04040]Cannot read file:[/] [#e04040]{e}[/]") - flash_btn.disabled = True - self._flash_state = "idle" - return - - self._flash_state = "file_selected" - - # Run all C2 validation checks - errors = [] - warnings = [] - - # Size checks - if len(image) > MAX_EEPROM_SIZE: - errors.append( - f"Image too large: {len(image)} bytes (max {MAX_EEPROM_SIZE})" - ) - if len(image) < 12: - errors.append(f"Image too small: {len(image)} bytes (need >= 12)") - - # Magic byte - if not image or image[0] != 0xC2: - errors.append( - f"Not a C2 image (first byte: 0x{image[0]:02X}, expected 0xC2)" - if image else "Empty file" - ) - - if errors: - self._flash_state = "invalid" - flash_btn.disabled = True - err_text = "\n".join(f"[bold #e04040]\u2717 {e}[/]" for e in errors) - validation.update(err_text) - return - - # Parse C2 header - header = parse_c2_header(image) - if header is None: - self._flash_state = "invalid" - flash_btn.disabled = True - validation.update("[bold #e04040]\u2717 Failed to parse C2 header[/]") - return - - # VID/PID match - if header["vid"] != VENDOR_ID: - errors.append( - f"VID mismatch: image 0x{header['vid']:04X}, " - f"expected 0x{VENDOR_ID:04X}" - ) - if header["pid"] != PRODUCT_ID: - errors.append( - f"PID mismatch: image 0x{header['pid']:04X}, " - f"expected 0x{PRODUCT_ID:04X}" - ) - - # Parse records and check for END marker - records = parse_records(image) - end_recs = [r for r in records if r["type"] == "end"] - invalid_recs = [r for r in records if r["type"] == "invalid"] - - if not end_recs: - errors.append("No END marker (0x8001) found in image") - if invalid_recs: - warnings.append(f"{len(invalid_recs)} invalid record(s) in image") - - if errors: - self._flash_state = "invalid" - flash_btn.disabled = True - lines = [f"[bold #e04040]\u2717 {e}[/]" for e in errors] - lines += [f"[#e8a020]\u26a0 {w}[/]" for w in warnings] - validation.update("\n".join(lines)) - return - - # All checks passed - self._flash_state = "validated" - self._flash_image = image - self._flash_header = header - self._flash_records = records - - data_recs = [r for r in records if r["type"] == "data"] - total_code = sum(r["length"] for r in data_recs) - entry = end_recs[0]["entry_point"] if end_recs else 0 - - lines = [ - "[bold #00e060]\u2713 Valid C2 image[/]", - f"[#506878]Size:[/] [#c8d0d8]{len(image)} bytes[/]", - f"[#506878]VID:[/] [#c8d0d8]0x{header['vid']:04X}[/] " - f"[#506878]PID:[/] [#c8d0d8]0x{header['pid']:04X}[/]", - f"[#506878]Code:[/] [#c8d0d8]{total_code} bytes " - f"in {len(data_recs)} segment(s)[/]", - f"[#506878]Entry:[/] [#c8d0d8]0x{entry:04X}[/]", - ] - if warnings: - lines += [f"[#e8a020]\u26a0 {w}[/]" for w in warnings] - - validation.update("\n".join(lines)) - flash_btn.disabled = False - - # --- Flash state machine --- - - def _ee_begin_flash(self) -> None: - """Start the flash sequence: backup -> countdown -> write -> verify.""" - if self._flash_state != "validated": - return - - # In demo mode, show the full flow but skip actual writes - if self._bridge.is_demo: - self._set_ee_status( - "[#e8a020 bold]DEMO MODE \u2014 simulating flash " - "(no hardware writes)[/]" - ) - - self.query_one("#dev-ee-flash", Button).disabled = True - self._flash_state = "backup" - self._ee_flash_backup() - - @work(thread=True) - def _ee_flash_backup(self) -> None: - """Pre-flash backup of current EEPROM contents.""" - self.app.call_from_thread( - self._set_ee_status, "[#506878]Pre-flash backup...[/]" - ) - self.app.call_from_thread(self._set_ee_progress, "Backup...", 0) - - try: - data = bytearray() - chunk_size = 64 - total = MAX_EEPROM_SIZE - for offset in range(0, total, chunk_size): - remaining = min(chunk_size, total - offset) - chunk = self._bridge.eeprom_read(offset, remaining) - data.extend(chunk) - pct = (offset + remaining) / total * 100 - self.app.call_from_thread(self._set_ee_progress, "Backup...", pct) - - self._eeprom_backup = bytes(data) - - # Save backup to file - ts = datetime.now().strftime("%Y%m%d_%H%M%S") - backup_file = f"eeprom_preflash_{ts}.bin" - with open(backup_file, 'wb') as f: - f.write(data) - - abs_path = os.path.abspath(backup_file) - self.app.call_from_thread( - self._set_ee_status, - f"[#00d4aa]Pre-flash backup saved:[/] [#c8d0d8]{abs_path}[/]", - ) - except Exception as e: - self.app.call_from_thread( - self._set_ee_status, - f"[bold #e04040]Backup failed \u2014 flash aborted:[/] " - f"[#e04040]{e}[/]", - ) - self._flash_state = "idle" - self.app.call_from_thread(self._enable_flash_button) - return - - self._flash_state = "countdown" - self.app.call_from_thread(self._show_countdown) - - def _show_countdown(self) -> None: - """Mount the countdown timer widget into the flash section.""" - if not self.is_mounted: - return - flash_section = self.query_one("#dev-ee-flash-section") - timer = CountdownTimer(id="dev-ee-countdown") - flash_section.mount(timer) - timer.start() - - def _ee_do_write(self) -> None: - """Called when countdown completes -- start the actual write.""" - self._flash_state = "writing" - w = self._ee_write_and_verify() - self._workers.append(w) - - @work(thread=True) - def _ee_write_and_verify(self) -> None: - """Write the firmware image to EEPROM, then verify.""" - image = self._flash_image - total_pages = (len(image) + EEPROM_PAGE_SIZE - 1) // EEPROM_PAGE_SIZE - write_errors = 0 - consecutive_errors = 0 - write_aborted = False - max_consecutive_errors = 3 - - self.app.call_from_thread( - self._set_ee_status, - f"[#e8a020 bold]Writing {len(image)} bytes " - f"({total_pages} pages)...[/]", - ) - - for page_num in range(total_pages): - offset = page_num * EEPROM_PAGE_SIZE - end = min(offset + EEPROM_PAGE_SIZE, len(image)) - chunk = image[offset:end] - pct = (page_num + 1) / total_pages * 100 - - self.app.call_from_thread( - self._set_ee_progress, - f"Writing 0x{offset:04X}...", pct, - ) - - try: - written = self._bridge.eeprom_write_page(offset, chunk) - if written != len(chunk): - write_errors += 1 - consecutive_errors += 1 - else: - consecutive_errors = 0 - except Exception: - write_errors += 1 - consecutive_errors += 1 - - if consecutive_errors >= max_consecutive_errors: - write_aborted = True - self.app.call_from_thread( - self._set_ee_status, - f"[bold #e04040]WRITE ABORTED after " - f"{consecutive_errors} consecutive errors " - f"at 0x{offset:04X}[/]", - ) - break - - # Wait for EEPROM internal write cycle - time.sleep(EEPROM_WRITE_CYCLE_MS / 1000.0) - - if write_aborted: - self._flash_state = "error" - self._verify_failed = True - self.app.call_from_thread( - self._show_verify_error, - f"Write aborted at page {page_num}/{total_pages} " - f"({write_errors} total errors)", - set(), - ) - return - - if write_errors: - self.app.call_from_thread( - self._set_ee_status, - f"[bold #e04040]Write completed with {write_errors} error(s)[/]", - ) - - # --- Verify phase --- - self._flash_state = "verifying" - self.app.call_from_thread( - self._set_ee_status, - f"[#506878]Verifying {len(image)} bytes...[/]", - ) - - try: - verify_data = bytearray() - chunk_size = 64 - for offset in range(0, len(image), chunk_size): - remaining = min(chunk_size, len(image) - offset) - chunk = self._bridge.eeprom_read(offset, remaining) - verify_data.extend(chunk) - pct = (offset + remaining) / len(image) * 100 - self.app.call_from_thread( - self._set_ee_progress, "Verifying...", pct, - ) - except Exception as e: - self._flash_state = "error" - self._verify_failed = True - self.app.call_from_thread( - self._show_verify_error, - f"Verify read failed: {e}", - set(), - ) - return - - # Check total length before comparing - if len(verify_data) != len(image): - self._flash_state = "error" - self._verify_failed = True - self.app.call_from_thread( - self._show_verify_error, - f"Read-back size mismatch: expected {len(image)} bytes, " - f"got {len(verify_data)}", - set(), - ) - return - - # Byte-by-byte comparison - mismatches: set[int] = set() - for i in range(len(image)): - if image[i] != verify_data[i]: - mismatches.add(i) - - if not mismatches: - self._flash_state = "done" - self._verify_failed = False - self.app.call_from_thread(self._show_verify_success, bytes(verify_data)) - else: - self._flash_state = "error" - self._verify_failed = True - self.app.call_from_thread( - self._show_verify_error, - f"{len(mismatches)} byte(s) differ", - mismatches, - bytes(verify_data), - ) - - def _show_verify_success(self, data: bytes) -> None: - """Flash + verify succeeded.""" - if not self.is_mounted: - return - self.query_one("#dev-ee-hex", HexView).set_data(data) - self._set_ee_progress("Complete", 100) - self._set_ee_status( - f"[bold #00e060]\u2713 VERIFIED \u2014 all {len(data)} bytes match[/]\n" - f"[#506878]Power cycle the device to boot new firmware.[/]" - ) - self.query_one("#dev-ee-persistent-warn", Static).update("") - self.query_one("#dev-ee-flash", Button).disabled = False - - def _show_verify_error(self, detail: str, mismatches: set[int], - verify_data: bytes | None = None) -> None: - """Flash verify FAILED -- show persistent warning.""" - if not self.is_mounted: - return - if verify_data: - self.query_one("#dev-ee-hex", HexView).set_data( - verify_data, diff_offsets=mismatches, - ) - self._set_ee_progress("FAILED", 100) - self._set_ee_status( - f"[bold #e04040]\u2717 VERIFY FAILED \u2014 {detail}[/]" - ) - # Persistent, impossible-to-miss warning - self.query_one("#dev-ee-persistent-warn", Static).update( - "[bold #e04040 on #1a0000]" - "\u26a0\u26a0\u26a0 DO NOT POWER CYCLE \u26a0\u26a0\u26a0\n" - "EEPROM contents do not match the image.\n" - "Power cycling now may brick the device.\n" - "Resolve this before removing power." - "[/]" - ) - self.query_one("#dev-ee-flash", Button).disabled = False - - def _enable_flash_button(self) -> None: - if not self.is_mounted: - return - self.query_one("#dev-ee-flash", Button).disabled = False - - # --- EEPROM UI helpers --- - - def _set_ee_status(self, text: str) -> None: - if not self.is_mounted: - return - self.query_one("#dev-ee-status", Static).update(text) - - def _set_ee_progress(self, label: str, pct: float) -> None: - if not self.is_mounted: - return - self.query_one("#dev-ee-progress-label", Static).update( - f"[#506878]{label}[/]" - ) - self.query_one("#dev-ee-pbar", ProgressBar).update(progress=pct) - - # ========================================================================== - # Diagnostics tab operations - # ========================================================================== - - @work(thread=True) - def _diag_boot_test(self) -> None: - """Run boot diagnostic with selected mode.""" - try: - mode = self.app.call_from_thread( - lambda: self.query_one("#dev-diag-boot-mode", Select).value - ) - if mode is Select.BLANK: - mode = 0x80 - except Exception: - mode = 0x80 - - self.app.call_from_thread( - self._set_diag_boot_result, - f"[#506878]Running boot test mode 0x{mode:02X}...[/]", - ) - - try: - result = self._bridge.boot_debug(mode) - except Exception as e: - self.app.call_from_thread( - self._set_diag_boot_result, - f"[bold #e04040]Boot test failed:[/] [#e04040]{e}[/]", - ) - return - - stage = result.get("stage", 0) - res = result.get("result", 0) - detail = result.get("detail", 0) - - if res == 0x01: - status_str = "[bold #00e060]PASS[/]" - elif res == 0x00: - status_str = "[#e8a020]NO RESULT[/]" - else: - status_str = f"[bold #e04040]FAIL (0x{res:02X})[/]" - - self.app.call_from_thread( - self._set_diag_boot_result, - f"Stage: 0x{stage:02X} Result: {status_str} " - f"Detail: 0x{detail:02X}", - ) - - def _set_diag_boot_result(self, text: str) -> None: - if not self.is_mounted: - return - self.query_one("#dev-diag-boot-result", Static).update(text) - - @work(thread=True) - def _diag_i2c_scan(self) -> None: - """Scan I2C bus for responding devices.""" - self.app.call_from_thread( - self._set_diag_i2c_result, - "[#506878]Scanning I2C bus...[/]", - ) - - try: - addresses = self._bridge.i2c_bus_scan() - except Exception as e: - self.app.call_from_thread( - self._set_diag_i2c_result, - f"[bold #e04040]I2C scan failed:[/] [#e04040]{e}[/]", - ) - return - - if not addresses: - self.app.call_from_thread( - self._set_diag_i2c_result, - "[#e8a020]No devices found on I2C bus[/]", - ) - return - - # Known device identification - known = { - 0x08: "BCM4500 demodulator", - 0x10: "Tuner / LNB controller", - 0x51: "24Cxx EEPROM (config/serial)", - } - parts = [] - for addr in addresses: - label = known.get(addr, "") - if label: - parts.append( - f"[#00d4aa]0x{addr:02X}[/] [#506878]({label})[/]" - ) - else: - parts.append(f"[#00d4aa]0x{addr:02X}[/]") - - self.app.call_from_thread( - self._set_diag_i2c_result, - f"[#c8d0d8]Found {len(addresses)} device(s):[/] " - + " ".join(parts), - ) - - def _set_diag_i2c_result(self, text: str) -> None: - if not self.is_mounted: - return - self.query_one("#dev-diag-i2c-result", Static).update(text) - - @work(thread=True) - def _diag_reg_read(self) -> None: - """Read BCM4500 registers and display in hex view.""" - try: - start_str = self.app.call_from_thread( - lambda: self.query_one("#dev-diag-reg-start", Input).value - ) - count_str = self.app.call_from_thread( - lambda: self.query_one("#dev-diag-reg-count", Input).value - ) - start_reg = int(start_str, 0) - count = int(count_str, 0) - count = max(1, min(count, 256)) - except (ValueError, TypeError): - self.app.call_from_thread( - self._set_diag_i2c_result, - "[bold #e04040]Invalid register address or count[/]", - ) - return - - try: - # Read in batches of 64 (USB transfer limit) - data = bytearray() - remaining = count - reg = start_reg - while remaining > 0: - batch = min(64, remaining) - chunk = self._bridge.multi_reg_read(reg, batch) - data.extend(chunk) - reg += batch - remaining -= batch - except Exception as e: - self.app.call_from_thread( - self._set_diag_i2c_result, - f"[bold #e04040]Register read failed:[/] [#e04040]{e}[/]", - ) - return - - self.app.call_from_thread( - self._show_diag_regs, bytes(data), start_reg, count, - ) - - def _show_diag_regs(self, data: bytes, start_reg: int, count: int) -> None: - if not self.is_mounted: - return - self.query_one("#dev-diag-hex", HexView).set_data(data) +"""Device screen -- firmware management, EEPROM operations, and diagnostics. + +This is the most complex screen in the TUI. It provides three tabs: + + Firmware: FX2 RAM read/write, CPU halt/start + EEPROM: Read, backup, and flash (safety-critical!) C2 firmware images + Diagnostics: Boot stage tests, I2C bus scan, BCM4500 register dump + +The EEPROM flash operation follows a strict state machine to prevent +accidental writes to the boot EEPROM, which would brick the device: + + idle -> file_selected -> validated -> backup -> countdown -> writing -> verifying -> done + |-> invalid (red) |-> error (persistent) + +A corrupted EEPROM means no USB enumeration until an external programmer +is used. Every safety gate is mandatory and cannot be bypassed via the UI. +""" + +import os +import time +from datetime import datetime + +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, Vertical +from textual.widgets import ( + Label, Input, Button, Static, Select, ProgressBar, +) +from textual import work +from textual.worker import Worker + +from eeprom_write import parse_c2_header, parse_records + +from skywalker_tui.widgets.hex_view import HexView +from skywalker_tui.widgets.countdown_timer import CountdownTimer +from skywalker_tui.widgets.config_bits import ConfigBitsDisplay + + +# EEPROM constants (must match eeprom_write.py and skywalker_lib.py) +EEPROM_PAGE_SIZE = 16 +EEPROM_WRITE_CYCLE_MS = 10 +MAX_EEPROM_SIZE = 16384 +VENDOR_ID = 0x09C0 +PRODUCT_ID = 0x0203 + + +class DeviceScreen(Container): + """Firmware management, EEPROM flash, and hardware diagnostics.""" + + DEFAULT_CSS = """ + DeviceScreen { + layout: vertical; + } + + /* --- Identity panel (always visible) --- */ + DeviceScreen #dev-identity { + height: auto; + padding: 1 2; + background: #0e1420; + border-bottom: solid #1a2a3a; + } + DeviceScreen #dev-identity-title { + color: #00d4aa; + text-style: bold; + margin: 0 0 1 0; + } + DeviceScreen #dev-identity-info { + color: #c8d0d8; + } + DeviceScreen #dev-demo-badge { + color: #e8a020; + text-style: bold; + margin: 0 0 0 0; + } + + /* --- Tab row --- */ + DeviceScreen #dev-tab-row { + height: auto; + padding: 0 2; + background: #0e1018; + layout: horizontal; + } + DeviceScreen .dev-tab-btn { + margin: 0 1 0 0; + min-width: 16; + } + DeviceScreen .dev-tab-btn.-active { + background: #0a2a3a; + color: #00d4aa; + border: round #00d4aa; + text-style: bold; + } + + /* --- Tab content panels --- */ + DeviceScreen #dev-tab-content { + height: 1fr; + } + DeviceScreen .dev-tab-panel { + height: 1fr; + layout: vertical; + padding: 1 2; + } + + /* --- Firmware tab --- */ + DeviceScreen #dev-fw-controls { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + DeviceScreen #dev-fw-controls Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + DeviceScreen #dev-fw-controls Input { + width: 12; + margin: 0 1; + } + DeviceScreen #dev-fw-controls Button { + margin: 0 1; + } + DeviceScreen #dev-fw-cpu-controls { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + DeviceScreen #dev-fw-cpu-controls Button { + margin: 0 1; + } + DeviceScreen #dev-fw-status { + height: auto; + color: #506878; + margin: 0 0 1 0; + } + + /* --- EEPROM tab --- */ + DeviceScreen #dev-ee-read-row { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + DeviceScreen #dev-ee-read-row Button { + margin: 0 1; + } + DeviceScreen #dev-ee-flash-section { + height: auto; + background: #0e1018; + border: round #1a2a3a; + padding: 1; + margin: 0 0 1 0; + } + DeviceScreen #dev-ee-flash-title { + color: #e8a020; + text-style: bold; + margin: 0 0 1 0; + } + DeviceScreen #dev-ee-flash-controls { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + DeviceScreen #dev-ee-flash-controls Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + DeviceScreen #dev-ee-flash-controls Input { + width: 1fr; + margin: 0 1; + } + DeviceScreen #dev-ee-flash-controls Button { + margin: 0 1; + } + DeviceScreen #dev-ee-validation { + height: auto; + margin: 0 0 1 0; + } + DeviceScreen #dev-ee-progress-row { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + DeviceScreen #dev-ee-progress-label { + width: auto; + margin: 0 1 0 0; + color: #506878; + } + DeviceScreen #dev-ee-pbar { + width: 1fr; + } + DeviceScreen #dev-ee-status { + height: auto; + color: #506878; + margin: 0 0 1 0; + } + DeviceScreen #dev-ee-persistent-warn { + height: auto; + color: #e04040; + text-style: bold; + margin: 0 0 1 0; + } + + /* --- Diagnostics tab --- */ + DeviceScreen #dev-diag-boot-row { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + DeviceScreen #dev-diag-boot-row Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + DeviceScreen #dev-diag-boot-row Button { + margin: 0 1; + } + DeviceScreen #dev-diag-boot-result { + height: auto; + color: #c8d0d8; + margin: 0 0 1 0; + } + DeviceScreen #dev-diag-i2c-row { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + DeviceScreen #dev-diag-i2c-row Button { + margin: 0 1; + } + DeviceScreen #dev-diag-i2c-result { + height: auto; + color: #c8d0d8; + margin: 0 0 1 0; + } + DeviceScreen #dev-diag-reg-row { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + DeviceScreen #dev-diag-reg-row Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + DeviceScreen #dev-diag-reg-row Input { + width: 10; + margin: 0 1; + } + DeviceScreen #dev-diag-reg-row Button { + margin: 0 1; + } + """ + + # Boot diagnostic mode values + _BOOT_MODES = [ + ("0x80 No-op", 0x80), + ("0x81 GPIO init", 0x81), + ("0x82 I2C probe", 0x82), + ("0x83 BCM4500 reset", 0x83), + ("0x84 FW load", 0x84), + ("0x85 Full boot", 0x85), + ] + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._active_tab = "firmware" + self._workers: list[Worker] = [] + + # EEPROM flash state machine + self._flash_state = "idle" + self._flash_image: bytes = b'' + self._flash_header: dict | None = None + self._flash_records: list | None = None + self._eeprom_backup: bytes = b'' + self._verify_failed = False + + def compose(self) -> ComposeResult: + # Identity panel (always visible) + with Vertical(id="dev-identity"): + yield Static("[#00d4aa bold]Device Identity[/]", id="dev-identity-title") + if self._bridge.is_demo: + yield Static("[#e8a020 bold]DEMO MODE \u2014 no hardware writes[/]", + id="dev-demo-badge") + yield Static("[#506878]Loading...[/]", id="dev-identity-info") + yield ConfigBitsDisplay(id="dev-config-bits") + + # Tab buttons + with Horizontal(id="dev-tab-row"): + yield Button("Firmware", id="dev-tab-firmware", + classes="dev-tab-btn -active") + yield Button("EEPROM", id="dev-tab-eeprom", classes="dev-tab-btn") + yield Button("Diagnostics", id="dev-tab-diag", classes="dev-tab-btn") + + # Tab content area -- we toggle visibility manually + with Vertical(id="dev-tab-content"): + # === Firmware tab === + with Vertical(id="dev-panel-firmware", classes="dev-tab-panel"): + with Horizontal(id="dev-fw-controls"): + yield Label("Address (hex):") + yield Input("0x0000", id="dev-fw-addr") + yield Label("Length:") + yield Input("64", id="dev-fw-len") + yield Button("Read RAM", id="dev-fw-read", variant="primary") + with Horizontal(id="dev-fw-cpu-controls"): + yield Button("Halt CPU", id="dev-fw-halt", variant="error") + yield Button("Start CPU", id="dev-fw-start", variant="success") + yield Static("[#506878]Ready[/]", id="dev-fw-status") + yield HexView(id="dev-fw-hex") + + # === EEPROM tab === + with Vertical(id="dev-panel-eeprom", classes="dev-tab-panel"): + with Horizontal(id="dev-ee-read-row"): + yield Button("Read All", id="dev-ee-read", variant="primary") + yield Button("Backup to File", id="dev-ee-backup") + yield Static("", id="dev-ee-persistent-warn") + + # Flash section + with Vertical(id="dev-ee-flash-section"): + yield Static( + "[#e8a020 bold]Flash Firmware Image[/]", + id="dev-ee-flash-title", + ) + with Horizontal(id="dev-ee-flash-controls"): + yield Label("Image file:") + yield Input("", id="dev-ee-filepath", + placeholder="/path/to/firmware.bin") + yield Button("Validate", id="dev-ee-validate") + yield Button("Flash", id="dev-ee-flash", disabled=True, + variant="error") + yield Static("", id="dev-ee-validation") + + with Horizontal(id="dev-ee-progress-row"): + yield Static("[#506878]Idle[/]", id="dev-ee-progress-label") + yield ProgressBar(total=100, show_eta=False, id="dev-ee-pbar") + yield Static("[#506878]Ready[/]", id="dev-ee-status") + yield HexView(id="dev-ee-hex") + + # === Diagnostics tab === + with Vertical(id="dev-panel-diag", classes="dev-tab-panel"): + # Boot test + yield Static("[#00d4aa bold]Boot Diagnostic[/]") + with Horizontal(id="dev-diag-boot-row"): + yield Label("Mode:") + yield Select( + [(label, val) for label, val in self._BOOT_MODES], + value=0x80, + id="dev-diag-boot-mode", + ) + yield Button("Run", id="dev-diag-boot-run", variant="primary") + yield Static("", id="dev-diag-boot-result") + + # I2C scan + yield Static("[#00d4aa bold]I2C Bus Scan[/]") + with Horizontal(id="dev-diag-i2c-row"): + yield Button("Scan", id="dev-diag-i2c-scan", variant="primary") + yield Static("", id="dev-diag-i2c-result") + + # BCM4500 register dump + yield Static("[#00d4aa bold]BCM4500 Register Dump[/]") + with Horizontal(id="dev-diag-reg-row"): + yield Label("Start reg:") + yield Input("0x00", id="dev-diag-reg-start") + yield Label("Count:") + yield Input("32", id="dev-diag-reg-count") + yield Button("Read", id="dev-diag-reg-read", variant="primary") + yield HexView(id="dev-diag-hex") + + # --- Lifecycle --- + + def on_show(self) -> None: + """Read device identity when the screen becomes visible.""" + self._read_identity() + # Hide inactive tabs + self._apply_tab_visibility() + + def on_hide(self) -> None: + """Cancel all running workers when leaving the screen.""" + for w in self._workers: + try: + w.cancel() + except Exception: + pass + self._workers.clear() + + # --- Tab switching --- + + def _switch_tab(self, tab: str) -> None: + """Switch between firmware / eeprom / diag tabs.""" + self._active_tab = tab + # Update button highlights + for name in ("firmware", "eeprom", "diag"): + btn = self.query_one(f"#dev-tab-{name}", Button) + if name == tab: + btn.add_class("-active") + else: + btn.remove_class("-active") + self._apply_tab_visibility() + + def _apply_tab_visibility(self) -> None: + """Show/hide tab panels based on active tab.""" + tab_map = { + "firmware": "dev-panel-firmware", + "eeprom": "dev-panel-eeprom", + "diag": "dev-panel-diag", + } + for name, panel_id in tab_map.items(): + try: + panel = self.query_one(f"#{panel_id}") + panel.display = (name == self._active_tab) + except Exception: + pass + + # --- Button dispatch --- + + def on_button_pressed(self, event: Button.Pressed) -> None: + btn_id = event.button.id or "" + + # Tab buttons + if btn_id == "dev-tab-firmware": + self._switch_tab("firmware") + elif btn_id == "dev-tab-eeprom": + self._switch_tab("eeprom") + elif btn_id == "dev-tab-diag": + self._switch_tab("diag") + + # Firmware tab + elif btn_id == "dev-fw-read": + self._fw_ram_read() + elif btn_id == "dev-fw-halt": + self._fw_cpu_halt() + elif btn_id == "dev-fw-start": + self._fw_cpu_start() + + # EEPROM tab + elif btn_id == "dev-ee-read": + self._ee_read_all() + elif btn_id == "dev-ee-backup": + self._ee_backup() + elif btn_id == "dev-ee-validate": + self._ee_validate_file() + elif btn_id == "dev-ee-flash": + self._ee_begin_flash() + + # Diagnostics tab + elif btn_id == "dev-diag-boot-run": + self._diag_boot_test() + elif btn_id == "dev-diag-i2c-scan": + self._diag_i2c_scan() + elif btn_id == "dev-diag-reg-read": + self._diag_reg_read() + + # --- Countdown timer messages --- + + def on_countdown_timer_completed(self, _msg: CountdownTimer.Completed) -> None: + """Countdown finished -- proceed with EEPROM write.""" + self._remove_countdown() + self._ee_do_write() + + def on_countdown_timer_aborted(self, _msg: CountdownTimer.Aborted) -> None: + """Operator aborted the countdown.""" + self._remove_countdown() + self._flash_state = "idle" + self._set_ee_status("[#e8a020]Flash aborted by operator[/]") + self.query_one("#dev-ee-flash", Button).disabled = False + + def _remove_countdown(self) -> None: + """Remove the countdown widget from the flash section.""" + try: + timer = self.query_one(CountdownTimer) + timer.remove() + except Exception: + pass + + # --- Identity panel --- + + @work(thread=True) + def _read_identity(self) -> None: + """Read device info in a background thread and update the identity panel.""" + try: + fw = self._bridge.get_fw_version() + serial = self._bridge.get_serial_number() + speed = self._bridge.get_usb_speed() + config = self._bridge.get_config() + vendor = self._bridge.get_vendor_string() + product = self._bridge.get_product_string() + except Exception as e: + self.app.call_from_thread( + self._update_identity_error, str(e) + ) + return + + speed_str = {0: "Unknown", 1: "Full (12 Mbps)", 2: "High (480 Mbps)"}.get( + speed, f"0x{speed:02X}" + ) + serial_hex = serial.hex(' ') if isinstance(serial, (bytes, bytearray)) else str(serial) + fw_str = fw.get("version", "?") + fw_date = fw.get("date", "?") + + info_lines = [ + f"[#506878]Firmware:[/] [#c8d0d8]{fw_str}[/] [#506878]({fw_date})[/]", + f"[#506878]Serial:[/] [#c8d0d8]{serial_hex}[/]", + f"[#506878]USB:[/] [#c8d0d8]{speed_str}[/]", + f"[#506878]Vendor:[/] [#c8d0d8]{vendor}[/]", + f"[#506878]Product:[/] [#c8d0d8]{product}[/]", + ] + self.app.call_from_thread(self._update_identity, "\n".join(info_lines), config) + + def _update_identity(self, info_text: str, config: int) -> None: + if not self.is_mounted: + return + self.query_one("#dev-identity-info", Static).update(info_text) + self.query_one("#dev-config-bits", ConfigBitsDisplay).update_config(config) + + def _update_identity_error(self, error: str) -> None: + if not self.is_mounted: + return + self.query_one("#dev-identity-info", Static).update( + f"[bold #e04040]Error reading device info:[/] [#e04040]{error}[/]" + ) + + # ========================================================================== + # Firmware tab operations + # ========================================================================== + + @work(thread=True) + def _fw_ram_read(self) -> None: + """Read FX2 RAM region and display in hex view.""" + try: + addr_str = self.app.call_from_thread( + lambda: self.query_one("#dev-fw-addr", Input).value + ) + len_str = self.app.call_from_thread( + lambda: self.query_one("#dev-fw-len", Input).value + ) + addr = int(addr_str, 0) + length = int(len_str, 0) + length = max(1, min(length, 4096)) + except (ValueError, TypeError): + self.app.call_from_thread( + self._set_fw_status, "[bold #e04040]Invalid address or length[/]" + ) + return + + self.app.call_from_thread( + self._set_fw_status, f"[#506878]Reading 0x{addr:04X}...[/]" + ) + + try: + # Read in chunks of 64 bytes (USB control transfer limit) + data = bytearray() + remaining = length + offset = addr + while remaining > 0: + chunk_size = min(64, remaining) + chunk = self._bridge.fx2_ram_read(offset, chunk_size) + data.extend(chunk) + offset += chunk_size + remaining -= chunk_size + except Exception as e: + self.app.call_from_thread( + self._set_fw_status, + f"[bold #e04040]RAM read failed:[/] [#e04040]{e}[/]", + ) + return + + self.app.call_from_thread(self._show_fw_data, bytes(data), addr, length) + + def _show_fw_data(self, data: bytes, addr: int, length: int) -> None: + if not self.is_mounted: + return + self.query_one("#dev-fw-hex", HexView).set_data(data) + self._set_fw_status( + f"[#00d4aa]Read {len(data)} bytes from 0x{addr:04X}[/]" + ) + + @work(thread=True) + def _fw_cpu_halt(self) -> None: + self.app.call_from_thread( + self._set_fw_status, "[#e8a020]Halting FX2 CPU...[/]" + ) + try: + self._bridge.fx2_cpu_halt() + self.app.call_from_thread( + self._set_fw_status, "[#e04040]CPU halted (CPUCS=0x01)[/]" + ) + except Exception as e: + self.app.call_from_thread( + self._set_fw_status, + f"[bold #e04040]Halt failed:[/] [#e04040]{e}[/]", + ) + + @work(thread=True) + def _fw_cpu_start(self) -> None: + self.app.call_from_thread( + self._set_fw_status, "[#e8a020]Starting FX2 CPU...[/]" + ) + try: + self._bridge.fx2_cpu_start() + self.app.call_from_thread( + self._set_fw_status, "[#00e060]CPU started (CPUCS=0x00)[/]" + ) + except Exception as e: + self.app.call_from_thread( + self._set_fw_status, + f"[bold #e04040]Start failed:[/] [#e04040]{e}[/]", + ) + + def _set_fw_status(self, text: str) -> None: + if not self.is_mounted: + return + self.query_one("#dev-fw-status", Static).update(text) + + # ========================================================================== + # EEPROM tab operations + # ========================================================================== + + @work(thread=True) + def _ee_read_all(self) -> None: + """Read entire EEPROM and display in hex view.""" + self.app.call_from_thread( + self._set_ee_status, "[#506878]Reading EEPROM (16 KB)...[/]" + ) + self.app.call_from_thread(self._set_ee_progress, "Reading...", 0) + + try: + data = bytearray() + chunk_size = 64 + total = MAX_EEPROM_SIZE + for offset in range(0, total, chunk_size): + remaining = min(chunk_size, total - offset) + chunk = self._bridge.eeprom_read(offset, remaining) + data.extend(chunk) + pct = (offset + remaining) / total * 100 + self.app.call_from_thread(self._set_ee_progress, "Reading...", pct) + except Exception as e: + self.app.call_from_thread( + self._set_ee_status, + f"[bold #e04040]EEPROM read failed:[/] [#e04040]{e}[/]", + ) + return + + self.app.call_from_thread(self._show_ee_data, bytes(data)) + + def _show_ee_data(self, data: bytes) -> None: + if not self.is_mounted: + return + self.query_one("#dev-ee-hex", HexView).set_data(data) + self._set_ee_progress("Complete", 100) + # Parse and display C2 header info + header = parse_c2_header(data) + if header: + self._set_ee_status( + f"[#00d4aa]Read {len(data)} bytes[/] \u2014 " + f"C2 VID=0x{header['vid']:04X} PID=0x{header['pid']:04X} " + f"Config=0x{header['config']:02X}" + ) + else: + self._set_ee_status( + f"[#e8a020]Read {len(data)} bytes \u2014 not a valid C2 image[/]" + ) + + @work(thread=True) + def _ee_backup(self) -> None: + """Read EEPROM and save to timestamped .bin file.""" + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"skywalker1_eeprom_{ts}.bin" + + self.app.call_from_thread( + self._set_ee_status, f"[#506878]Backing up to {filename}...[/]" + ) + self.app.call_from_thread(self._set_ee_progress, "Backup...", 0) + + try: + data = bytearray() + chunk_size = 64 + total = MAX_EEPROM_SIZE + for offset in range(0, total, chunk_size): + remaining = min(chunk_size, total - offset) + chunk = self._bridge.eeprom_read(offset, remaining) + data.extend(chunk) + pct = (offset + remaining) / total * 100 + self.app.call_from_thread(self._set_ee_progress, "Backup...", pct) + + with open(filename, 'wb') as f: + f.write(data) + except Exception as e: + self.app.call_from_thread( + self._set_ee_status, + f"[bold #e04040]Backup failed:[/] [#e04040]{e}[/]", + ) + return + + abs_path = os.path.abspath(filename) + self.app.call_from_thread( + self._set_ee_status, + f"[#00d4aa]Backup saved:[/] [#c8d0d8]{abs_path}[/] " + f"[#506878]({len(data)} bytes)[/]", + ) + self.app.call_from_thread(self._set_ee_progress, "Complete", 100) + + # --- C2 image validation --- + + def _ee_validate_file(self) -> None: + """Validate the selected C2 firmware image file.""" + # Block re-entry during active flash operations + if self._flash_state in ("backup", "countdown", "writing", "verifying"): + return + filepath = self.query_one("#dev-ee-filepath", Input).value.strip() + validation = self.query_one("#dev-ee-validation", Static) + flash_btn = self.query_one("#dev-ee-flash", Button) + + if not filepath: + validation.update("[bold #e04040]No file path entered[/]") + flash_btn.disabled = True + self._flash_state = "idle" + return + + if not os.path.exists(filepath): + validation.update(f"[bold #e04040]File not found:[/] [#e04040]{filepath}[/]") + flash_btn.disabled = True + self._flash_state = "idle" + return + + try: + with open(filepath, 'rb') as f: + image = f.read() + except OSError as e: + validation.update(f"[bold #e04040]Cannot read file:[/] [#e04040]{e}[/]") + flash_btn.disabled = True + self._flash_state = "idle" + return + + self._flash_state = "file_selected" + + # Run all C2 validation checks + errors = [] + warnings = [] + + # Size checks + if len(image) > MAX_EEPROM_SIZE: + errors.append( + f"Image too large: {len(image)} bytes (max {MAX_EEPROM_SIZE})" + ) + if len(image) < 12: + errors.append(f"Image too small: {len(image)} bytes (need >= 12)") + + # Magic byte + if not image or image[0] != 0xC2: + errors.append( + f"Not a C2 image (first byte: 0x{image[0]:02X}, expected 0xC2)" + if image else "Empty file" + ) + + if errors: + self._flash_state = "invalid" + flash_btn.disabled = True + err_text = "\n".join(f"[bold #e04040]\u2717 {e}[/]" for e in errors) + validation.update(err_text) + return + + # Parse C2 header + header = parse_c2_header(image) + if header is None: + self._flash_state = "invalid" + flash_btn.disabled = True + validation.update("[bold #e04040]\u2717 Failed to parse C2 header[/]") + return + + # VID/PID match + if header["vid"] != VENDOR_ID: + errors.append( + f"VID mismatch: image 0x{header['vid']:04X}, " + f"expected 0x{VENDOR_ID:04X}" + ) + if header["pid"] != PRODUCT_ID: + errors.append( + f"PID mismatch: image 0x{header['pid']:04X}, " + f"expected 0x{PRODUCT_ID:04X}" + ) + + # Parse records and check for END marker + records = parse_records(image) + end_recs = [r for r in records if r["type"] == "end"] + invalid_recs = [r for r in records if r["type"] == "invalid"] + + if not end_recs: + errors.append("No END marker (0x8001) found in image") + if invalid_recs: + warnings.append(f"{len(invalid_recs)} invalid record(s) in image") + + if errors: + self._flash_state = "invalid" + flash_btn.disabled = True + lines = [f"[bold #e04040]\u2717 {e}[/]" for e in errors] + lines += [f"[#e8a020]\u26a0 {w}[/]" for w in warnings] + validation.update("\n".join(lines)) + return + + # All checks passed + self._flash_state = "validated" + self._flash_image = image + self._flash_header = header + self._flash_records = records + + data_recs = [r for r in records if r["type"] == "data"] + total_code = sum(r["length"] for r in data_recs) + entry = end_recs[0]["entry_point"] if end_recs else 0 + + lines = [ + "[bold #00e060]\u2713 Valid C2 image[/]", + f"[#506878]Size:[/] [#c8d0d8]{len(image)} bytes[/]", + f"[#506878]VID:[/] [#c8d0d8]0x{header['vid']:04X}[/] " + f"[#506878]PID:[/] [#c8d0d8]0x{header['pid']:04X}[/]", + f"[#506878]Code:[/] [#c8d0d8]{total_code} bytes " + f"in {len(data_recs)} segment(s)[/]", + f"[#506878]Entry:[/] [#c8d0d8]0x{entry:04X}[/]", + ] + if warnings: + lines += [f"[#e8a020]\u26a0 {w}[/]" for w in warnings] + + validation.update("\n".join(lines)) + flash_btn.disabled = False + + # --- Flash state machine --- + + def _ee_begin_flash(self) -> None: + """Start the flash sequence: backup -> countdown -> write -> verify.""" + if self._flash_state != "validated": + return + + # In demo mode, show the full flow but skip actual writes + if self._bridge.is_demo: + self._set_ee_status( + "[#e8a020 bold]DEMO MODE \u2014 simulating flash " + "(no hardware writes)[/]" + ) + + self.query_one("#dev-ee-flash", Button).disabled = True + self._flash_state = "backup" + self._ee_flash_backup() + + @work(thread=True) + def _ee_flash_backup(self) -> None: + """Pre-flash backup of current EEPROM contents.""" + self.app.call_from_thread( + self._set_ee_status, "[#506878]Pre-flash backup...[/]" + ) + self.app.call_from_thread(self._set_ee_progress, "Backup...", 0) + + try: + data = bytearray() + chunk_size = 64 + total = MAX_EEPROM_SIZE + for offset in range(0, total, chunk_size): + remaining = min(chunk_size, total - offset) + chunk = self._bridge.eeprom_read(offset, remaining) + data.extend(chunk) + pct = (offset + remaining) / total * 100 + self.app.call_from_thread(self._set_ee_progress, "Backup...", pct) + + self._eeprom_backup = bytes(data) + + # Save backup to file + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_file = f"eeprom_preflash_{ts}.bin" + with open(backup_file, 'wb') as f: + f.write(data) + + abs_path = os.path.abspath(backup_file) + self.app.call_from_thread( + self._set_ee_status, + f"[#00d4aa]Pre-flash backup saved:[/] [#c8d0d8]{abs_path}[/]", + ) + except Exception as e: + self.app.call_from_thread( + self._set_ee_status, + f"[bold #e04040]Backup failed \u2014 flash aborted:[/] " + f"[#e04040]{e}[/]", + ) + self._flash_state = "idle" + self.app.call_from_thread(self._enable_flash_button) + return + + self._flash_state = "countdown" + self.app.call_from_thread(self._show_countdown) + + def _show_countdown(self) -> None: + """Mount the countdown timer widget into the flash section.""" + if not self.is_mounted: + return + flash_section = self.query_one("#dev-ee-flash-section") + timer = CountdownTimer(id="dev-ee-countdown") + flash_section.mount(timer) + timer.start() + + def _ee_do_write(self) -> None: + """Called when countdown completes -- start the actual write.""" + self._flash_state = "writing" + w = self._ee_write_and_verify() + self._workers.append(w) + + @work(thread=True) + def _ee_write_and_verify(self) -> None: + """Write the firmware image to EEPROM, then verify.""" + image = self._flash_image + total_pages = (len(image) + EEPROM_PAGE_SIZE - 1) // EEPROM_PAGE_SIZE + write_errors = 0 + consecutive_errors = 0 + write_aborted = False + max_consecutive_errors = 3 + + self.app.call_from_thread( + self._set_ee_status, + f"[#e8a020 bold]Writing {len(image)} bytes " + f"({total_pages} pages)...[/]", + ) + + for page_num in range(total_pages): + offset = page_num * EEPROM_PAGE_SIZE + end = min(offset + EEPROM_PAGE_SIZE, len(image)) + chunk = image[offset:end] + pct = (page_num + 1) / total_pages * 100 + + self.app.call_from_thread( + self._set_ee_progress, + f"Writing 0x{offset:04X}...", pct, + ) + + try: + written = self._bridge.eeprom_write_page(offset, chunk) + if written != len(chunk): + write_errors += 1 + consecutive_errors += 1 + else: + consecutive_errors = 0 + except Exception: + write_errors += 1 + consecutive_errors += 1 + + if consecutive_errors >= max_consecutive_errors: + write_aborted = True + self.app.call_from_thread( + self._set_ee_status, + f"[bold #e04040]WRITE ABORTED after " + f"{consecutive_errors} consecutive errors " + f"at 0x{offset:04X}[/]", + ) + break + + # Wait for EEPROM internal write cycle + time.sleep(EEPROM_WRITE_CYCLE_MS / 1000.0) + + if write_aborted: + self._flash_state = "error" + self._verify_failed = True + self.app.call_from_thread( + self._show_verify_error, + f"Write aborted at page {page_num}/{total_pages} " + f"({write_errors} total errors)", + set(), + ) + return + + if write_errors: + self.app.call_from_thread( + self._set_ee_status, + f"[bold #e04040]Write completed with {write_errors} error(s)[/]", + ) + + # --- Verify phase --- + self._flash_state = "verifying" + self.app.call_from_thread( + self._set_ee_status, + f"[#506878]Verifying {len(image)} bytes...[/]", + ) + + try: + verify_data = bytearray() + chunk_size = 64 + for offset in range(0, len(image), chunk_size): + remaining = min(chunk_size, len(image) - offset) + chunk = self._bridge.eeprom_read(offset, remaining) + verify_data.extend(chunk) + pct = (offset + remaining) / len(image) * 100 + self.app.call_from_thread( + self._set_ee_progress, "Verifying...", pct, + ) + except Exception as e: + self._flash_state = "error" + self._verify_failed = True + self.app.call_from_thread( + self._show_verify_error, + f"Verify read failed: {e}", + set(), + ) + return + + # Check total length before comparing + if len(verify_data) != len(image): + self._flash_state = "error" + self._verify_failed = True + self.app.call_from_thread( + self._show_verify_error, + f"Read-back size mismatch: expected {len(image)} bytes, " + f"got {len(verify_data)}", + set(), + ) + return + + # Byte-by-byte comparison + mismatches: set[int] = set() + for i in range(len(image)): + if image[i] != verify_data[i]: + mismatches.add(i) + + if not mismatches: + self._flash_state = "done" + self._verify_failed = False + self.app.call_from_thread(self._show_verify_success, bytes(verify_data)) + else: + self._flash_state = "error" + self._verify_failed = True + self.app.call_from_thread( + self._show_verify_error, + f"{len(mismatches)} byte(s) differ", + mismatches, + bytes(verify_data), + ) + + def _show_verify_success(self, data: bytes) -> None: + """Flash + verify succeeded.""" + if not self.is_mounted: + return + self.query_one("#dev-ee-hex", HexView).set_data(data) + self._set_ee_progress("Complete", 100) + self._set_ee_status( + f"[bold #00e060]\u2713 VERIFIED \u2014 all {len(data)} bytes match[/]\n" + f"[#506878]Power cycle the device to boot new firmware.[/]" + ) + self.query_one("#dev-ee-persistent-warn", Static).update("") + self.query_one("#dev-ee-flash", Button).disabled = False + + def _show_verify_error(self, detail: str, mismatches: set[int], + verify_data: bytes | None = None) -> None: + """Flash verify FAILED -- show persistent warning.""" + if not self.is_mounted: + return + if verify_data: + self.query_one("#dev-ee-hex", HexView).set_data( + verify_data, diff_offsets=mismatches, + ) + self._set_ee_progress("FAILED", 100) + self._set_ee_status( + f"[bold #e04040]\u2717 VERIFY FAILED \u2014 {detail}[/]" + ) + # Persistent, impossible-to-miss warning + self.query_one("#dev-ee-persistent-warn", Static).update( + "[bold #e04040 on #1a0000]" + "\u26a0\u26a0\u26a0 DO NOT POWER CYCLE \u26a0\u26a0\u26a0\n" + "EEPROM contents do not match the image.\n" + "Power cycling now may brick the device.\n" + "Resolve this before removing power." + "[/]" + ) + self.query_one("#dev-ee-flash", Button).disabled = False + + def _enable_flash_button(self) -> None: + if not self.is_mounted: + return + self.query_one("#dev-ee-flash", Button).disabled = False + + # --- EEPROM UI helpers --- + + def _set_ee_status(self, text: str) -> None: + if not self.is_mounted: + return + self.query_one("#dev-ee-status", Static).update(text) + + def _set_ee_progress(self, label: str, pct: float) -> None: + if not self.is_mounted: + return + self.query_one("#dev-ee-progress-label", Static).update( + f"[#506878]{label}[/]" + ) + self.query_one("#dev-ee-pbar", ProgressBar).update(progress=pct) + + # ========================================================================== + # Diagnostics tab operations + # ========================================================================== + + @work(thread=True) + def _diag_boot_test(self) -> None: + """Run boot diagnostic with selected mode.""" + try: + mode = self.app.call_from_thread( + lambda: self.query_one("#dev-diag-boot-mode", Select).value + ) + if mode is Select.BLANK: + mode = 0x80 + except Exception: + mode = 0x80 + + self.app.call_from_thread( + self._set_diag_boot_result, + f"[#506878]Running boot test mode 0x{mode:02X}...[/]", + ) + + try: + result = self._bridge.boot_debug(mode) + except Exception as e: + self.app.call_from_thread( + self._set_diag_boot_result, + f"[bold #e04040]Boot test failed:[/] [#e04040]{e}[/]", + ) + return + + stage = result.get("stage", 0) + res = result.get("result", 0) + detail = result.get("detail", 0) + + if res == 0x01: + status_str = "[bold #00e060]PASS[/]" + elif res == 0x00: + status_str = "[#e8a020]NO RESULT[/]" + else: + status_str = f"[bold #e04040]FAIL (0x{res:02X})[/]" + + self.app.call_from_thread( + self._set_diag_boot_result, + f"Stage: 0x{stage:02X} Result: {status_str} " + f"Detail: 0x{detail:02X}", + ) + + def _set_diag_boot_result(self, text: str) -> None: + if not self.is_mounted: + return + self.query_one("#dev-diag-boot-result", Static).update(text) + + @work(thread=True) + def _diag_i2c_scan(self) -> None: + """Scan I2C bus for responding devices.""" + self.app.call_from_thread( + self._set_diag_i2c_result, + "[#506878]Scanning I2C bus...[/]", + ) + + try: + addresses = self._bridge.i2c_bus_scan() + except Exception as e: + self.app.call_from_thread( + self._set_diag_i2c_result, + f"[bold #e04040]I2C scan failed:[/] [#e04040]{e}[/]", + ) + return + + if not addresses: + self.app.call_from_thread( + self._set_diag_i2c_result, + "[#e8a020]No devices found on I2C bus[/]", + ) + return + + # Known device identification + known = { + 0x08: "BCM4500 demodulator", + 0x10: "Tuner / LNB controller", + 0x51: "24Cxx EEPROM (config/serial)", + } + parts = [] + for addr in addresses: + label = known.get(addr, "") + if label: + parts.append( + f"[#00d4aa]0x{addr:02X}[/] [#506878]({label})[/]" + ) + else: + parts.append(f"[#00d4aa]0x{addr:02X}[/]") + + self.app.call_from_thread( + self._set_diag_i2c_result, + f"[#c8d0d8]Found {len(addresses)} device(s):[/] " + + " ".join(parts), + ) + + def _set_diag_i2c_result(self, text: str) -> None: + if not self.is_mounted: + return + self.query_one("#dev-diag-i2c-result", Static).update(text) + + @work(thread=True) + def _diag_reg_read(self) -> None: + """Read BCM4500 registers and display in hex view.""" + try: + start_str = self.app.call_from_thread( + lambda: self.query_one("#dev-diag-reg-start", Input).value + ) + count_str = self.app.call_from_thread( + lambda: self.query_one("#dev-diag-reg-count", Input).value + ) + start_reg = int(start_str, 0) + count = int(count_str, 0) + count = max(1, min(count, 256)) + except (ValueError, TypeError): + self.app.call_from_thread( + self._set_diag_i2c_result, + "[bold #e04040]Invalid register address or count[/]", + ) + return + + try: + # Read in batches of 64 (USB transfer limit) + data = bytearray() + remaining = count + reg = start_reg + while remaining > 0: + batch = min(64, remaining) + chunk = self._bridge.multi_reg_read(reg, batch) + data.extend(chunk) + reg += batch + remaining -= batch + except Exception as e: + self.app.call_from_thread( + self._set_diag_i2c_result, + f"[bold #e04040]Register read failed:[/] [#e04040]{e}[/]", + ) + return + + self.app.call_from_thread( + self._show_diag_regs, bytes(data), start_reg, count, + ) + + def _show_diag_regs(self, data: bytes, start_reg: int, count: int) -> None: + if not self.is_mounted: + return + self.query_one("#dev-diag-hex", HexView).set_data(data) diff --git a/tui/src/skywalker_tui/screens/lband.py b/tui/src/skywalker_tui/screens/lband.py index 22d9ba4..5e60ec1 100644 --- a/tui/src/skywalker_tui/screens/lband.py +++ b/tui/src/skywalker_tui/screens/lband.py @@ -1,215 +1,215 @@ -"""L-Band screen — direct input spectrum analyzer with allocation annotations. - -Same sweep mechanics as the spectrum screen, but with LNB disabled (direct input) -and band allocation overlays showing what service each frequency range belongs to. -""" - -from textual.app import ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import Label, Input, Button, Static, ProgressBar -from textual import work -from textual.worker import Worker - -from skywalker_lib import LBAND_ALLOCATIONS - -from skywalker_tui.widgets.spectrum_plot import SpectrumPlot -from skywalker_tui.widgets.waterfall import WaterfallDisplay - - -def _alloc_table(start: float, stop: float) -> str: - """Build a Rich-markup allocation reference for the visible range.""" - lines = ["[#00d4aa bold]L-Band Allocations in range:[/]"] - colors = ["#60a0c0", "#80b060", "#c0a050", "#a06080", "#50a0a0", "#a08060", "#6080a0"] - for i, (lo, hi, name) in enumerate(LBAND_ALLOCATIONS): - if lo < stop and hi > start: - overlap_lo = max(lo, start) - overlap_hi = min(hi, stop) - c = colors[i % len(colors)] - lines.append(f" [{c}]{overlap_lo:.0f}-{overlap_hi:.0f} MHz {name}[/]") - if len(lines) == 1: - lines.append(" [#506878](none in range)[/]") - return "\n".join(lines) - - -class LBandScreen(Container): - """L-band direct input analyzer with allocation annotations.""" - - DEFAULT_CSS = """ - LBandScreen { - layout: vertical; - } - LBandScreen #lband-main { - height: 1fr; - layout: horizontal; - } - LBandScreen #lband-plot-col { - width: 2fr; - layout: vertical; - } - LBandScreen #lband-info-col { - width: 1fr; - padding: 1; - background: #0e1420; - border-left: solid #1a2a3a; - layout: vertical; - } - LBandScreen #lband-alloc-panel { - height: auto; - padding: 1; - } - LBandScreen #lband-progress { - height: 3; - layout: horizontal; - padding: 0 2; - background: #0e1018; - } - LBandScreen #lband-progress Static { - width: auto; - margin: 1 1 0 0; - } - LBandScreen #lband-progress ProgressBar { - width: 1fr; - margin: 1 1 0 0; - } - LBandScreen #lband-controls { - height: auto; - padding: 1 2; - background: #0e1018; - border-top: solid #1a2a3a; - layout: horizontal; - } - LBandScreen #lband-controls Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - LBandScreen #lband-controls Input { - width: 10; - margin: 0 1; - } - LBandScreen #lband-controls Button { - margin: 0 1; - } - """ - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._sweeping = False - self._sweep_worker: Worker | None = None - - def compose(self) -> ComposeResult: - with Horizontal(id="lband-main"): - with Vertical(id="lband-plot-col"): - yield SpectrumPlot(title="L-Band Spectrum (Direct Input)", - id="lband-plot") - yield WaterfallDisplay(title="Waterfall", id="lband-waterfall") - with Vertical(id="lband-info-col"): - yield Static(_alloc_table(950, 2150), id="lband-alloc-panel") - - with Horizontal(id="lband-progress"): - yield Static("[#506878]Ready[/]", id="lband-status") - yield ProgressBar(total=100, show_eta=False, id="lband-pbar") - - with Horizontal(id="lband-controls"): - yield Label("Start:") - yield Input("950", id="lband-start") - yield Label("Stop:") - yield Input("2150", id="lband-stop") - yield Label("Step:") - yield Input("2", id="lband-step") - yield Label("Dwell:") - yield Input("20", id="lband-dwell") - yield Button("23cm", id="lband-23cm-btn") - yield Button("Sweep", id="lband-sweep-btn", variant="success") - yield Button("Stop", id="lband-stop-btn", variant="error") - - def on_show(self) -> None: - if self._bridge.is_demo and not self._sweeping: - self._start_sweep() - - def on_hide(self) -> None: - self._stop_sweep() - - def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "lband-sweep-btn": - self._start_sweep() - elif event.button.id == "lband-stop-btn": - self._stop_sweep() - elif event.button.id == "lband-23cm-btn": - self.query_one("#lband-start", Input).value = "1240" - self.query_one("#lband-stop", Input).value = "1300" - self.query_one("#lband-step", Input).value = "0.5" - # Update allocation display - self.query_one("#lband-alloc-panel", Static).update( - _alloc_table(1240, 1300) - ) - - def _start_sweep(self) -> None: - if self._sweeping: - return - self._sweeping = True - - start = float(self.query_one("#lband-start", Input).value or "950") - stop = float(self.query_one("#lband-stop", Input).value or "2150") - step = float(self.query_one("#lband-step", Input).value or "2") - dwell = int(self.query_one("#lband-dwell", Input).value or "20") - - # Update allocation panel for current range - self.query_one("#lband-alloc-panel", Static).update( - _alloc_table(start, stop) - ) - - self._sweep_worker = self._do_sweep(start, stop, step, dwell) - - def _stop_sweep(self) -> None: - self._sweeping = False - if self._sweep_worker: - self._sweep_worker.cancel() - self._sweep_worker = None - - @work(thread=True) - def _do_sweep(self, start: float, stop: float, step: float, dwell: int) -> None: - """L-band sweep with LNB disabled.""" - try: - self._bridge.ensure_booted() - # Disable LNB for direct input - self._bridge.configure_lnb(disable_lnb=True) - except Exception: - pass - - def progress_cb(freq, step_num, total, result): - pct = (step_num + 1) / total * 100 - self.app.call_from_thread(self._update_progress, pct, freq) - - self.app.call_from_thread(self._set_status, "Sweeping...") - - freqs, powers, results = self._bridge.sweep_spectrum( - start, stop, step, dwell, sr_ksps=20000, callback=progress_cb, - ) - - self.app.call_from_thread(self._show_results, freqs, powers, results) - self._sweeping = False - - def _update_progress(self, pct: float, freq: float) -> None: - if not self.is_mounted: - return - self.query_one("#lband-pbar", ProgressBar).update(progress=pct) - self.query_one("#lband-status", Static).update( - f"[#00d4aa]{freq:.1f} MHz[/]" - ) - - def _set_status(self, msg: str) -> None: - if not self.is_mounted: - return - self.query_one("#lband-status", Static).update(f"[#506878]{msg}[/]") - - def _show_results(self, freqs, powers, results) -> None: - if not self.is_mounted: - return - self.query_one("#lband-plot", SpectrumPlot).update_data( - freqs, powers, results, lnb_lo=0.0, - ) - self.query_one("#lband-waterfall", WaterfallDisplay).add_sweep(powers) - self.query_one("#lband-status", Static).update("[#506878]Complete[/]") - self.query_one("#lband-pbar", ProgressBar).update(progress=100) +"""L-Band screen — direct input spectrum analyzer with allocation annotations. + +Same sweep mechanics as the spectrum screen, but with LNB disabled (direct input) +and band allocation overlays showing what service each frequency range belongs to. +""" + +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, Vertical +from textual.widgets import Label, Input, Button, Static, ProgressBar +from textual import work +from textual.worker import Worker + +from skywalker_lib import LBAND_ALLOCATIONS + +from skywalker_tui.widgets.spectrum_plot import SpectrumPlot +from skywalker_tui.widgets.waterfall import WaterfallDisplay + + +def _alloc_table(start: float, stop: float) -> str: + """Build a Rich-markup allocation reference for the visible range.""" + lines = ["[#00d4aa bold]L-Band Allocations in range:[/]"] + colors = ["#60a0c0", "#80b060", "#c0a050", "#a06080", "#50a0a0", "#a08060", "#6080a0"] + for i, (lo, hi, name) in enumerate(LBAND_ALLOCATIONS): + if lo < stop and hi > start: + overlap_lo = max(lo, start) + overlap_hi = min(hi, stop) + c = colors[i % len(colors)] + lines.append(f" [{c}]{overlap_lo:.0f}-{overlap_hi:.0f} MHz {name}[/]") + if len(lines) == 1: + lines.append(" [#506878](none in range)[/]") + return "\n".join(lines) + + +class LBandScreen(Container): + """L-band direct input analyzer with allocation annotations.""" + + DEFAULT_CSS = """ + LBandScreen { + layout: vertical; + } + LBandScreen #lband-main { + height: 1fr; + layout: horizontal; + } + LBandScreen #lband-plot-col { + width: 2fr; + layout: vertical; + } + LBandScreen #lband-info-col { + width: 1fr; + padding: 1; + background: #0e1420; + border-left: solid #1a2a3a; + layout: vertical; + } + LBandScreen #lband-alloc-panel { + height: auto; + padding: 1; + } + LBandScreen #lband-progress { + height: 3; + layout: horizontal; + padding: 0 2; + background: #0e1018; + } + LBandScreen #lband-progress Static { + width: auto; + margin: 1 1 0 0; + } + LBandScreen #lband-progress ProgressBar { + width: 1fr; + margin: 1 1 0 0; + } + LBandScreen #lband-controls { + height: auto; + padding: 1 2; + background: #0e1018; + border-top: solid #1a2a3a; + layout: horizontal; + } + LBandScreen #lband-controls Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + LBandScreen #lband-controls Input { + width: 10; + margin: 0 1; + } + LBandScreen #lband-controls Button { + margin: 0 1; + } + """ + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._sweeping = False + self._sweep_worker: Worker | None = None + + def compose(self) -> ComposeResult: + with Horizontal(id="lband-main"): + with Vertical(id="lband-plot-col"): + yield SpectrumPlot(title="L-Band Spectrum (Direct Input)", + id="lband-plot") + yield WaterfallDisplay(title="Waterfall", id="lband-waterfall") + with Vertical(id="lband-info-col"): + yield Static(_alloc_table(950, 2150), id="lband-alloc-panel") + + with Horizontal(id="lband-progress"): + yield Static("[#506878]Ready[/]", id="lband-status") + yield ProgressBar(total=100, show_eta=False, id="lband-pbar") + + with Horizontal(id="lband-controls"): + yield Label("Start:") + yield Input("950", id="lband-start") + yield Label("Stop:") + yield Input("2150", id="lband-stop") + yield Label("Step:") + yield Input("2", id="lband-step") + yield Label("Dwell:") + yield Input("20", id="lband-dwell") + yield Button("23cm", id="lband-23cm-btn") + yield Button("Sweep", id="lband-sweep-btn", variant="success") + yield Button("Stop", id="lband-stop-btn", variant="error") + + def on_show(self) -> None: + if self._bridge.is_demo and not self._sweeping: + self._start_sweep() + + def on_hide(self) -> None: + self._stop_sweep() + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "lband-sweep-btn": + self._start_sweep() + elif event.button.id == "lband-stop-btn": + self._stop_sweep() + elif event.button.id == "lband-23cm-btn": + self.query_one("#lband-start", Input).value = "1240" + self.query_one("#lband-stop", Input).value = "1300" + self.query_one("#lband-step", Input).value = "0.5" + # Update allocation display + self.query_one("#lband-alloc-panel", Static).update( + _alloc_table(1240, 1300) + ) + + def _start_sweep(self) -> None: + if self._sweeping: + return + self._sweeping = True + + start = float(self.query_one("#lband-start", Input).value or "950") + stop = float(self.query_one("#lband-stop", Input).value or "2150") + step = float(self.query_one("#lband-step", Input).value or "2") + dwell = int(self.query_one("#lband-dwell", Input).value or "20") + + # Update allocation panel for current range + self.query_one("#lband-alloc-panel", Static).update( + _alloc_table(start, stop) + ) + + self._sweep_worker = self._do_sweep(start, stop, step, dwell) + + def _stop_sweep(self) -> None: + self._sweeping = False + if self._sweep_worker: + self._sweep_worker.cancel() + self._sweep_worker = None + + @work(thread=True) + def _do_sweep(self, start: float, stop: float, step: float, dwell: int) -> None: + """L-band sweep with LNB disabled.""" + try: + self._bridge.ensure_booted() + # Disable LNB for direct input + self._bridge.configure_lnb(disable_lnb=True) + except Exception: + pass + + def progress_cb(freq, step_num, total, result): + pct = (step_num + 1) / total * 100 + self.app.call_from_thread(self._update_progress, pct, freq) + + self.app.call_from_thread(self._set_status, "Sweeping...") + + freqs, powers, results = self._bridge.sweep_spectrum( + start, stop, step, dwell, sr_ksps=20000, callback=progress_cb, + ) + + self.app.call_from_thread(self._show_results, freqs, powers, results) + self._sweeping = False + + def _update_progress(self, pct: float, freq: float) -> None: + if not self.is_mounted: + return + self.query_one("#lband-pbar", ProgressBar).update(progress=pct) + self.query_one("#lband-status", Static).update( + f"[#00d4aa]{freq:.1f} MHz[/]" + ) + + def _set_status(self, msg: str) -> None: + if not self.is_mounted: + return + self.query_one("#lband-status", Static).update(f"[#506878]{msg}[/]") + + def _show_results(self, freqs, powers, results) -> None: + if not self.is_mounted: + return + self.query_one("#lband-plot", SpectrumPlot).update_data( + freqs, powers, results, lnb_lo=0.0, + ) + self.query_one("#lband-waterfall", WaterfallDisplay).add_sweep(powers) + self.query_one("#lband-status", Static).update("[#506878]Complete[/]") + self.query_one("#lband-pbar", ProgressBar).update(progress=100) diff --git a/tui/src/skywalker_tui/screens/monitor.py b/tui/src/skywalker_tui/screens/monitor.py index c4814c4..f066c46 100644 --- a/tui/src/skywalker_tui/screens/monitor.py +++ b/tui/src/skywalker_tui/screens/monitor.py @@ -1,205 +1,205 @@ -"""Monitor screen — real-time signal strength at a single frequency. - -This is the dish-alignment / signal-monitoring mode. It polls signal_monitor() -at a configurable rate and displays SNR, power, lock state, and a rolling -sparkline history. -""" - -from textual.app import ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import Label, Input, Button, Static -from textual import work -from textual.worker import Worker - -from skywalker_tui.widgets.signal_gauge import SignalGauge -from skywalker_tui.widgets.sparkline_widget import SparklineWidget - - -class MonitorScreen(Container): - """Real-time signal monitor with gauge and sparkline.""" - - DEFAULT_CSS = """ - MonitorScreen { - layout: vertical; - } - MonitorScreen #monitor-main { - height: 1fr; - layout: vertical; - padding: 1 2; - } - MonitorScreen #monitor-controls { - height: auto; - padding: 1 2; - background: #0e1018; - border-top: solid #1a2a3a; - layout: horizontal; - } - MonitorScreen #monitor-controls Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - MonitorScreen #monitor-controls Input { - width: 14; - margin: 0 1; - } - MonitorScreen #monitor-controls Button { - margin: 0 1; - } - MonitorScreen #monitor-stats { - height: 3; - layout: horizontal; - padding: 0 2; - } - MonitorScreen #monitor-stats Static { - width: 1fr; - height: 3; - content-align: center middle; - background: #121c2a; - border: round #1a3050; - margin: 0 1 0 0; - } - """ - - BINDINGS = [ - ("space", "toggle_poll", "Start/Stop"), - ] - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._polling = False - self._poll_worker: Worker | None = None - self._sample_count = 0 - self._peak_snr = 0.0 - - def compose(self) -> ComposeResult: - with Vertical(id="monitor-main"): - yield SignalGauge(id="monitor-gauge") - yield SparklineWidget(title="SNR History", color="#00d4aa", - id="snr-sparkline") - yield SparklineWidget(title="Power History", color="#2196f3", - id="power-sparkline") - with Horizontal(id="monitor-stats"): - yield Static("[#506878]Samples:[/] [#00d4aa]0[/]", id="stat-samples") - yield Static("[#506878]Peak SNR:[/] [#00d4aa]0.0 dB[/]", id="stat-peak") - yield Static("[#506878]Status:[/] [#e8a020]Stopped[/]", id="stat-status") - - with Horizontal(id="monitor-controls"): - yield Label("Freq (MHz):") - yield Input("1200", id="mon-freq") - yield Label("SR (ksps):") - yield Input("20000", id="mon-sr") - yield Label("Rate (Hz):") - yield Input("5", id="mon-rate") - yield Button("Start", id="mon-start", variant="success") - yield Button("Stop", id="mon-stop", variant="error") - - def on_show(self) -> None: - # Auto-start polling in demo mode - if self._bridge.is_demo and not self._polling: - self._start_polling() - - def on_hide(self) -> None: - self._stop_polling() - - def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "mon-start": - self._start_polling() - elif event.button.id == "mon-stop": - self._stop_polling() - - def action_toggle_poll(self) -> None: - if self._polling: - self._stop_polling() - else: - self._start_polling() - - def _start_polling(self) -> None: - if self._polling: - return - self._polling = True - self._sample_count = 0 - self._peak_snr = 0.0 - - freq_mhz = float(self.query_one("#mon-freq", Input).value or "1200") - sr_ksps = int(self.query_one("#mon-sr", Input).value or "20000") - rate = float(self.query_one("#mon-rate", Input).value or "5") - - self.query_one("#stat-status", Static).update( - "[#506878]Status:[/] [bold #00d4aa]Running[/]" - ) - - self._poll_worker = self._do_poll(freq_mhz, sr_ksps, rate) - - def _stop_polling(self) -> None: - self._polling = False - if self._poll_worker is not None: - self._poll_worker.cancel() - self._poll_worker = None - try: - self.query_one("#stat-status", Static).update( - "[#506878]Status:[/] [#e8a020]Stopped[/]" - ) - except Exception: - pass - - @staticmethod - def _parse_input(input_widget: Input, default: float) -> float: - try: - return float(input_widget.value) - except (ValueError, TypeError): - return default - - @work(thread=True) - def _do_poll(self, freq_mhz: float, sr_ksps: int, rate: float) -> None: - """Background worker that polls signal_monitor() in a thread.""" - import time - - interval = 1.0 / max(0.5, rate) - freq_khz = int(freq_mhz * 1000) - sr_sps = sr_ksps * 1000 - - # Initial tune - try: - self._bridge.ensure_booted() - self._bridge.tune(sr_sps, freq_khz, 0, 5) - time.sleep(0.3) - except Exception: - pass - - while self._polling: - t0 = time.monotonic() - try: - sig = self._bridge.signal_monitor() - except Exception: - time.sleep(interval) - continue - - self._sample_count += 1 - snr_db = sig.get("snr_db", 0.0) - self._peak_snr = max(self._peak_snr, snr_db) - - # Post updates to the UI thread - self.app.call_from_thread(self._update_ui, sig) - - elapsed = time.monotonic() - t0 - sleep = interval - elapsed - if sleep > 0: - time.sleep(sleep) - - def _update_ui(self, sig: dict) -> None: - """Called from the main thread to update widgets.""" - if not self.is_mounted: - return - - self.query_one("#monitor-gauge", SignalGauge).update_signal(sig) - self.query_one("#snr-sparkline", SparklineWidget).push(sig.get("snr_db", 0)) - self.query_one("#power-sparkline", SparklineWidget).push(sig.get("power_db", -40)) - - self.query_one("#stat-samples", Static).update( - f"[#506878]Samples:[/] [#00d4aa]{self._sample_count}[/]" - ) - self.query_one("#stat-peak", Static).update( - f"[#506878]Peak SNR:[/] [#00d4aa]{self._peak_snr:.1f} dB[/]" - ) +"""Monitor screen — real-time signal strength at a single frequency. + +This is the dish-alignment / signal-monitoring mode. It polls signal_monitor() +at a configurable rate and displays SNR, power, lock state, and a rolling +sparkline history. +""" + +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, Vertical +from textual.widgets import Label, Input, Button, Static +from textual import work +from textual.worker import Worker + +from skywalker_tui.widgets.signal_gauge import SignalGauge +from skywalker_tui.widgets.sparkline_widget import SparklineWidget + + +class MonitorScreen(Container): + """Real-time signal monitor with gauge and sparkline.""" + + DEFAULT_CSS = """ + MonitorScreen { + layout: vertical; + } + MonitorScreen #monitor-main { + height: 1fr; + layout: vertical; + padding: 1 2; + } + MonitorScreen #monitor-controls { + height: auto; + padding: 1 2; + background: #0e1018; + border-top: solid #1a2a3a; + layout: horizontal; + } + MonitorScreen #monitor-controls Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + MonitorScreen #monitor-controls Input { + width: 14; + margin: 0 1; + } + MonitorScreen #monitor-controls Button { + margin: 0 1; + } + MonitorScreen #monitor-stats { + height: 3; + layout: horizontal; + padding: 0 2; + } + MonitorScreen #monitor-stats Static { + width: 1fr; + height: 3; + content-align: center middle; + background: #121c2a; + border: round #1a3050; + margin: 0 1 0 0; + } + """ + + BINDINGS = [ + ("space", "toggle_poll", "Start/Stop"), + ] + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._polling = False + self._poll_worker: Worker | None = None + self._sample_count = 0 + self._peak_snr = 0.0 + + def compose(self) -> ComposeResult: + with Vertical(id="monitor-main"): + yield SignalGauge(id="monitor-gauge") + yield SparklineWidget(title="SNR History", color="#00d4aa", + id="snr-sparkline") + yield SparklineWidget(title="Power History", color="#2196f3", + id="power-sparkline") + with Horizontal(id="monitor-stats"): + yield Static("[#506878]Samples:[/] [#00d4aa]0[/]", id="stat-samples") + yield Static("[#506878]Peak SNR:[/] [#00d4aa]0.0 dB[/]", id="stat-peak") + yield Static("[#506878]Status:[/] [#e8a020]Stopped[/]", id="stat-status") + + with Horizontal(id="monitor-controls"): + yield Label("Freq (MHz):") + yield Input("1200", id="mon-freq") + yield Label("SR (ksps):") + yield Input("20000", id="mon-sr") + yield Label("Rate (Hz):") + yield Input("5", id="mon-rate") + yield Button("Start", id="mon-start", variant="success") + yield Button("Stop", id="mon-stop", variant="error") + + def on_show(self) -> None: + # Auto-start polling in demo mode + if self._bridge.is_demo and not self._polling: + self._start_polling() + + def on_hide(self) -> None: + self._stop_polling() + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "mon-start": + self._start_polling() + elif event.button.id == "mon-stop": + self._stop_polling() + + def action_toggle_poll(self) -> None: + if self._polling: + self._stop_polling() + else: + self._start_polling() + + def _start_polling(self) -> None: + if self._polling: + return + self._polling = True + self._sample_count = 0 + self._peak_snr = 0.0 + + freq_mhz = float(self.query_one("#mon-freq", Input).value or "1200") + sr_ksps = int(self.query_one("#mon-sr", Input).value or "20000") + rate = float(self.query_one("#mon-rate", Input).value or "5") + + self.query_one("#stat-status", Static).update( + "[#506878]Status:[/] [bold #00d4aa]Running[/]" + ) + + self._poll_worker = self._do_poll(freq_mhz, sr_ksps, rate) + + def _stop_polling(self) -> None: + self._polling = False + if self._poll_worker is not None: + self._poll_worker.cancel() + self._poll_worker = None + try: + self.query_one("#stat-status", Static).update( + "[#506878]Status:[/] [#e8a020]Stopped[/]" + ) + except Exception: + pass + + @staticmethod + def _parse_input(input_widget: Input, default: float) -> float: + try: + return float(input_widget.value) + except (ValueError, TypeError): + return default + + @work(thread=True) + def _do_poll(self, freq_mhz: float, sr_ksps: int, rate: float) -> None: + """Background worker that polls signal_monitor() in a thread.""" + import time + + interval = 1.0 / max(0.5, rate) + freq_khz = int(freq_mhz * 1000) + sr_sps = sr_ksps * 1000 + + # Initial tune + try: + self._bridge.ensure_booted() + self._bridge.tune(sr_sps, freq_khz, 0, 5) + time.sleep(0.3) + except Exception: + pass + + while self._polling: + t0 = time.monotonic() + try: + sig = self._bridge.signal_monitor() + except Exception: + time.sleep(interval) + continue + + self._sample_count += 1 + snr_db = sig.get("snr_db", 0.0) + self._peak_snr = max(self._peak_snr, snr_db) + + # Post updates to the UI thread + self.app.call_from_thread(self._update_ui, sig) + + elapsed = time.monotonic() - t0 + sleep = interval - elapsed + if sleep > 0: + time.sleep(sleep) + + def _update_ui(self, sig: dict) -> None: + """Called from the main thread to update widgets.""" + if not self.is_mounted: + return + + self.query_one("#monitor-gauge", SignalGauge).update_signal(sig) + self.query_one("#snr-sparkline", SparklineWidget).push(sig.get("snr_db", 0)) + self.query_one("#power-sparkline", SparklineWidget).push(sig.get("power_db", -40)) + + self.query_one("#stat-samples", Static).update( + f"[#506878]Samples:[/] [#00d4aa]{self._sample_count}[/]" + ) + self.query_one("#stat-peak", Static).update( + f"[#506878]Peak SNR:[/] [#00d4aa]{self._peak_snr:.1f} dB[/]" + ) diff --git a/tui/src/skywalker_tui/screens/motor.py b/tui/src/skywalker_tui/screens/motor.py index de0a632..a53b468 100644 --- a/tui/src/skywalker_tui/screens/motor.py +++ b/tui/src/skywalker_tui/screens/motor.py @@ -1,411 +1,411 @@ -"""Motor screen — DiSEqC 1.2 positioner control with live signal feedback. - -Three-column layout: Motor Control (jog/halt/limits) | Positions (store/recall) | -USALS GotoX (calculator + presets). Bottom bar shows live signal monitor for -dish alignment feedback during jog. -""" - -from textual.app import ComposeResult -from textual.containers import Container, Horizontal, Vertical, Grid -from textual.widgets import Label, Input, Button, Static -from textual import work -from textual.worker import Worker - -from skywalker_tui.widgets.signal_gauge import SignalGauge - - -_QO100_SAT_LON = 25.9 # Es'hail-2 - - -class MotorScreen(Container): - """DiSEqC 1.2 positioner control with signal feedback.""" - - DEFAULT_CSS = """ - MotorScreen { - layout: vertical; - } - MotorScreen #motor-main { - height: 1fr; - layout: horizontal; - } - MotorScreen .motor-col { - width: 1fr; - padding: 1; - } - MotorScreen .motor-panel { - background: #0e1420; - border: round #1a2a3a; - padding: 1; - margin: 0 0 1 0; - height: auto; - } - MotorScreen .motor-panel-title { - color: #00d4aa; - text-style: bold; - margin: 0 0 1 0; - } - MotorScreen .jog-row { - height: auto; - layout: horizontal; - margin: 0 0 1 0; - } - MotorScreen .jog-row Button { - width: 1fr; - margin: 0 1 0 0; - } - MotorScreen .pos-grid { - layout: grid; - grid-size: 3; - grid-gutter: 1; - height: auto; - } - MotorScreen .pos-grid Button { - height: 3; - } - MotorScreen .pos-btn-stored { - background: #1a3a2a; - color: #00d4aa; - border: round #00d4aa; - } - MotorScreen .pos-btn-empty { - background: #121c2a; - color: #506878; - border: round #1a3050; - } - MotorScreen #motor-signal { - height: auto; - padding: 1 2; - background: #0e1018; - border-top: solid #1a2a3a; - dock: bottom; - } - MotorScreen #motor-signal .sig-row { - height: 3; - layout: horizontal; - } - MotorScreen #motor-signal Static { - width: 1fr; - height: 3; - content-align: center middle; - background: #121c2a; - border: round #1a3050; - margin: 0 1 0 0; - } - MotorScreen .usals-input-row { - height: auto; - } - MotorScreen .usals-input-row Label { - color: #506878; - width: auto; - margin: 0 1 0 0; - } - MotorScreen .usals-input-row Input { - width: 12; - margin: 0 1; - } - MotorScreen .motor-status { - height: auto; - color: #506878; - margin: 1 0 0 0; - } - """ - - BINDINGS = [ - ("left", "jog_west", "Jog West"), - ("right", "jog_east", "Jog East"), - ("space", "halt", "Halt"), - ] - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._polling = False - self._poll_worker: Worker | None = None - self._jog_active = False - self._jog_start_time = 0.0 - self._stored_positions: dict[int, bool] = {} - - def compose(self) -> ComposeResult: - with Horizontal(id="motor-main"): - # Column 1: Motor jog control - with Vertical(classes="motor-col"): - with Vertical(classes="motor-panel"): - yield Label("Motor Control", classes="motor-panel-title") - with Horizontal(classes="jog-row"): - yield Button("West", id="jog-west", variant="warning") - yield Button("HALT", id="jog-halt", variant="error") - yield Button("East", id="jog-east", variant="warning") - with Horizontal(classes="jog-row"): - yield Button("Step W", id="step-west") - yield Button("Step E", id="step-east") - yield Static("[#506878]Direction:[/] [#e8a020]Stopped[/]", - id="motor-dir-status", classes="motor-status") - yield Static("[#506878]Position:[/] [#00d4aa]0.0 deg[/]", - id="motor-pos-status", classes="motor-status") - - with Vertical(classes="motor-panel"): - yield Label("Limits", classes="motor-panel-title") - with Horizontal(classes="jog-row"): - yield Button("Set East Limit", id="limit-east") - yield Button("Set West Limit", id="limit-west") - yield Button("Disable Limits", id="limit-disable") - - # Column 2: Stored positions - with Vertical(classes="motor-col"): - with Vertical(classes="motor-panel"): - yield Label("Stored Positions", classes="motor-panel-title") - yield Static( - "[#506878]Press to recall, hold S+number to store[/]", - classes="motor-status", - ) - with Grid(classes="pos-grid"): - for i in range(1, 10): - yield Button( - f"Pos {i}", - id=f"pos-{i}", - classes="pos-btn-empty", - ) - yield Button("Go to Reference (0)", id="pos-ref") - yield Static("", id="pos-info", classes="motor-status") - - # Column 3: USALS GotoX - with Vertical(classes="motor-col"): - with Vertical(classes="motor-panel"): - yield Label("USALS GotoX", classes="motor-panel-title") - with Horizontal(classes="usals-input-row"): - yield Label("Observer Lon:") - yield Input("-97.5", id="usals-obs-lon") - with Horizontal(classes="usals-input-row"): - yield Label("Satellite Lon:") - yield Input("25.9", id="usals-sat-lon") - yield Button("Calculate & Go", id="usals-go", variant="success") - yield Static("", id="usals-result", classes="motor-status") - - with Vertical(classes="motor-panel"): - yield Label("Presets", classes="motor-panel-title") - yield Button("QO-100 (25.9E)", id="preset-qo100") - yield Button("Galaxy 19 (97.0W)", id="preset-g19") - yield Button("AMC-1 (103.0W)", id="preset-amc1") - - # Bottom: live signal bar - with Vertical(id="motor-signal"): - yield SignalGauge(id="motor-gauge") - with Horizontal(classes="sig-row"): - yield Static("[#506878]SNR:[/] [#00d4aa]-- dB[/]", id="sig-snr") - yield Static("[#506878]Power:[/] [#00d4aa]-- dB[/]", id="sig-power") - yield Static("[#506878]Lock:[/] [#e04040]NO[/]", id="sig-lock") - yield Static("[#506878]Motor:[/] [#e8a020]Idle[/]", id="sig-motor") - - def on_show(self) -> None: - if not self._polling: - self._start_polling() - - def on_hide(self) -> None: - self._stop_polling() - # Safety: halt motor when leaving screen - if self._jog_active: - try: - self._bridge.motor_halt() - except Exception: - pass - self._jog_active = False - - def on_button_pressed(self, event: Button.Pressed) -> None: - btn = event.button.id or "" - - if btn == "jog-east": - self._do_jog_east() - elif btn == "jog-west": - self._do_jog_west() - elif btn == "jog-halt": - self._do_halt() - elif btn == "step-east": - self._do_step(east=True) - elif btn == "step-west": - self._do_step(east=False) - elif btn == "limit-east": - self._bridge.motor_set_limit("east") - elif btn == "limit-west": - self._bridge.motor_set_limit("west") - elif btn == "limit-disable": - self._bridge.motor_disable_limits() - elif btn.startswith("pos-") and btn != "pos-ref": - slot = int(btn.split("-")[1]) - self._do_goto_position(slot) - elif btn == "pos-ref": - self._do_goto_position(0) - elif btn == "usals-go": - self._do_usals_go() - elif btn == "preset-qo100": - self._do_preset(25.9) - elif btn == "preset-g19": - self._do_preset(-97.0) - elif btn == "preset-amc1": - self._do_preset(-103.0) - - def action_jog_east(self) -> None: - self._do_jog_east() - - def action_jog_west(self) -> None: - self._do_jog_west() - - def action_halt(self) -> None: - self._do_halt() - - def _do_jog_east(self) -> None: - import time - self._jog_active = True - self._jog_start_time = time.monotonic() - self._bridge.motor_drive_east() - self._update_dir_status("East", "#00e060") - - def _do_jog_west(self) -> None: - import time - self._jog_active = True - self._jog_start_time = time.monotonic() - self._bridge.motor_drive_west() - self._update_dir_status("West", "#2196f3") - - def _do_halt(self) -> None: - self._jog_active = False - self._bridge.motor_halt() - self._update_dir_status("Stopped", "#e8a020") - - def _do_step(self, east: bool) -> None: - if east: - self._bridge.motor_drive_east(steps=10) - else: - self._bridge.motor_drive_west(steps=10) - self._update_dir_status("Stepping", "#e8a020") - - def _do_goto_position(self, slot: int) -> None: - self._bridge.motor_goto_position(slot) - label = f"Pos {slot}" if slot > 0 else "Reference" - self._update_dir_status(f"Going to {label}", "#00d4aa") - - def _do_usals_go(self) -> None: - try: - obs_lon = float(self.query_one("#usals-obs-lon", Input).value) - sat_lon = float(self.query_one("#usals-sat-lon", Input).value) - except (ValueError, TypeError): - return - - self._bridge.motor_goto_x(obs_lon, sat_lon) - - # Show calculated angle - import sys, os - sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "tools")) - try: - from skywalker_lib import usals_angle - angle = usals_angle(obs_lon, sat_lon) - direction = "East" if angle >= 0 else "West" - self.query_one("#usals-result", Static).update( - f"[#506878]Angle:[/] [#00d4aa]{abs(angle):.1f} deg {direction}[/]" - ) - except ImportError: - pass - - self._update_dir_status("USALS GotoX", "#00d4aa") - - def _do_preset(self, sat_lon: float) -> None: - self.query_one("#usals-sat-lon", Input).value = str(sat_lon) - self._do_usals_go() - - def _update_dir_status(self, text: str, color: str) -> None: - if not self.is_mounted: - return - self.query_one("#motor-dir-status", Static).update( - f"[#506878]Direction:[/] [{color}]{text}[/]" - ) - - def _start_polling(self) -> None: - self._polling = True - self._poll_worker = self._do_signal_poll() - - def _stop_polling(self) -> None: - self._polling = False - if self._poll_worker: - self._poll_worker.cancel() - self._poll_worker = None - - @work(thread=True) - def _do_signal_poll(self) -> None: - """Poll signal + motor state at ~2 Hz for alignment feedback.""" - import time - - try: - self._bridge.ensure_booted() - except Exception: - pass - - while self._polling: - t0 = time.monotonic() - - # Safety: auto-halt after 30s continuous jog - if self._jog_active: - elapsed = t0 - self._jog_start_time - if elapsed > 30.0: - self._bridge.motor_halt() - self._jog_active = False - self.app.call_from_thread( - self._update_dir_status, "Auto-halted (30s)", "#e04040" - ) - - try: - sig = self._bridge.signal_monitor() - except Exception: - time.sleep(0.5) - continue - - # Read motor position from demo device - motor_pos = None - motor_moving = False - if hasattr(self._bridge, '_dev'): - dev = self._bridge._dev - if hasattr(dev, 'motor_position'): - motor_pos = dev.motor_position - motor_moving = dev.motor_is_moving - - self.app.call_from_thread(self._update_signal_ui, sig, motor_pos, motor_moving) - - elapsed = time.monotonic() - t0 - sleep = 0.5 - elapsed - if sleep > 0: - time.sleep(sleep) - - def _update_signal_ui(self, sig: dict, motor_pos: float | None, - motor_moving: bool) -> None: - if not self.is_mounted: - return - - self.query_one("#motor-gauge", SignalGauge).update_signal(sig) - - snr = sig.get("snr_db", 0.0) - power = sig.get("power_db", -40.0) - locked = sig.get("locked", False) - - self.query_one("#sig-snr", Static).update( - f"[#506878]SNR:[/] [#00d4aa]{snr:.1f} dB[/]" - ) - self.query_one("#sig-power", Static).update( - f"[#506878]Power:[/] [#00d4aa]{power:.1f} dB[/]" - ) - - lock_color = "#00e060" if locked else "#e04040" - lock_text = "LOCKED" if locked else "NO" - self.query_one("#sig-lock", Static).update( - f"[#506878]Lock:[/] [{lock_color}]{lock_text}[/]" - ) - - if motor_pos is not None: - direction = "E" if motor_pos >= 0 else "W" - move_indicator = " [#e8a020]>>>[/]" if motor_moving else "" - self.query_one("#sig-motor", Static).update( - f"[#506878]Motor:[/] [#00d4aa]{abs(motor_pos):.1f} deg {direction}[/]{move_indicator}" - ) - self.query_one("#motor-pos-status", Static).update( - f"[#506878]Position:[/] [#00d4aa]{motor_pos:.1f} deg[/]" - ) - - if not self._jog_active and not motor_moving: - self._update_dir_status("Stopped", "#e8a020") +"""Motor screen — DiSEqC 1.2 positioner control with live signal feedback. + +Three-column layout: Motor Control (jog/halt/limits) | Positions (store/recall) | +USALS GotoX (calculator + presets). Bottom bar shows live signal monitor for +dish alignment feedback during jog. +""" + +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, Vertical, Grid +from textual.widgets import Label, Input, Button, Static +from textual import work +from textual.worker import Worker + +from skywalker_tui.widgets.signal_gauge import SignalGauge + + +_QO100_SAT_LON = 25.9 # Es'hail-2 + + +class MotorScreen(Container): + """DiSEqC 1.2 positioner control with signal feedback.""" + + DEFAULT_CSS = """ + MotorScreen { + layout: vertical; + } + MotorScreen #motor-main { + height: 1fr; + layout: horizontal; + } + MotorScreen .motor-col { + width: 1fr; + padding: 1; + } + MotorScreen .motor-panel { + background: #0e1420; + border: round #1a2a3a; + padding: 1; + margin: 0 0 1 0; + height: auto; + } + MotorScreen .motor-panel-title { + color: #00d4aa; + text-style: bold; + margin: 0 0 1 0; + } + MotorScreen .jog-row { + height: auto; + layout: horizontal; + margin: 0 0 1 0; + } + MotorScreen .jog-row Button { + width: 1fr; + margin: 0 1 0 0; + } + MotorScreen .pos-grid { + layout: grid; + grid-size: 3; + grid-gutter: 1; + height: auto; + } + MotorScreen .pos-grid Button { + height: 3; + } + MotorScreen .pos-btn-stored { + background: #1a3a2a; + color: #00d4aa; + border: round #00d4aa; + } + MotorScreen .pos-btn-empty { + background: #121c2a; + color: #506878; + border: round #1a3050; + } + MotorScreen #motor-signal { + height: auto; + padding: 1 2; + background: #0e1018; + border-top: solid #1a2a3a; + dock: bottom; + } + MotorScreen #motor-signal .sig-row { + height: 3; + layout: horizontal; + } + MotorScreen #motor-signal Static { + width: 1fr; + height: 3; + content-align: center middle; + background: #121c2a; + border: round #1a3050; + margin: 0 1 0 0; + } + MotorScreen .usals-input-row { + height: auto; + } + MotorScreen .usals-input-row Label { + color: #506878; + width: auto; + margin: 0 1 0 0; + } + MotorScreen .usals-input-row Input { + width: 12; + margin: 0 1; + } + MotorScreen .motor-status { + height: auto; + color: #506878; + margin: 1 0 0 0; + } + """ + + BINDINGS = [ + ("left", "jog_west", "Jog West"), + ("right", "jog_east", "Jog East"), + ("space", "halt", "Halt"), + ] + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._polling = False + self._poll_worker: Worker | None = None + self._jog_active = False + self._jog_start_time = 0.0 + self._stored_positions: dict[int, bool] = {} + + def compose(self) -> ComposeResult: + with Horizontal(id="motor-main"): + # Column 1: Motor jog control + with Vertical(classes="motor-col"): + with Vertical(classes="motor-panel"): + yield Label("Motor Control", classes="motor-panel-title") + with Horizontal(classes="jog-row"): + yield Button("West", id="jog-west", variant="warning") + yield Button("HALT", id="jog-halt", variant="error") + yield Button("East", id="jog-east", variant="warning") + with Horizontal(classes="jog-row"): + yield Button("Step W", id="step-west") + yield Button("Step E", id="step-east") + yield Static("[#506878]Direction:[/] [#e8a020]Stopped[/]", + id="motor-dir-status", classes="motor-status") + yield Static("[#506878]Position:[/] [#00d4aa]0.0 deg[/]", + id="motor-pos-status", classes="motor-status") + + with Vertical(classes="motor-panel"): + yield Label("Limits", classes="motor-panel-title") + with Horizontal(classes="jog-row"): + yield Button("Set East Limit", id="limit-east") + yield Button("Set West Limit", id="limit-west") + yield Button("Disable Limits", id="limit-disable") + + # Column 2: Stored positions + with Vertical(classes="motor-col"): + with Vertical(classes="motor-panel"): + yield Label("Stored Positions", classes="motor-panel-title") + yield Static( + "[#506878]Press to recall, hold S+number to store[/]", + classes="motor-status", + ) + with Grid(classes="pos-grid"): + for i in range(1, 10): + yield Button( + f"Pos {i}", + id=f"pos-{i}", + classes="pos-btn-empty", + ) + yield Button("Go to Reference (0)", id="pos-ref") + yield Static("", id="pos-info", classes="motor-status") + + # Column 3: USALS GotoX + with Vertical(classes="motor-col"): + with Vertical(classes="motor-panel"): + yield Label("USALS GotoX", classes="motor-panel-title") + with Horizontal(classes="usals-input-row"): + yield Label("Observer Lon:") + yield Input("-97.5", id="usals-obs-lon") + with Horizontal(classes="usals-input-row"): + yield Label("Satellite Lon:") + yield Input("25.9", id="usals-sat-lon") + yield Button("Calculate & Go", id="usals-go", variant="success") + yield Static("", id="usals-result", classes="motor-status") + + with Vertical(classes="motor-panel"): + yield Label("Presets", classes="motor-panel-title") + yield Button("QO-100 (25.9E)", id="preset-qo100") + yield Button("Galaxy 19 (97.0W)", id="preset-g19") + yield Button("AMC-1 (103.0W)", id="preset-amc1") + + # Bottom: live signal bar + with Vertical(id="motor-signal"): + yield SignalGauge(id="motor-gauge") + with Horizontal(classes="sig-row"): + yield Static("[#506878]SNR:[/] [#00d4aa]-- dB[/]", id="sig-snr") + yield Static("[#506878]Power:[/] [#00d4aa]-- dB[/]", id="sig-power") + yield Static("[#506878]Lock:[/] [#e04040]NO[/]", id="sig-lock") + yield Static("[#506878]Motor:[/] [#e8a020]Idle[/]", id="sig-motor") + + def on_show(self) -> None: + if not self._polling: + self._start_polling() + + def on_hide(self) -> None: + self._stop_polling() + # Safety: halt motor when leaving screen + if self._jog_active: + try: + self._bridge.motor_halt() + except Exception: + pass + self._jog_active = False + + def on_button_pressed(self, event: Button.Pressed) -> None: + btn = event.button.id or "" + + if btn == "jog-east": + self._do_jog_east() + elif btn == "jog-west": + self._do_jog_west() + elif btn == "jog-halt": + self._do_halt() + elif btn == "step-east": + self._do_step(east=True) + elif btn == "step-west": + self._do_step(east=False) + elif btn == "limit-east": + self._bridge.motor_set_limit("east") + elif btn == "limit-west": + self._bridge.motor_set_limit("west") + elif btn == "limit-disable": + self._bridge.motor_disable_limits() + elif btn.startswith("pos-") and btn != "pos-ref": + slot = int(btn.split("-")[1]) + self._do_goto_position(slot) + elif btn == "pos-ref": + self._do_goto_position(0) + elif btn == "usals-go": + self._do_usals_go() + elif btn == "preset-qo100": + self._do_preset(25.9) + elif btn == "preset-g19": + self._do_preset(-97.0) + elif btn == "preset-amc1": + self._do_preset(-103.0) + + def action_jog_east(self) -> None: + self._do_jog_east() + + def action_jog_west(self) -> None: + self._do_jog_west() + + def action_halt(self) -> None: + self._do_halt() + + def _do_jog_east(self) -> None: + import time + self._jog_active = True + self._jog_start_time = time.monotonic() + self._bridge.motor_drive_east() + self._update_dir_status("East", "#00e060") + + def _do_jog_west(self) -> None: + import time + self._jog_active = True + self._jog_start_time = time.monotonic() + self._bridge.motor_drive_west() + self._update_dir_status("West", "#2196f3") + + def _do_halt(self) -> None: + self._jog_active = False + self._bridge.motor_halt() + self._update_dir_status("Stopped", "#e8a020") + + def _do_step(self, east: bool) -> None: + if east: + self._bridge.motor_drive_east(steps=10) + else: + self._bridge.motor_drive_west(steps=10) + self._update_dir_status("Stepping", "#e8a020") + + def _do_goto_position(self, slot: int) -> None: + self._bridge.motor_goto_position(slot) + label = f"Pos {slot}" if slot > 0 else "Reference" + self._update_dir_status(f"Going to {label}", "#00d4aa") + + def _do_usals_go(self) -> None: + try: + obs_lon = float(self.query_one("#usals-obs-lon", Input).value) + sat_lon = float(self.query_one("#usals-sat-lon", Input).value) + except (ValueError, TypeError): + return + + self._bridge.motor_goto_x(obs_lon, sat_lon) + + # Show calculated angle + import sys, os + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "tools")) + try: + from skywalker_lib import usals_angle + angle = usals_angle(obs_lon, sat_lon) + direction = "East" if angle >= 0 else "West" + self.query_one("#usals-result", Static).update( + f"[#506878]Angle:[/] [#00d4aa]{abs(angle):.1f} deg {direction}[/]" + ) + except ImportError: + pass + + self._update_dir_status("USALS GotoX", "#00d4aa") + + def _do_preset(self, sat_lon: float) -> None: + self.query_one("#usals-sat-lon", Input).value = str(sat_lon) + self._do_usals_go() + + def _update_dir_status(self, text: str, color: str) -> None: + if not self.is_mounted: + return + self.query_one("#motor-dir-status", Static).update( + f"[#506878]Direction:[/] [{color}]{text}[/]" + ) + + def _start_polling(self) -> None: + self._polling = True + self._poll_worker = self._do_signal_poll() + + def _stop_polling(self) -> None: + self._polling = False + if self._poll_worker: + self._poll_worker.cancel() + self._poll_worker = None + + @work(thread=True) + def _do_signal_poll(self) -> None: + """Poll signal + motor state at ~2 Hz for alignment feedback.""" + import time + + try: + self._bridge.ensure_booted() + except Exception: + pass + + while self._polling: + t0 = time.monotonic() + + # Safety: auto-halt after 30s continuous jog + if self._jog_active: + elapsed = t0 - self._jog_start_time + if elapsed > 30.0: + self._bridge.motor_halt() + self._jog_active = False + self.app.call_from_thread( + self._update_dir_status, "Auto-halted (30s)", "#e04040" + ) + + try: + sig = self._bridge.signal_monitor() + except Exception: + time.sleep(0.5) + continue + + # Read motor position from demo device + motor_pos = None + motor_moving = False + if hasattr(self._bridge, '_dev'): + dev = self._bridge._dev + if hasattr(dev, 'motor_position'): + motor_pos = dev.motor_position + motor_moving = dev.motor_is_moving + + self.app.call_from_thread(self._update_signal_ui, sig, motor_pos, motor_moving) + + elapsed = time.monotonic() - t0 + sleep = 0.5 - elapsed + if sleep > 0: + time.sleep(sleep) + + def _update_signal_ui(self, sig: dict, motor_pos: float | None, + motor_moving: bool) -> None: + if not self.is_mounted: + return + + self.query_one("#motor-gauge", SignalGauge).update_signal(sig) + + snr = sig.get("snr_db", 0.0) + power = sig.get("power_db", -40.0) + locked = sig.get("locked", False) + + self.query_one("#sig-snr", Static).update( + f"[#506878]SNR:[/] [#00d4aa]{snr:.1f} dB[/]" + ) + self.query_one("#sig-power", Static).update( + f"[#506878]Power:[/] [#00d4aa]{power:.1f} dB[/]" + ) + + lock_color = "#00e060" if locked else "#e04040" + lock_text = "LOCKED" if locked else "NO" + self.query_one("#sig-lock", Static).update( + f"[#506878]Lock:[/] [{lock_color}]{lock_text}[/]" + ) + + if motor_pos is not None: + direction = "E" if motor_pos >= 0 else "W" + move_indicator = " [#e8a020]>>>[/]" if motor_moving else "" + self.query_one("#sig-motor", Static).update( + f"[#506878]Motor:[/] [#00d4aa]{abs(motor_pos):.1f} deg {direction}[/]{move_indicator}" + ) + self.query_one("#motor-pos-status", Static).update( + f"[#506878]Position:[/] [#00d4aa]{motor_pos:.1f} deg[/]" + ) + + if not self._jog_active and not motor_moving: + self._update_dir_status("Stopped", "#e8a020") diff --git a/tui/src/skywalker_tui/screens/scan.py b/tui/src/skywalker_tui/screens/scan.py index ad7eb0b..21d355c 100644 --- a/tui/src/skywalker_tui/screens/scan.py +++ b/tui/src/skywalker_tui/screens/scan.py @@ -1,257 +1,257 @@ -"""Scan screen — automated transponder discovery. - -Multi-phase pipeline: coarse sweep → peak detection → fine sweep → blind scan. -Shows progress, spectrum visualization, and a results table. -""" - -from textual.app import ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import Label, Input, Button, Static, ProgressBar -from textual import work -from textual.worker import Worker - -from skywalker_tui.widgets.spectrum_plot import SpectrumPlot -from skywalker_tui.widgets.frequency_table import FrequencyTable - - -class ScanScreen(Container): - """Multi-phase transponder scanner with progress and results table.""" - - DEFAULT_CSS = """ - ScanScreen { - layout: vertical; - } - ScanScreen #scan-main { - height: 1fr; - layout: vertical; - } - ScanScreen #scan-upper { - height: 1fr; - layout: horizontal; - } - ScanScreen #scan-spectrum-col { - width: 1fr; - } - ScanScreen #scan-results-col { - width: 1fr; - } - ScanScreen #scan-progress { - height: auto; - padding: 1 2; - background: #0e1018; - layout: vertical; - } - ScanScreen #scan-progress-row { - height: 3; - layout: horizontal; - } - ScanScreen #scan-progress Static { - width: auto; - margin: 0 1 0 0; - } - ScanScreen #scan-progress ProgressBar { - width: 1fr; - margin: 0 1; - } - ScanScreen #scan-controls { - height: auto; - padding: 1 2; - background: #0e1018; - border-top: solid #1a2a3a; - layout: horizontal; - } - ScanScreen #scan-controls Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - ScanScreen #scan-controls Input { - width: 10; - margin: 0 1; - } - ScanScreen #scan-controls Button { - margin: 0 1; - } - """ - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._scanning = False - self._scan_worker: Worker | None = None - - def compose(self) -> ComposeResult: - with Vertical(id="scan-main"): - with Horizontal(id="scan-upper"): - with Vertical(id="scan-spectrum-col"): - yield SpectrumPlot(title="Coarse Sweep", id="scan-spectrum") - with Vertical(id="scan-results-col"): - yield Static("[#00d4aa bold]Transponders Found[/]", - id="scan-results-title") - yield FrequencyTable(id="scan-table") - - with Vertical(id="scan-progress"): - yield Static("[#506878]Ready[/]", id="scan-phase") - with Horizontal(id="scan-progress-row"): - yield ProgressBar(total=100, show_eta=False, id="scan-pbar") - - with Horizontal(id="scan-controls"): - yield Label("Start:") - yield Input("950", id="scan-start") - yield Label("Stop:") - yield Input("2150", id="scan-stop") - yield Label("LNB LO:") - yield Input("9750", id="scan-lnb") - yield Label("Threshold:") - yield Input("3", id="scan-thresh") - yield Button("Scan", id="scan-start-btn", variant="success") - yield Button("Stop", id="scan-stop-btn", variant="error") - - def on_hide(self) -> None: - self._stop_scan() - - def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "scan-start-btn": - self._start_scan() - elif event.button.id == "scan-stop-btn": - self._stop_scan() - - def _start_scan(self) -> None: - if self._scanning: - return - self._scanning = True - - start = float(self.query_one("#scan-start", Input).value or "950") - stop = float(self.query_one("#scan-stop", Input).value or "2150") - lnb_lo = float(self.query_one("#scan-lnb", Input).value or "9750") - threshold = float(self.query_one("#scan-thresh", Input).value or "3") - - # Clear previous results - self.query_one("#scan-table", FrequencyTable).clear_table() - - self._scan_worker = self._do_scan(start, stop, lnb_lo, threshold) - - def _stop_scan(self) -> None: - self._scanning = False - if self._scan_worker: - self._scan_worker.cancel() - self._scan_worker = None - - @work(thread=True) - def _do_scan(self, start: float, stop: float, lnb_lo: float, - threshold: float) -> None: - """Multi-phase scan pipeline in a background thread.""" - import sys - import os - sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "tools")) - from skywalker_lib import detect_peaks, if_to_rf - - try: - self._bridge.ensure_booted() - except Exception: - pass - - # Phase 1: Coarse sweep - self.app.call_from_thread(self._set_phase, "Phase 1: Coarse sweep", 0) - coarse_step = 10 - - def coarse_cb(freq, step_num, total, result): - pct = (step_num + 1) / total * 100 - self.app.call_from_thread(self._set_progress, pct) - - freqs, powers, results = self._bridge.sweep_spectrum( - start, stop, coarse_step, dwell_ms=15, sr_ksps=20000, - callback=coarse_cb, - ) - - if not self._scanning: - return - - self.app.call_from_thread(self._update_spectrum, freqs, powers, results, lnb_lo) - - # Phase 2: Peak detection - self.app.call_from_thread(self._set_phase, "Phase 2: Peak detection", 50) - peaks = detect_peaks(freqs, powers, threshold_db=threshold) - - if not peaks: - self.app.call_from_thread(self._set_phase, "No peaks found", 100) - self._scanning = False - return - - # Phase 3: Fine sweep around peaks - self.app.call_from_thread( - self._set_phase, - f"Phase 3: Fine sweep ({len(peaks)} peaks)", 60, - ) - refined = [] - for i, (freq, pwr, idx) in enumerate(peaks): - if not self._scanning: - return - fine_start = max(start, freq - 15) - fine_stop = min(stop, freq + 15) - fine_freqs, fine_powers, fine_results = self._bridge.sweep_spectrum( - fine_start, fine_stop, step_mhz=2.0, dwell_ms=20, sr_ksps=20000, - ) - if fine_powers: - best_idx = fine_powers.index(max(fine_powers)) - refined.append(( - fine_freqs[best_idx], fine_powers[best_idx], - fine_results[best_idx], - )) - pct = 60 + (i + 1) / len(peaks) * 20 - self.app.call_from_thread(self._set_progress, pct) - - # Phase 4: Blind scan - self.app.call_from_thread( - self._set_phase, - f"Phase 4: Blind scan ({len(refined)} candidates)", 80, - ) - sr_min = 1000 * 1000 - sr_max = 30000 * 1000 - sr_step = 500 * 1000 - - for i, (freq, pwr, result) in enumerate(refined): - if not self._scanning: - return - freq_khz = int(freq * 1000) - bs_result = self._bridge.blind_scan(freq_khz, sr_min, sr_max, sr_step) - if bs_result and bs_result.get("locked"): - tp = { - "if_mhz": bs_result.get("freq_khz", freq_khz) / 1000.0, - "rf_mhz": if_to_rf( - bs_result.get("freq_khz", freq_khz) / 1000.0, lnb_lo - ), - "sr_ksps": bs_result.get("sr_sps", 0) // 1000, - "power_db": pwr, - "locked": True, - } - self.app.call_from_thread(self._add_transponder, tp) - - pct = 80 + (i + 1) / len(refined) * 20 - self.app.call_from_thread(self._set_progress, pct) - - self.app.call_from_thread(self._set_phase, "Scan complete", 100) - self._scanning = False - - def _set_phase(self, text: str, progress: float) -> None: - if not self.is_mounted: - return - self.query_one("#scan-phase", Static).update(f"[#00d4aa]{text}[/]") - self.query_one("#scan-pbar", ProgressBar).update(progress=progress) - - def _set_progress(self, pct: float) -> None: - if not self.is_mounted: - return - self.query_one("#scan-pbar", ProgressBar).update(progress=pct) - - def _update_spectrum(self, freqs, powers, results, lnb_lo) -> None: - if not self.is_mounted: - return - self.query_one("#scan-spectrum", SpectrumPlot).update_data( - freqs, powers, results, lnb_lo=lnb_lo, - ) - - def _add_transponder(self, tp: dict) -> None: - if not self.is_mounted: - return - self.query_one("#scan-table", FrequencyTable).add_transponder(tp) +"""Scan screen — automated transponder discovery. + +Multi-phase pipeline: coarse sweep → peak detection → fine sweep → blind scan. +Shows progress, spectrum visualization, and a results table. +""" + +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, Vertical +from textual.widgets import Label, Input, Button, Static, ProgressBar +from textual import work +from textual.worker import Worker + +from skywalker_tui.widgets.spectrum_plot import SpectrumPlot +from skywalker_tui.widgets.frequency_table import FrequencyTable + + +class ScanScreen(Container): + """Multi-phase transponder scanner with progress and results table.""" + + DEFAULT_CSS = """ + ScanScreen { + layout: vertical; + } + ScanScreen #scan-main { + height: 1fr; + layout: vertical; + } + ScanScreen #scan-upper { + height: 1fr; + layout: horizontal; + } + ScanScreen #scan-spectrum-col { + width: 1fr; + } + ScanScreen #scan-results-col { + width: 1fr; + } + ScanScreen #scan-progress { + height: auto; + padding: 1 2; + background: #0e1018; + layout: vertical; + } + ScanScreen #scan-progress-row { + height: 3; + layout: horizontal; + } + ScanScreen #scan-progress Static { + width: auto; + margin: 0 1 0 0; + } + ScanScreen #scan-progress ProgressBar { + width: 1fr; + margin: 0 1; + } + ScanScreen #scan-controls { + height: auto; + padding: 1 2; + background: #0e1018; + border-top: solid #1a2a3a; + layout: horizontal; + } + ScanScreen #scan-controls Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + ScanScreen #scan-controls Input { + width: 10; + margin: 0 1; + } + ScanScreen #scan-controls Button { + margin: 0 1; + } + """ + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._scanning = False + self._scan_worker: Worker | None = None + + def compose(self) -> ComposeResult: + with Vertical(id="scan-main"): + with Horizontal(id="scan-upper"): + with Vertical(id="scan-spectrum-col"): + yield SpectrumPlot(title="Coarse Sweep", id="scan-spectrum") + with Vertical(id="scan-results-col"): + yield Static("[#00d4aa bold]Transponders Found[/]", + id="scan-results-title") + yield FrequencyTable(id="scan-table") + + with Vertical(id="scan-progress"): + yield Static("[#506878]Ready[/]", id="scan-phase") + with Horizontal(id="scan-progress-row"): + yield ProgressBar(total=100, show_eta=False, id="scan-pbar") + + with Horizontal(id="scan-controls"): + yield Label("Start:") + yield Input("950", id="scan-start") + yield Label("Stop:") + yield Input("2150", id="scan-stop") + yield Label("LNB LO:") + yield Input("9750", id="scan-lnb") + yield Label("Threshold:") + yield Input("3", id="scan-thresh") + yield Button("Scan", id="scan-start-btn", variant="success") + yield Button("Stop", id="scan-stop-btn", variant="error") + + def on_hide(self) -> None: + self._stop_scan() + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "scan-start-btn": + self._start_scan() + elif event.button.id == "scan-stop-btn": + self._stop_scan() + + def _start_scan(self) -> None: + if self._scanning: + return + self._scanning = True + + start = float(self.query_one("#scan-start", Input).value or "950") + stop = float(self.query_one("#scan-stop", Input).value or "2150") + lnb_lo = float(self.query_one("#scan-lnb", Input).value or "9750") + threshold = float(self.query_one("#scan-thresh", Input).value or "3") + + # Clear previous results + self.query_one("#scan-table", FrequencyTable).clear_table() + + self._scan_worker = self._do_scan(start, stop, lnb_lo, threshold) + + def _stop_scan(self) -> None: + self._scanning = False + if self._scan_worker: + self._scan_worker.cancel() + self._scan_worker = None + + @work(thread=True) + def _do_scan(self, start: float, stop: float, lnb_lo: float, + threshold: float) -> None: + """Multi-phase scan pipeline in a background thread.""" + import sys + import os + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "tools")) + from skywalker_lib import detect_peaks, if_to_rf + + try: + self._bridge.ensure_booted() + except Exception: + pass + + # Phase 1: Coarse sweep + self.app.call_from_thread(self._set_phase, "Phase 1: Coarse sweep", 0) + coarse_step = 10 + + def coarse_cb(freq, step_num, total, result): + pct = (step_num + 1) / total * 100 + self.app.call_from_thread(self._set_progress, pct) + + freqs, powers, results = self._bridge.sweep_spectrum( + start, stop, coarse_step, dwell_ms=15, sr_ksps=20000, + callback=coarse_cb, + ) + + if not self._scanning: + return + + self.app.call_from_thread(self._update_spectrum, freqs, powers, results, lnb_lo) + + # Phase 2: Peak detection + self.app.call_from_thread(self._set_phase, "Phase 2: Peak detection", 50) + peaks = detect_peaks(freqs, powers, threshold_db=threshold) + + if not peaks: + self.app.call_from_thread(self._set_phase, "No peaks found", 100) + self._scanning = False + return + + # Phase 3: Fine sweep around peaks + self.app.call_from_thread( + self._set_phase, + f"Phase 3: Fine sweep ({len(peaks)} peaks)", 60, + ) + refined = [] + for i, (freq, pwr, idx) in enumerate(peaks): + if not self._scanning: + return + fine_start = max(start, freq - 15) + fine_stop = min(stop, freq + 15) + fine_freqs, fine_powers, fine_results = self._bridge.sweep_spectrum( + fine_start, fine_stop, step_mhz=2.0, dwell_ms=20, sr_ksps=20000, + ) + if fine_powers: + best_idx = fine_powers.index(max(fine_powers)) + refined.append(( + fine_freqs[best_idx], fine_powers[best_idx], + fine_results[best_idx], + )) + pct = 60 + (i + 1) / len(peaks) * 20 + self.app.call_from_thread(self._set_progress, pct) + + # Phase 4: Blind scan + self.app.call_from_thread( + self._set_phase, + f"Phase 4: Blind scan ({len(refined)} candidates)", 80, + ) + sr_min = 1000 * 1000 + sr_max = 30000 * 1000 + sr_step = 500 * 1000 + + for i, (freq, pwr, result) in enumerate(refined): + if not self._scanning: + return + freq_khz = int(freq * 1000) + bs_result = self._bridge.blind_scan(freq_khz, sr_min, sr_max, sr_step) + if bs_result and bs_result.get("locked"): + tp = { + "if_mhz": bs_result.get("freq_khz", freq_khz) / 1000.0, + "rf_mhz": if_to_rf( + bs_result.get("freq_khz", freq_khz) / 1000.0, lnb_lo + ), + "sr_ksps": bs_result.get("sr_sps", 0) // 1000, + "power_db": pwr, + "locked": True, + } + self.app.call_from_thread(self._add_transponder, tp) + + pct = 80 + (i + 1) / len(refined) * 20 + self.app.call_from_thread(self._set_progress, pct) + + self.app.call_from_thread(self._set_phase, "Scan complete", 100) + self._scanning = False + + def _set_phase(self, text: str, progress: float) -> None: + if not self.is_mounted: + return + self.query_one("#scan-phase", Static).update(f"[#00d4aa]{text}[/]") + self.query_one("#scan-pbar", ProgressBar).update(progress=progress) + + def _set_progress(self, pct: float) -> None: + if not self.is_mounted: + return + self.query_one("#scan-pbar", ProgressBar).update(progress=pct) + + def _update_spectrum(self, freqs, powers, results, lnb_lo) -> None: + if not self.is_mounted: + return + self.query_one("#scan-spectrum", SpectrumPlot).update_data( + freqs, powers, results, lnb_lo=lnb_lo, + ) + + def _add_transponder(self, tp: dict) -> None: + if not self.is_mounted: + return + self.query_one("#scan-table", FrequencyTable).add_transponder(tp) diff --git a/tui/src/skywalker_tui/screens/spectrum.py b/tui/src/skywalker_tui/screens/spectrum.py index 74a5c80..be9b05e 100644 --- a/tui/src/skywalker_tui/screens/spectrum.py +++ b/tui/src/skywalker_tui/screens/spectrum.py @@ -1,206 +1,206 @@ -"""Spectrum screen — sweep analyzer across the IF range. - -Displays a bar chart of power vs. frequency, optionally with a rolling -waterfall beneath it. Uses threaded workers for the blocking USB sweep. -""" - -from textual.app import ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import Label, Input, Button, Static, ProgressBar, Checkbox -from textual import work -from textual.worker import Worker - -from skywalker_tui.widgets.spectrum_plot import SpectrumPlot -from skywalker_tui.widgets.waterfall import WaterfallDisplay - - -class SpectrumScreen(Container): - """Spectrum analyzer with bar chart and optional waterfall.""" - - DEFAULT_CSS = """ - SpectrumScreen { - layout: vertical; - } - SpectrumScreen #spec-main { - height: 1fr; - layout: vertical; - } - SpectrumScreen #spec-progress-row { - height: 3; - layout: horizontal; - padding: 0 2; - background: #0e1018; - } - SpectrumScreen #spec-progress-row Static { - width: auto; - margin: 1 1 0 0; - } - SpectrumScreen #spec-progress-row ProgressBar { - width: 1fr; - margin: 1 1 0 0; - } - SpectrumScreen #spec-controls { - height: auto; - padding: 1 2; - background: #0e1018; - border-top: solid #1a2a3a; - layout: horizontal; - } - SpectrumScreen #spec-controls Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - SpectrumScreen #spec-controls Input { - width: 10; - margin: 0 1; - } - SpectrumScreen #spec-controls Button { - margin: 0 1; - } - SpectrumScreen #spec-controls Checkbox { - margin: 1 1 0 0; - } - """ - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._sweeping = False - self._sweep_worker: Worker | None = None - - def compose(self) -> ComposeResult: - with Vertical(id="spec-main"): - yield SpectrumPlot(title="Spectrum Analyzer", id="spec-plot") - yield WaterfallDisplay(title="Waterfall", id="spec-waterfall") - - with Horizontal(id="spec-progress-row"): - yield Static("[#506878]Ready[/]", id="spec-status") - yield ProgressBar(total=100, show_eta=False, id="spec-pbar") - - with Horizontal(id="spec-controls"): - yield Label("Start:") - yield Input("950", id="spec-start") - yield Label("Stop:") - yield Input("2150", id="spec-stop") - yield Label("Step:") - yield Input("5", id="spec-step") - yield Label("Dwell:") - yield Input("10", id="spec-dwell") - yield Label("LNB LO:") - yield Input("0", id="spec-lnb") - yield Checkbox("Continuous", id="spec-continuous") - yield Button("Sweep", id="spec-sweep-btn", variant="success") - yield Button("Stop", id="spec-stop-btn", variant="error") - - def on_show(self) -> None: - if self._bridge.is_demo and not self._sweeping: - self._start_sweep() - - def on_hide(self) -> None: - self._stop_sweep() - - def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "spec-sweep-btn": - self._start_sweep() - elif event.button.id == "spec-stop-btn": - self._stop_sweep() - - def _start_sweep(self) -> None: - if self._sweeping: - return - - # Validate inputs - try: - start = float(self.query_one("#spec-start", Input).value or "950") - stop = float(self.query_one("#spec-stop", Input).value or "2150") - step = float(self.query_one("#spec-step", Input).value or "5") - dwell = int(float(self.query_one("#spec-dwell", Input).value or "10")) - lnb_lo = float(self.query_one("#spec-lnb", Input).value or "0") - except ValueError: - self._update_status("Invalid input — check numeric fields") - return - - if not (950 <= start <= 2150) or not (950 <= stop <= 2150): - self._update_status("Frequency out of range (950–2150 MHz)") - return - if start >= stop: - self._update_status("Start must be less than Stop") - return - if not (0.1 <= step <= 500): - self._update_status("Step out of range (0.1–500 MHz)") - return - - continuous = self.query_one("#spec-continuous", Checkbox).value - self._sweeping = True - self._sweep_worker = self._do_sweep(start, stop, step, dwell, lnb_lo, continuous) - - def _stop_sweep(self) -> None: - self._sweeping = False - if self._sweep_worker: - self._sweep_worker.cancel() - self._sweep_worker = None - - @work(thread=True) - def _do_sweep(self, start: float, stop: float, step: float, - dwell: int, lnb_lo: float, continuous: bool) -> None: - """Background sweep worker.""" - import time - - try: - self._bridge.ensure_booted() - except Exception: - pass - - sweep_num = 0 - while self._sweeping: - sweep_num += 1 - step_count = [0] - - def progress_cb(freq, step_num, total, result): - step_count[0] = step_num + 1 - pct = (step_num + 1) / total * 100 - self.app.call_from_thread(self._update_progress, pct, freq, sweep_num) - - self.app.call_from_thread(self._update_status, f"Sweeping #{sweep_num}...") - - freqs, powers, results = self._bridge.sweep_spectrum( - start, stop, step, dwell, sr_ksps=20000, callback=progress_cb, - ) - - self.app.call_from_thread(self._update_plot, freqs, powers, results, lnb_lo) - self.app.call_from_thread(self._update_waterfall, powers) - - if not continuous: - break - - time.sleep(0.1) - - self._sweeping = False - self.app.call_from_thread(self._update_status, "Complete") - - def _update_progress(self, pct: float, freq: float, sweep_num: int) -> None: - if not self.is_mounted: - return - self.query_one("#spec-pbar", ProgressBar).update(progress=pct) - self.query_one("#spec-status", Static).update( - f"[#00d4aa]Sweep #{sweep_num}[/] [#506878]{freq:.0f} MHz[/]" - ) - - def _update_status(self, msg: str) -> None: - if not self.is_mounted: - return - self.query_one("#spec-status", Static).update(f"[#506878]{msg}[/]") - self.query_one("#spec-pbar", ProgressBar).update(progress=0) - - def _update_plot(self, freqs, powers, results, lnb_lo) -> None: - if not self.is_mounted: - return - self.query_one("#spec-plot", SpectrumPlot).update_data( - freqs, powers, results, lnb_lo=lnb_lo, - ) - - def _update_waterfall(self, powers) -> None: - if not self.is_mounted: - return - self.query_one("#spec-waterfall", WaterfallDisplay).add_sweep(powers) +"""Spectrum screen — sweep analyzer across the IF range. + +Displays a bar chart of power vs. frequency, optionally with a rolling +waterfall beneath it. Uses threaded workers for the blocking USB sweep. +""" + +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, Vertical +from textual.widgets import Label, Input, Button, Static, ProgressBar, Checkbox +from textual import work +from textual.worker import Worker + +from skywalker_tui.widgets.spectrum_plot import SpectrumPlot +from skywalker_tui.widgets.waterfall import WaterfallDisplay + + +class SpectrumScreen(Container): + """Spectrum analyzer with bar chart and optional waterfall.""" + + DEFAULT_CSS = """ + SpectrumScreen { + layout: vertical; + } + SpectrumScreen #spec-main { + height: 1fr; + layout: vertical; + } + SpectrumScreen #spec-progress-row { + height: 3; + layout: horizontal; + padding: 0 2; + background: #0e1018; + } + SpectrumScreen #spec-progress-row Static { + width: auto; + margin: 1 1 0 0; + } + SpectrumScreen #spec-progress-row ProgressBar { + width: 1fr; + margin: 1 1 0 0; + } + SpectrumScreen #spec-controls { + height: auto; + padding: 1 2; + background: #0e1018; + border-top: solid #1a2a3a; + layout: horizontal; + } + SpectrumScreen #spec-controls Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + SpectrumScreen #spec-controls Input { + width: 10; + margin: 0 1; + } + SpectrumScreen #spec-controls Button { + margin: 0 1; + } + SpectrumScreen #spec-controls Checkbox { + margin: 1 1 0 0; + } + """ + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._sweeping = False + self._sweep_worker: Worker | None = None + + def compose(self) -> ComposeResult: + with Vertical(id="spec-main"): + yield SpectrumPlot(title="Spectrum Analyzer", id="spec-plot") + yield WaterfallDisplay(title="Waterfall", id="spec-waterfall") + + with Horizontal(id="spec-progress-row"): + yield Static("[#506878]Ready[/]", id="spec-status") + yield ProgressBar(total=100, show_eta=False, id="spec-pbar") + + with Horizontal(id="spec-controls"): + yield Label("Start:") + yield Input("950", id="spec-start") + yield Label("Stop:") + yield Input("2150", id="spec-stop") + yield Label("Step:") + yield Input("5", id="spec-step") + yield Label("Dwell:") + yield Input("10", id="spec-dwell") + yield Label("LNB LO:") + yield Input("0", id="spec-lnb") + yield Checkbox("Continuous", id="spec-continuous") + yield Button("Sweep", id="spec-sweep-btn", variant="success") + yield Button("Stop", id="spec-stop-btn", variant="error") + + def on_show(self) -> None: + if self._bridge.is_demo and not self._sweeping: + self._start_sweep() + + def on_hide(self) -> None: + self._stop_sweep() + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "spec-sweep-btn": + self._start_sweep() + elif event.button.id == "spec-stop-btn": + self._stop_sweep() + + def _start_sweep(self) -> None: + if self._sweeping: + return + + # Validate inputs + try: + start = float(self.query_one("#spec-start", Input).value or "950") + stop = float(self.query_one("#spec-stop", Input).value or "2150") + step = float(self.query_one("#spec-step", Input).value or "5") + dwell = int(float(self.query_one("#spec-dwell", Input).value or "10")) + lnb_lo = float(self.query_one("#spec-lnb", Input).value or "0") + except ValueError: + self._update_status("Invalid input — check numeric fields") + return + + if not (950 <= start <= 2150) or not (950 <= stop <= 2150): + self._update_status("Frequency out of range (950–2150 MHz)") + return + if start >= stop: + self._update_status("Start must be less than Stop") + return + if not (0.1 <= step <= 500): + self._update_status("Step out of range (0.1–500 MHz)") + return + + continuous = self.query_one("#spec-continuous", Checkbox).value + self._sweeping = True + self._sweep_worker = self._do_sweep(start, stop, step, dwell, lnb_lo, continuous) + + def _stop_sweep(self) -> None: + self._sweeping = False + if self._sweep_worker: + self._sweep_worker.cancel() + self._sweep_worker = None + + @work(thread=True) + def _do_sweep(self, start: float, stop: float, step: float, + dwell: int, lnb_lo: float, continuous: bool) -> None: + """Background sweep worker.""" + import time + + try: + self._bridge.ensure_booted() + except Exception: + pass + + sweep_num = 0 + while self._sweeping: + sweep_num += 1 + step_count = [0] + + def progress_cb(freq, step_num, total, result): + step_count[0] = step_num + 1 + pct = (step_num + 1) / total * 100 + self.app.call_from_thread(self._update_progress, pct, freq, sweep_num) + + self.app.call_from_thread(self._update_status, f"Sweeping #{sweep_num}...") + + freqs, powers, results = self._bridge.sweep_spectrum( + start, stop, step, dwell, sr_ksps=20000, callback=progress_cb, + ) + + self.app.call_from_thread(self._update_plot, freqs, powers, results, lnb_lo) + self.app.call_from_thread(self._update_waterfall, powers) + + if not continuous: + break + + time.sleep(0.1) + + self._sweeping = False + self.app.call_from_thread(self._update_status, "Complete") + + def _update_progress(self, pct: float, freq: float, sweep_num: int) -> None: + if not self.is_mounted: + return + self.query_one("#spec-pbar", ProgressBar).update(progress=pct) + self.query_one("#spec-status", Static).update( + f"[#00d4aa]Sweep #{sweep_num}[/] [#506878]{freq:.0f} MHz[/]" + ) + + def _update_status(self, msg: str) -> None: + if not self.is_mounted: + return + self.query_one("#spec-status", Static).update(f"[#506878]{msg}[/]") + self.query_one("#spec-pbar", ProgressBar).update(progress=0) + + def _update_plot(self, freqs, powers, results, lnb_lo) -> None: + if not self.is_mounted: + return + self.query_one("#spec-plot", SpectrumPlot).update_data( + freqs, powers, results, lnb_lo=lnb_lo, + ) + + def _update_waterfall(self, powers) -> None: + if not self.is_mounted: + return + self.query_one("#spec-waterfall", WaterfallDisplay).add_sweep(powers) diff --git a/tui/src/skywalker_tui/screens/splash.py b/tui/src/skywalker_tui/screens/splash.py index 5a19ce2..563bfc5 100644 --- a/tui/src/skywalker_tui/screens/splash.py +++ b/tui/src/skywalker_tui/screens/splash.py @@ -1,151 +1,151 @@ -"""Splash screen — renders 16colo.rs art on startup. - -Pre-baked ANSI half-block art (.ans files) display instantly — no runtime -image decoding or terminal protocol detection needed. Generated by -scripts/prebake_splash.py from the original artwork. - -Randomly selects from bundled artwork on each launch. Auto-dismisses -after 5 seconds or on any keypress. -""" - -import os -import random -from pathlib import Path - -from textual.app import ComposeResult -from textual.binding import Binding -from textual.screen import Screen -from textual.containers import Vertical -from textual.widgets import Static -from rich.text import Text - - -ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets" / "splash" - -# Artwork catalog — stem (sans extension), artist, title -ART_CATALOG = [ - ("seti-satellite", "Illarterate", "S.E.T.I. Satellite"), - ("dialtone", "192.168.10.13", "Dialtone"), - ("so-far-away", "Blippypixel", "So Far Away"), - ("prodigy-out-of-space", "Jellica Jake", "Prodigy / Out of Space"), - ("space-docker", "Blippypixel", "Space Docker"), -] - - -def _is_kitty() -> bool: - """Detect if running inside Kitty terminal.""" - return bool(os.environ.get("KITTY_WINDOW_ID")) - - -class SplashScreen(Screen): - """Full-screen splash with 16colo.rs art, auto-dismisses.""" - - BINDINGS = [ - Binding("escape", "dismiss_splash", "Skip", show=False), - ] - - DEFAULT_CSS = """ - SplashScreen { - align: center middle; - background: #000000; - } - SplashScreen #splash-container { - width: 100%; - height: 100%; - align: center middle; - background: #000000; - } - SplashScreen #splash-title { - height: 1; - color: #00d4aa; - text-style: bold; - text-align: center; - dock: bottom; - margin: 0 0 2 0; - } - SplashScreen #splash-credit { - height: 1; - color: #506878; - text-align: center; - dock: bottom; - margin: 0 0 1 0; - } - SplashScreen #splash-skip { - height: 1; - color: #2a3a4a; - text-align: center; - dock: bottom; - } - SplashScreen #splash-art { - width: 100%; - height: 1fr; - content-align: center middle; - background: #000000; - } - SplashScreen #splash-fallback { - width: 100%; - height: 1fr; - content-align: center middle; - color: #00d4aa; - text-style: bold; - } - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._ans_path: Path | None = None - self._artist = "" - self._title = "" - self._select_art() - - def _select_art(self) -> None: - """Pick a random artwork from available pre-baked .ans files.""" - available = [] - for stem, artist, title in ART_CATALOG: - ans_path = ASSETS_DIR / f"{stem}.ans" - if ans_path.exists(): - available.append((ans_path, artist, title)) - - if available: - self._ans_path, self._artist, self._title = random.choice(available) - - def compose(self) -> ComposeResult: - with Vertical(id="splash-container"): - if self._ans_path: - ansi_content = self._ans_path.read_text() - yield Static(Text.from_ansi(ansi_content), id="splash-art") - else: - yield Static( - "S K Y W A L K E R - 1", - id="splash-fallback", - ) - - kitty_tag = " \U0001f431" if _is_kitty() else "" - yield Static( - f"S K Y W A L K E R - 1 / DVB-S RF Tool{kitty_tag}", - id="splash-title", - ) - if self._artist: - yield Static( - f"Art by {self._artist} \u2014 16colo.rs/mistigris", - id="splash-credit", - ) - yield Static("[any key to skip]", id="splash-skip") - - def on_mount(self) -> None: - # Brief delay before accepting key dismissal — prevents eating - # keystrokes from the transition that pushed this screen - self._accept_keys = False - self.set_timer(0.3, self._enable_keys) - self.set_timer(5, self.action_dismiss_splash) - - def _enable_keys(self) -> None: - self._accept_keys = True - - def on_key(self) -> None: - if getattr(self, "_accept_keys", False): - self.action_dismiss_splash() - - def action_dismiss_splash(self) -> None: - if self.is_current: - self.app.pop_screen() +"""Splash screen — renders 16colo.rs art on startup. + +Pre-baked ANSI half-block art (.ans files) display instantly — no runtime +image decoding or terminal protocol detection needed. Generated by +scripts/prebake_splash.py from the original artwork. + +Randomly selects from bundled artwork on each launch. Auto-dismisses +after 5 seconds or on any keypress. +""" + +import os +import random +from pathlib import Path + +from textual.app import ComposeResult +from textual.binding import Binding +from textual.screen import Screen +from textual.containers import Vertical +from textual.widgets import Static +from rich.text import Text + + +ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets" / "splash" + +# Artwork catalog — stem (sans extension), artist, title +ART_CATALOG = [ + ("seti-satellite", "Illarterate", "S.E.T.I. Satellite"), + ("dialtone", "192.168.10.13", "Dialtone"), + ("so-far-away", "Blippypixel", "So Far Away"), + ("prodigy-out-of-space", "Jellica Jake", "Prodigy / Out of Space"), + ("space-docker", "Blippypixel", "Space Docker"), +] + + +def _is_kitty() -> bool: + """Detect if running inside Kitty terminal.""" + return bool(os.environ.get("KITTY_WINDOW_ID")) + + +class SplashScreen(Screen): + """Full-screen splash with 16colo.rs art, auto-dismisses.""" + + BINDINGS = [ + Binding("escape", "dismiss_splash", "Skip", show=False), + ] + + DEFAULT_CSS = """ + SplashScreen { + align: center middle; + background: #000000; + } + SplashScreen #splash-container { + width: 100%; + height: 100%; + align: center middle; + background: #000000; + } + SplashScreen #splash-title { + height: 1; + color: #00d4aa; + text-style: bold; + text-align: center; + dock: bottom; + margin: 0 0 2 0; + } + SplashScreen #splash-credit { + height: 1; + color: #506878; + text-align: center; + dock: bottom; + margin: 0 0 1 0; + } + SplashScreen #splash-skip { + height: 1; + color: #2a3a4a; + text-align: center; + dock: bottom; + } + SplashScreen #splash-art { + width: 100%; + height: 1fr; + content-align: center middle; + background: #000000; + } + SplashScreen #splash-fallback { + width: 100%; + height: 1fr; + content-align: center middle; + color: #00d4aa; + text-style: bold; + } + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._ans_path: Path | None = None + self._artist = "" + self._title = "" + self._select_art() + + def _select_art(self) -> None: + """Pick a random artwork from available pre-baked .ans files.""" + available = [] + for stem, artist, title in ART_CATALOG: + ans_path = ASSETS_DIR / f"{stem}.ans" + if ans_path.exists(): + available.append((ans_path, artist, title)) + + if available: + self._ans_path, self._artist, self._title = random.choice(available) + + def compose(self) -> ComposeResult: + with Vertical(id="splash-container"): + if self._ans_path: + ansi_content = self._ans_path.read_text() + yield Static(Text.from_ansi(ansi_content), id="splash-art") + else: + yield Static( + "S K Y W A L K E R - 1", + id="splash-fallback", + ) + + kitty_tag = " \U0001f431" if _is_kitty() else "" + yield Static( + f"S K Y W A L K E R - 1 / DVB-S RF Tool{kitty_tag}", + id="splash-title", + ) + if self._artist: + yield Static( + f"Art by {self._artist} \u2014 16colo.rs/mistigris", + id="splash-credit", + ) + yield Static("[any key to skip]", id="splash-skip") + + def on_mount(self) -> None: + # Brief delay before accepting key dismissal — prevents eating + # keystrokes from the transition that pushed this screen + self._accept_keys = False + self.set_timer(0.3, self._enable_keys) + self.set_timer(5, self.action_dismiss_splash) + + def _enable_keys(self) -> None: + self._accept_keys = True + + def on_key(self) -> None: + if getattr(self, "_accept_keys", False): + self.action_dismiss_splash() + + def action_dismiss_splash(self) -> None: + if self.is_current: + self.app.pop_screen() diff --git a/tui/src/skywalker_tui/screens/starwars.py b/tui/src/skywalker_tui/screens/starwars.py index 9429e42..e83393d 100644 --- a/tui/src/skywalker_tui/screens/starwars.py +++ b/tui/src/skywalker_tui/screens/starwars.py @@ -1,448 +1,448 @@ -"""Star Wars Easter egg — ASCII Star Wars via telnet with offline fallback. - -Attempts to connect to towel.blinkenlights.nl:23 for the famous ASCII -Star Wars animation. If the network blocks port 23 (common), falls back -to a built-in opening crawl with ASCII art. - -The telnet stream uses VT100 escape sequences: - ESC[H — cursor home (frame boundary) - ESC[J — clear to end of screen - ESC[...m — SGR color/style codes -We detect ESC[H as "new frame", buffer the frame text, then render it -atomically via Rich's Text.from_ansi() which handles native ANSI styling. - -Hidden binding: ctrl+w (W for Wars/Walker). -""" - -import socket -import time - -from textual.app import ComposeResult -from textual.binding import Binding -from textual.screen import Screen -from textual.containers import Vertical -from textual.widgets import Static, RichLog -from textual import work -from rich.text import Text - - -TELNET_HOST = "towel.blinkenlights.nl" -TELNET_PORT = 23 -RECV_SIZE = 4096 -CONNECT_TIMEOUT = 8 - - -# ── Built-in offline crawl ──────────────────────────────────────────── -# Shown when telnet is unreachable. Frames are (lines, delay_seconds). - -_CRAWL_FRAMES = [ - # Pause on black - ([""], 2.0), - # Title card - ([ - "", - "", - "", - " . . . . . . . . .", - "", - " A long time ago in a galaxy far,", - " far away....", - "", - "", - ], 3.0), - # Clear + logo - ([ - "", - "", - r" ________________. ___ .______", - r" / | / \ | _ \ ", - r" | (-----| |----`/ ^ \ | |_) |", - r" \ \ | | / /_\ \ | /", - r" .-----) | | | / _____ \ | |\ \------.", - r" |________/ |__| /__/ \__\| _| `.________|", - "", - r" ____ __ ____ ___ .______ ________.", - r" \ \ / \ / / / \ | _ \ / |", - r" \ \/ \/ / / ^ \ | |_) || (-----`", - r" \ / / /_\ \ | / \ \ ", - r" \ /\ / / _____ \ | |\ \---) |", - r" \__/ \__/ /__/ \__\|__| `._______/", - "", - "", - ], 4.0), - # Episode info - ([ - "", - "", - "", - " Episode IV.I", - "", - " A N E W F R E Q U E N C Y", - "", - "", - ], 3.0), - # Opening crawl text — the SkyWalker-1 version - ([ - " It is a period of civil engineering.", - " Rebel hackers, armed with USB cables", - " and logic analyzers, have won their", - " first victory against the evil", - " Proprietary Firmware Empire.", - "", - " During the battle, rebel spies managed", - " to steal secret plans to the Empire's", - " ultimate weapon, the GP8PSK USB protocol,", - " a device with enough power to receive", - " an entire satellite transponder.", - "", - " Pursued by the Empire's sinister agents,", - " Princess SkyWalker races home aboard her", - " DVB-S receiver, custodian of the stolen", - " vendor commands that can save her people", - " and restore signal lock to the galaxy....", - "", - ], 0.18), # per-line scroll for this frame - # Star Destroyer ASCII art - ([ - "", - "", - "", - " . ", - " /|\\ ", - " / | \\ ", - " / | \\ ", - " / | \\ ", - " ____/ | \\____ ", - " ____/ _______|_______ \\____ ", - " ____/ ____/ | \\____ \\____", - " _____/ ___/ | \\____\\_____ ", - " /______/=====_____________|_____________=====\\______\\", - " \\ \\============|============/ /", - " \\ \\ | / /", - " \\ |__________|__________| /", - " \\ | | | /", - " \\___|__________|__________|___/", - " \\ | /", - " \\_______/ \\_______/", - "", - "", - " >>> GENPIX SKYWALKER-1 DVB-S RECEIVER <<<", - " Firmware reversed. Signal acquired.", - "", - "", - ], 3.5), - # Credits - ([ - "", - " ==========================================", - " Directed by .............. Ryan Malloy", - " Firmware by ........... Genpix Electronics", - " Reversed by ........... USB sniffers & gdb", - " Radar Scope by ........ P1 green phosphor", - " Theme Music by ........ 22 kHz tone burst", - "", - " ASCII Star Wars originally by:", - " Simon Jansen (asciimation.co.nz)", - " Sten Spans (blinkenlights.nl)", - " Mike Edwards (terminal tricks)", - "", - " Telnet blocked? Blame your ISP.", - " ==========================================", - "", - "", - " [ESC to close]", - "", - ], 0), -] - - -class _TelnetStripper: - """Stateful telnet IAC sequence stripper that handles chunk boundaries. - - Telnet IAC sequences can span TCP segment boundaries. This class - buffers partial sequences across feed() calls so they're correctly - stripped even when split across recv() chunks. - """ - - def __init__(self): - self._pending = b"" - - def feed(self, data: bytes) -> bytes: - data = self._pending + data - self._pending = b"" - result = bytearray() - i = 0 - while i < len(data): - if data[i] == 0xFF: - if i + 1 >= len(data): - self._pending = data[i:] - break - if data[i + 1] == 0xFF: # escaped 0xFF - result.append(0xFF) - i += 2 - elif data[i + 1] in (0xFB, 0xFC, 0xFD, 0xFE): # WILL/WONT/DO/DONT - if i + 2 >= len(data): - self._pending = data[i:] - break - i += 3 - elif data[i + 1] == 0xFA: # Sub-negotiation - end = data.find(b"\xff\xf0", i) - if end == -1: - self._pending = data[i:] - break - i = end + 2 - else: - i += 2 - else: - result.append(data[i]) - i += 1 - return bytes(result) - - -class StarWarsScreen(Screen): - """Modal overlay streaming ASCII Star Wars from telnet, with offline fallback.""" - - BINDINGS = [ - Binding("escape", "dismiss", "Close", show=True), - Binding("q", "dismiss", "Close", show=False), - ] - - DEFAULT_CSS = """ - StarWarsScreen { - align: center middle; - background: #000000 90%; - } - StarWarsScreen #sw-container { - width: 90%; - height: 90%; - background: #000000; - border: round #1a3050; - } - StarWarsScreen #sw-header { - height: 1; - color: #e8a020; - text-style: bold; - text-align: center; - padding: 0 1; - } - StarWarsScreen #sw-log { - height: 1fr; - background: #000000; - color: #c8d0d8; - } - StarWarsScreen #sw-footer { - height: 1; - color: #506878; - text-align: center; - } - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._socket: socket.socket | None = None - self._running = False - - def compose(self) -> ComposeResult: - with Vertical(id="sw-container"): - yield Static( - "A long time ago in a galaxy far, far away....", - id="sw-header", - ) - yield RichLog(id="sw-log", wrap=False, markup=False) - yield Static("[ESC] Close", id="sw-footer") - - def on_mount(self) -> None: - self._running = True - self._stream_starwars() - - def on_unmount(self) -> None: - self._running = False - self._close_socket() - - def action_dismiss(self) -> None: - self._running = False - self._close_socket() - self.app.pop_screen() - - def _close_socket(self) -> None: - if self._socket: - try: - self._socket.close() - except Exception: - pass - self._socket = None - - def _safe_write(self, text: str) -> None: - """Write to RichLog only if screen is still mounted and running.""" - if not self._running or not self.is_mounted: - return - try: - log = self.query_one("#sw-log", RichLog) - log.write(text) - except Exception: - pass - - def _safe_clear(self) -> None: - """Clear the RichLog.""" - if not self._running or not self.is_mounted: - return - try: - self.query_one("#sw-log", RichLog).clear() - except Exception: - pass - - @work(thread=True) - def _stream_starwars(self) -> None: - """Try telnet first, fall back to built-in crawl.""" - self.app.call_from_thread( - self._safe_write, "Connecting to towel.blinkenlights.nl:23..." - ) - - if self._try_telnet(): - return # telnet worked, we're done - - # Telnet failed — play the built-in crawl - self.app.call_from_thread(self._safe_write, "Falling back to local crawl...\n") - time.sleep(1.0) - self.app.call_from_thread(self._safe_clear) - self._play_offline_crawl() - - def _try_telnet(self) -> bool: - """Attempt telnet connection. Returns True if streaming completed. - - Uses AF_UNSPEC to try both IPv6 and IPv4 (happy eyeballs style). - Many networks route IPv6 correctly but block or drop IPv4 on port 23. - """ - try: - addrs = socket.getaddrinfo(TELNET_HOST, TELNET_PORT, - socket.AF_UNSPEC, socket.SOCK_STREAM) - if not addrs: - raise socket.gaierror("No address found") - labels = [] - for fam, _, _, _, sa in addrs: - tag = "v6" if fam == socket.AF_INET6 else "v4" - labels.append(f"{tag}:{sa[0]}") - self.app.call_from_thread( - self._safe_write, f"Resolved: {', '.join(labels)}" - ) - except socket.gaierror as e: - self.app.call_from_thread( - self._safe_write, f"DNS failed: {e}" - ) - return False - - # Try each address until one connects (IPv6 first if available) - sock = None - last_err = None - for fam, socktype, proto, _canon, sa in addrs: - tag = "v6" if fam == socket.AF_INET6 else "v4" - self.app.call_from_thread( - self._safe_write, f"Trying {tag}:{sa[0]}..." - ) - try: - s = socket.socket(fam, socktype, proto) - s.settimeout(CONNECT_TIMEOUT) - s.connect(sa) - s.settimeout(2.0) - sock = s - break - except (socket.timeout, OSError) as e: - last_err = e - try: - s.close() - except Exception: - pass - - if sock is None: - self.app.call_from_thread( - self._safe_write, f"All addresses failed: {last_err}" - ) - return False - - self._socket = sock - - self.app.call_from_thread(self._safe_write, "Connected! Streaming...\n") - - stripper = _TelnetStripper() - buffer = b"" - while self._running: - try: - chunk = sock.recv(RECV_SIZE) - if not chunk: - break - clean = stripper.feed(chunk) - buffer += clean - - # ESC[H (cursor home) marks frame boundaries. - # Buffer until we have a complete frame, then render atomically. - while b"\x1b[H" in buffer: - frame_data, buffer = buffer.split(b"\x1b[H", 1) - if frame_data.strip(): - self.app.call_from_thread( - self._render_frame, frame_data - ) - - except socket.timeout: - # Timeout with no ESC[H — flush buffer as partial frame - if buffer.strip(): - self.app.call_from_thread( - self._render_frame, buffer - ) - buffer = b"" - continue - except Exception: - break - - # Flush anything remaining - if buffer.strip(): - self.app.call_from_thread(self._render_frame, buffer) - - self.app.call_from_thread(self._safe_write, "\n[Stream ended]") - self._close_socket() - return True - - def _render_frame(self, data: bytes) -> None: - """Render a complete animation frame, replacing previous content. - - Uses Rich's Text.from_ansi() to natively handle any ANSI styling - in the stream. ESC[J (clear) is stripped since we replace the - whole frame anyway. - """ - if not self._running or not self.is_mounted: - return - try: - log = self.query_one("#sw-log", RichLog) - log.clear() - # Decode frame, strip ESC[J (redundant — we clear anyway) - text = data.replace(b"\x1b[J", b"") - text = text.decode("ascii", errors="replace") - text = text.replace("\r", "") - # Rich's from_ansi() renders ANSI color/style codes natively - log.write(Text.from_ansi(text)) - except Exception: - pass - - def _play_offline_crawl(self) -> None: - """Play the built-in Star Wars opening crawl frame by frame.""" - for lines, delay in _CRAWL_FRAMES: - if not self._running: - return - - # The opening crawl frame scrolls line-by-line - if delay > 0 and delay < 0.5 and len(lines) > 3: - # Per-line scroll mode - self.app.call_from_thread(self._safe_clear) - for line in lines: - if not self._running: - return - self.app.call_from_thread(self._safe_write, line) - time.sleep(delay) - else: - # Full-frame mode - self.app.call_from_thread(self._safe_clear) - for line in lines: - if not self._running: - return - self.app.call_from_thread(self._safe_write, line) - if delay > 0: - time.sleep(delay) +"""Star Wars Easter egg — ASCII Star Wars via telnet with offline fallback. + +Attempts to connect to towel.blinkenlights.nl:23 for the famous ASCII +Star Wars animation. If the network blocks port 23 (common), falls back +to a built-in opening crawl with ASCII art. + +The telnet stream uses VT100 escape sequences: + ESC[H — cursor home (frame boundary) + ESC[J — clear to end of screen + ESC[...m — SGR color/style codes +We detect ESC[H as "new frame", buffer the frame text, then render it +atomically via Rich's Text.from_ansi() which handles native ANSI styling. + +Hidden binding: ctrl+w (W for Wars/Walker). +""" + +import socket +import time + +from textual.app import ComposeResult +from textual.binding import Binding +from textual.screen import Screen +from textual.containers import Vertical +from textual.widgets import Static, RichLog +from textual import work +from rich.text import Text + + +TELNET_HOST = "towel.blinkenlights.nl" +TELNET_PORT = 23 +RECV_SIZE = 4096 +CONNECT_TIMEOUT = 8 + + +# ── Built-in offline crawl ──────────────────────────────────────────── +# Shown when telnet is unreachable. Frames are (lines, delay_seconds). + +_CRAWL_FRAMES = [ + # Pause on black + ([""], 2.0), + # Title card + ([ + "", + "", + "", + " . . . . . . . . .", + "", + " A long time ago in a galaxy far,", + " far away....", + "", + "", + ], 3.0), + # Clear + logo + ([ + "", + "", + r" ________________. ___ .______", + r" / | / \ | _ \ ", + r" | (-----| |----`/ ^ \ | |_) |", + r" \ \ | | / /_\ \ | /", + r" .-----) | | | / _____ \ | |\ \------.", + r" |________/ |__| /__/ \__\| _| `.________|", + "", + r" ____ __ ____ ___ .______ ________.", + r" \ \ / \ / / / \ | _ \ / |", + r" \ \/ \/ / / ^ \ | |_) || (-----`", + r" \ / / /_\ \ | / \ \ ", + r" \ /\ / / _____ \ | |\ \---) |", + r" \__/ \__/ /__/ \__\|__| `._______/", + "", + "", + ], 4.0), + # Episode info + ([ + "", + "", + "", + " Episode IV.I", + "", + " A N E W F R E Q U E N C Y", + "", + "", + ], 3.0), + # Opening crawl text — the SkyWalker-1 version + ([ + " It is a period of civil engineering.", + " Rebel hackers, armed with USB cables", + " and logic analyzers, have won their", + " first victory against the evil", + " Proprietary Firmware Empire.", + "", + " During the battle, rebel spies managed", + " to steal secret plans to the Empire's", + " ultimate weapon, the GP8PSK USB protocol,", + " a device with enough power to receive", + " an entire satellite transponder.", + "", + " Pursued by the Empire's sinister agents,", + " Princess SkyWalker races home aboard her", + " DVB-S receiver, custodian of the stolen", + " vendor commands that can save her people", + " and restore signal lock to the galaxy....", + "", + ], 0.18), # per-line scroll for this frame + # Star Destroyer ASCII art + ([ + "", + "", + "", + " . ", + " /|\\ ", + " / | \\ ", + " / | \\ ", + " / | \\ ", + " ____/ | \\____ ", + " ____/ _______|_______ \\____ ", + " ____/ ____/ | \\____ \\____", + " _____/ ___/ | \\____\\_____ ", + " /______/=====_____________|_____________=====\\______\\", + " \\ \\============|============/ /", + " \\ \\ | / /", + " \\ |__________|__________| /", + " \\ | | | /", + " \\___|__________|__________|___/", + " \\ | /", + " \\_______/ \\_______/", + "", + "", + " >>> GENPIX SKYWALKER-1 DVB-S RECEIVER <<<", + " Firmware reversed. Signal acquired.", + "", + "", + ], 3.5), + # Credits + ([ + "", + " ==========================================", + " Directed by .............. Ryan Malloy", + " Firmware by ........... Genpix Electronics", + " Reversed by ........... USB sniffers & gdb", + " Radar Scope by ........ P1 green phosphor", + " Theme Music by ........ 22 kHz tone burst", + "", + " ASCII Star Wars originally by:", + " Simon Jansen (asciimation.co.nz)", + " Sten Spans (blinkenlights.nl)", + " Mike Edwards (terminal tricks)", + "", + " Telnet blocked? Blame your ISP.", + " ==========================================", + "", + "", + " [ESC to close]", + "", + ], 0), +] + + +class _TelnetStripper: + """Stateful telnet IAC sequence stripper that handles chunk boundaries. + + Telnet IAC sequences can span TCP segment boundaries. This class + buffers partial sequences across feed() calls so they're correctly + stripped even when split across recv() chunks. + """ + + def __init__(self): + self._pending = b"" + + def feed(self, data: bytes) -> bytes: + data = self._pending + data + self._pending = b"" + result = bytearray() + i = 0 + while i < len(data): + if data[i] == 0xFF: + if i + 1 >= len(data): + self._pending = data[i:] + break + if data[i + 1] == 0xFF: # escaped 0xFF + result.append(0xFF) + i += 2 + elif data[i + 1] in (0xFB, 0xFC, 0xFD, 0xFE): # WILL/WONT/DO/DONT + if i + 2 >= len(data): + self._pending = data[i:] + break + i += 3 + elif data[i + 1] == 0xFA: # Sub-negotiation + end = data.find(b"\xff\xf0", i) + if end == -1: + self._pending = data[i:] + break + i = end + 2 + else: + i += 2 + else: + result.append(data[i]) + i += 1 + return bytes(result) + + +class StarWarsScreen(Screen): + """Modal overlay streaming ASCII Star Wars from telnet, with offline fallback.""" + + BINDINGS = [ + Binding("escape", "dismiss", "Close", show=True), + Binding("q", "dismiss", "Close", show=False), + ] + + DEFAULT_CSS = """ + StarWarsScreen { + align: center middle; + background: #000000 90%; + } + StarWarsScreen #sw-container { + width: 90%; + height: 90%; + background: #000000; + border: round #1a3050; + } + StarWarsScreen #sw-header { + height: 1; + color: #e8a020; + text-style: bold; + text-align: center; + padding: 0 1; + } + StarWarsScreen #sw-log { + height: 1fr; + background: #000000; + color: #c8d0d8; + } + StarWarsScreen #sw-footer { + height: 1; + color: #506878; + text-align: center; + } + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._socket: socket.socket | None = None + self._running = False + + def compose(self) -> ComposeResult: + with Vertical(id="sw-container"): + yield Static( + "A long time ago in a galaxy far, far away....", + id="sw-header", + ) + yield RichLog(id="sw-log", wrap=False, markup=False) + yield Static("[ESC] Close", id="sw-footer") + + def on_mount(self) -> None: + self._running = True + self._stream_starwars() + + def on_unmount(self) -> None: + self._running = False + self._close_socket() + + def action_dismiss(self) -> None: + self._running = False + self._close_socket() + self.app.pop_screen() + + def _close_socket(self) -> None: + if self._socket: + try: + self._socket.close() + except Exception: + pass + self._socket = None + + def _safe_write(self, text: str) -> None: + """Write to RichLog only if screen is still mounted and running.""" + if not self._running or not self.is_mounted: + return + try: + log = self.query_one("#sw-log", RichLog) + log.write(text) + except Exception: + pass + + def _safe_clear(self) -> None: + """Clear the RichLog.""" + if not self._running or not self.is_mounted: + return + try: + self.query_one("#sw-log", RichLog).clear() + except Exception: + pass + + @work(thread=True) + def _stream_starwars(self) -> None: + """Try telnet first, fall back to built-in crawl.""" + self.app.call_from_thread( + self._safe_write, "Connecting to towel.blinkenlights.nl:23..." + ) + + if self._try_telnet(): + return # telnet worked, we're done + + # Telnet failed — play the built-in crawl + self.app.call_from_thread(self._safe_write, "Falling back to local crawl...\n") + time.sleep(1.0) + self.app.call_from_thread(self._safe_clear) + self._play_offline_crawl() + + def _try_telnet(self) -> bool: + """Attempt telnet connection. Returns True if streaming completed. + + Uses AF_UNSPEC to try both IPv6 and IPv4 (happy eyeballs style). + Many networks route IPv6 correctly but block or drop IPv4 on port 23. + """ + try: + addrs = socket.getaddrinfo(TELNET_HOST, TELNET_PORT, + socket.AF_UNSPEC, socket.SOCK_STREAM) + if not addrs: + raise socket.gaierror("No address found") + labels = [] + for fam, _, _, _, sa in addrs: + tag = "v6" if fam == socket.AF_INET6 else "v4" + labels.append(f"{tag}:{sa[0]}") + self.app.call_from_thread( + self._safe_write, f"Resolved: {', '.join(labels)}" + ) + except socket.gaierror as e: + self.app.call_from_thread( + self._safe_write, f"DNS failed: {e}" + ) + return False + + # Try each address until one connects (IPv6 first if available) + sock = None + last_err = None + for fam, socktype, proto, _canon, sa in addrs: + tag = "v6" if fam == socket.AF_INET6 else "v4" + self.app.call_from_thread( + self._safe_write, f"Trying {tag}:{sa[0]}..." + ) + try: + s = socket.socket(fam, socktype, proto) + s.settimeout(CONNECT_TIMEOUT) + s.connect(sa) + s.settimeout(2.0) + sock = s + break + except (socket.timeout, OSError) as e: + last_err = e + try: + s.close() + except Exception: + pass + + if sock is None: + self.app.call_from_thread( + self._safe_write, f"All addresses failed: {last_err}" + ) + return False + + self._socket = sock + + self.app.call_from_thread(self._safe_write, "Connected! Streaming...\n") + + stripper = _TelnetStripper() + buffer = b"" + while self._running: + try: + chunk = sock.recv(RECV_SIZE) + if not chunk: + break + clean = stripper.feed(chunk) + buffer += clean + + # ESC[H (cursor home) marks frame boundaries. + # Buffer until we have a complete frame, then render atomically. + while b"\x1b[H" in buffer: + frame_data, buffer = buffer.split(b"\x1b[H", 1) + if frame_data.strip(): + self.app.call_from_thread( + self._render_frame, frame_data + ) + + except socket.timeout: + # Timeout with no ESC[H — flush buffer as partial frame + if buffer.strip(): + self.app.call_from_thread( + self._render_frame, buffer + ) + buffer = b"" + continue + except Exception: + break + + # Flush anything remaining + if buffer.strip(): + self.app.call_from_thread(self._render_frame, buffer) + + self.app.call_from_thread(self._safe_write, "\n[Stream ended]") + self._close_socket() + return True + + def _render_frame(self, data: bytes) -> None: + """Render a complete animation frame, replacing previous content. + + Uses Rich's Text.from_ansi() to natively handle any ANSI styling + in the stream. ESC[J (clear) is stripped since we replace the + whole frame anyway. + """ + if not self._running or not self.is_mounted: + return + try: + log = self.query_one("#sw-log", RichLog) + log.clear() + # Decode frame, strip ESC[J (redundant — we clear anyway) + text = data.replace(b"\x1b[J", b"") + text = text.decode("ascii", errors="replace") + text = text.replace("\r", "") + # Rich's from_ansi() renders ANSI color/style codes natively + log.write(Text.from_ansi(text)) + except Exception: + pass + + def _play_offline_crawl(self) -> None: + """Play the built-in Star Wars opening crawl frame by frame.""" + for lines, delay in _CRAWL_FRAMES: + if not self._running: + return + + # The opening crawl frame scrolls line-by-line + if delay > 0 and delay < 0.5 and len(lines) > 3: + # Per-line scroll mode + self.app.call_from_thread(self._safe_clear) + for line in lines: + if not self._running: + return + self.app.call_from_thread(self._safe_write, line) + time.sleep(delay) + else: + # Full-frame mode + self.app.call_from_thread(self._safe_clear) + for line in lines: + if not self._running: + return + self.app.call_from_thread(self._safe_write, line) + if delay > 0: + time.sleep(delay) diff --git a/tui/src/skywalker_tui/screens/stream.py b/tui/src/skywalker_tui/screens/stream.py index cc3c4a7..298db0f 100644 --- a/tui/src/skywalker_tui/screens/stream.py +++ b/tui/src/skywalker_tui/screens/stream.py @@ -1,401 +1,401 @@ -"""Stream screen -- live MPEG-2 transport stream capture and analysis. - -Reads raw TS data from the SkyWalker-1 bulk endpoint, parses 188-byte -packets in real time, and displays PID distribution statistics alongside -a hierarchical PSI (PAT/PMT) program structure tree. - -Supports file capture mode for saving raw .ts files to disk. - -arm_transfer(on) is always paired in try/finally to guarantee the USB -bulk endpoint is disarmed when monitoring stops. -""" - -import time - -from textual.app import ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import Label, Input, Button, Static -from textual import work -from textual.worker import Worker - -from ts_analyze import ( - TSPacket, PSIParser, parse_pat, parse_pmt, - KNOWN_PIDS, TS_PACKET_SIZE, -) - -from skywalker_tui.widgets.pid_table import PidTable -from skywalker_tui.widgets.psi_tree import PsiTree - - -class StreamScreen(Container): - """Live MPEG-2 TS monitor with PID analysis and PSI tree.""" - - DEFAULT_CSS = """ - StreamScreen { - layout: vertical; - } - StreamScreen #stream-main { - height: 1fr; - layout: horizontal; - } - StreamScreen #stream-pid-col { - width: 3fr; - height: 1fr; - padding: 1; - } - StreamScreen #stream-psi-col { - width: 2fr; - height: 1fr; - padding: 1; - } - StreamScreen #stream-controls { - height: auto; - padding: 1 2; - background: #0e1018; - border-top: solid #1a2a3a; - layout: horizontal; - } - StreamScreen #stream-controls Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - StreamScreen #stream-controls Input { - width: 14; - margin: 0 1; - } - StreamScreen #stream-controls Button { - margin: 0 1; - } - StreamScreen #stream-stats { - height: 3; - layout: horizontal; - padding: 0 2; - } - StreamScreen #stream-stats Static { - width: 1fr; - height: 3; - content-align: center middle; - background: #121c2a; - border: round #1a3050; - margin: 0 1 0 0; - } - """ - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._monitoring = False - self._capturing = False - self._capture_file = None - self._monitor_worker: Worker | None = None - - # Accumulated stats (written by worker thread, read by UI thread) - self._total_packets = 0 - self._total_bytes = 0 - self._tei_count = 0 - self._pid_counts: dict[int, int] = {} - self._cc_last: dict[int, int] = {} - self._cc_errors: dict[int, int] = {} - self._start_time = 0.0 - - def compose(self) -> ComposeResult: - with Horizontal(id="stream-main"): - with Vertical(id="stream-pid-col"): - yield Static( - "[bold #00d4aa]PID Distribution[/]", classes="panel-title" - ) - yield PidTable(id="stream-pid-table") - with Vertical(id="stream-psi-col"): - yield Static( - "[bold #00d4aa]Program Structure[/]", classes="panel-title" - ) - yield PsiTree(id="stream-psi-tree") - with Horizontal(id="stream-stats"): - yield Static("[#506878]Packets:[/] [#00d4aa]0[/]", id="stat-packets") - yield Static("[#506878]Bytes:[/] [#00d4aa]0[/]", id="stat-bytes") - yield Static("[#506878]PIDs:[/] [#00d4aa]0[/]", id="stat-pids") - yield Static("[#506878]CC Errors:[/] [#00d4aa]0[/]", id="stat-cc") - yield Static( - "[#506878]Duration:[/] [#00d4aa]0.0s[/]", id="stat-duration" - ) - with Horizontal(id="stream-controls"): - yield Button("Start Monitor", id="stream-start", variant="success") - yield Button("Stop", id="stream-stop", variant="error") - yield Label("Capture:") - yield Input("capture.ts", id="stream-capture-file") - yield Button("Capture", id="stream-capture", variant="warning") - yield Static("[#506878]Idle[/]", id="stream-status") - - def on_show(self) -> None: - """Auto-start monitoring in demo mode when this screen becomes visible.""" - if self._bridge.is_demo and not self._monitoring: - self._start_monitor() - - def on_hide(self) -> None: - """Stop monitoring when navigating away from this screen.""" - self._stop_monitor() - - def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "stream-start": - self._start_monitor() - elif event.button.id == "stream-stop": - self._stop_monitor() - elif event.button.id == "stream-capture": - self._toggle_capture() - - # -- Monitor lifecycle -- - - def _start_monitor(self) -> None: - """Begin streaming from the device bulk endpoint.""" - if self._monitoring: - return - self._monitoring = True - self._reset_stats() - self._start_time = time.monotonic() - try: - self.query_one("#stream-status", Static).update( - "[bold #00d4aa]Monitoring[/]" - ) - except Exception: - pass - self._monitor_worker = self._do_monitor() - - def _stop_monitor(self) -> None: - """Stop the monitor worker and clean up capture state.""" - self._monitoring = False - self._capturing = False - # Cancel worker BEFORE closing file — worker may still be writing - if self._monitor_worker is not None: - self._monitor_worker.cancel() - self._monitor_worker = None - if self._capture_file is not None: - self._capture_file.close() - self._capture_file = None - try: - self.query_one("#stream-status", Static).update( - "[#506878]Stopped[/]" - ) - except Exception: - pass - - def _toggle_capture(self) -> None: - """Toggle file capture on/off. Starts monitor if not already running.""" - if self._capturing: - self._capturing = False - if self._capture_file is not None: - self._capture_file.close() - self._capture_file = None - try: - self.query_one("#stream-capture", Button).label = "Capture" - except Exception: - pass - return - - # Start capture - if not self._monitoring: - self._start_monitor() - filename = self.query_one("#stream-capture-file", Input).value or "capture.ts" - try: - self._capture_file = open(filename, "wb") - self._capturing = True - self.query_one("#stream-capture", Button).label = "Stop Capture" - except OSError as e: - self.app.notify(f"Cannot open {filename}: {e}", severity="error") - - def _reset_stats(self) -> None: - """Zero all counters and clear display widgets.""" - self._total_packets = 0 - self._total_bytes = 0 - self._tei_count = 0 - self._pid_counts.clear() - self._cc_last.clear() - self._cc_errors.clear() - try: - self.query_one("#stream-pid-table", PidTable).clear_table() - self.query_one("#stream-psi-tree", PsiTree).clear_tree() - except Exception: - pass - - # -- Background worker -- - - @work(thread=True) - def _do_monitor(self) -> None: - """Background worker: read TS stream, parse packets, post UI updates. - - Runs in a dedicated thread via Textual's @work(thread=True) decorator. - Calls arm_transfer(True) on entry and arm_transfer(False) in finally - to guarantee the bulk endpoint is always disarmed on exit. - """ - psi_pat = PSIParser() - psi_pmt = PSIParser() - pat = None - pmt_pids: set[int] = set() - last_ui_update = 0.0 - - try: - self._bridge.ensure_booted() - self._bridge.arm_transfer(True) - - while self._monitoring: - data = self._bridge.read_stream(8192, timeout=1000) - if not data: - time.sleep(0.05) - continue - - self._total_bytes += len(data) - - # Write raw data to capture file if active - if self._capturing and self._capture_file is not None: - try: - self._capture_file.write(data) - except OSError: - self._capturing = False - - # Parse 188-byte TS packets from the chunk - offset = 0 - while offset + TS_PACKET_SIZE <= len(data): - if data[offset] != 0x47: - # Lost sync, scan forward for next sync byte - offset += 1 - continue - - try: - pkt = TSPacket(data[offset:offset + TS_PACKET_SIZE]) - except (ValueError, IndexError): - offset += 1 - continue - - offset += TS_PACKET_SIZE - self._total_packets += 1 - pid = pkt.pid - - # PID counting - self._pid_counts[pid] = self._pid_counts.get(pid, 0) + 1 - - # TEI (Transport Error Indicator) check - if pkt.tei: - self._tei_count += 1 - - # Continuity counter check (payload-bearing, non-null PIDs) - if pkt.adaptation & 0x01 and pid != 0x1FFF: - if pid in self._cc_last: - expected = (self._cc_last[pid] + 1) & 0x0F - if (pkt.continuity != expected - and pkt.continuity != self._cc_last[pid]): - self._cc_errors[pid] = ( - self._cc_errors.get(pid, 0) + 1 - ) - self._cc_last[pid] = pkt.continuity - - # PAT parsing (PID 0x0000) - if pid == 0x0000: - section = psi_pat.feed(pkt) - if section is not None: - parsed = parse_pat(section) - if parsed is not None: - pat = parsed - for prog_num, pmt_pid in pat.get( - "programs", {} - ).items(): - if prog_num != 0: - pmt_pids.add(pmt_pid) - - # PMT parsing (PIDs discovered from PAT) - if pid in pmt_pids: - section = psi_pmt.feed(pkt) - if section is not None: - parsed = parse_pmt(section) - if parsed is not None: - self.app.call_from_thread( - self._update_pmt, pid, parsed - ) - - # Batch UI updates every 500ms to avoid flooding the event loop - now = time.monotonic() - if now - last_ui_update >= 0.5: - last_ui_update = now - self.app.call_from_thread( - self._update_ui, - pat, - dict(self._pid_counts), - dict(self._cc_errors), - self._total_packets, - self._total_bytes, - self._tei_count, - ) - - finally: - for _attempt in range(2): - try: - self._bridge.arm_transfer(False) - break - except Exception: - time.sleep(0.1) - - # -- UI update methods (called from main thread) -- - - def _update_ui( - self, - pat: dict | None, - pid_counts: dict[int, int], - cc_errors: dict[int, int], - total_pkts: int, - total_bytes: int, - tei_count: int, - ) -> None: - """Push accumulated stats to display widgets.""" - if not self.is_mounted: - return - - # Build known PID names by merging standard table with PAT-discovered PMTs - known = dict(KNOWN_PIDS) - if pat: - for prog_num, pmt_pid in pat.get("programs", {}).items(): - if prog_num == 0: - known[pmt_pid] = "NIT" - else: - known[pmt_pid] = f"PMT (prog {prog_num})" - - self.query_one("#stream-pid-table", PidTable).update_pids( - pid_counts, cc_errors, total_pkts, known - ) - - if pat: - self.query_one("#stream-psi-tree", PsiTree).update_pat(pat) - - total_cc = sum(cc_errors.values()) - duration = time.monotonic() - self._start_time - - self.query_one("#stat-packets", Static).update( - f"[#506878]Packets:[/] [#00d4aa]{total_pkts:,}[/]" - ) - - if total_bytes >= 1_000_000: - bytes_str = f"{total_bytes / 1_000_000:.1f} MB" - elif total_bytes >= 1_000: - bytes_str = f"{total_bytes / 1_000:.1f} KB" - else: - bytes_str = str(total_bytes) - self.query_one("#stat-bytes", Static).update( - f"[#506878]Bytes:[/] [#00d4aa]{bytes_str}[/]" - ) - - self.query_one("#stat-pids", Static).update( - f"[#506878]PIDs:[/] [#00d4aa]{len(pid_counts)}[/]" - ) - - cc_color = "#e04040" if total_cc > 0 else "#00d4aa" - self.query_one("#stat-cc", Static).update( - f"[#506878]CC Errors:[/] [{cc_color}]{total_cc}[/]" - ) - - self.query_one("#stat-duration", Static).update( - f"[#506878]Duration:[/] [#00d4aa]{duration:.1f}s[/]" - ) - - def _update_pmt(self, pmt_pid: int, pmt: dict) -> None: - """Push a newly parsed PMT to the PSI tree widget.""" - if not self.is_mounted: - return - self.query_one("#stream-psi-tree", PsiTree).update_pmt(pmt_pid, pmt) +"""Stream screen -- live MPEG-2 transport stream capture and analysis. + +Reads raw TS data from the SkyWalker-1 bulk endpoint, parses 188-byte +packets in real time, and displays PID distribution statistics alongside +a hierarchical PSI (PAT/PMT) program structure tree. + +Supports file capture mode for saving raw .ts files to disk. + +arm_transfer(on) is always paired in try/finally to guarantee the USB +bulk endpoint is disarmed when monitoring stops. +""" + +import time + +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, Vertical +from textual.widgets import Label, Input, Button, Static +from textual import work +from textual.worker import Worker + +from ts_analyze import ( + TSPacket, PSIParser, parse_pat, parse_pmt, + KNOWN_PIDS, TS_PACKET_SIZE, +) + +from skywalker_tui.widgets.pid_table import PidTable +from skywalker_tui.widgets.psi_tree import PsiTree + + +class StreamScreen(Container): + """Live MPEG-2 TS monitor with PID analysis and PSI tree.""" + + DEFAULT_CSS = """ + StreamScreen { + layout: vertical; + } + StreamScreen #stream-main { + height: 1fr; + layout: horizontal; + } + StreamScreen #stream-pid-col { + width: 3fr; + height: 1fr; + padding: 1; + } + StreamScreen #stream-psi-col { + width: 2fr; + height: 1fr; + padding: 1; + } + StreamScreen #stream-controls { + height: auto; + padding: 1 2; + background: #0e1018; + border-top: solid #1a2a3a; + layout: horizontal; + } + StreamScreen #stream-controls Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + StreamScreen #stream-controls Input { + width: 14; + margin: 0 1; + } + StreamScreen #stream-controls Button { + margin: 0 1; + } + StreamScreen #stream-stats { + height: 3; + layout: horizontal; + padding: 0 2; + } + StreamScreen #stream-stats Static { + width: 1fr; + height: 3; + content-align: center middle; + background: #121c2a; + border: round #1a3050; + margin: 0 1 0 0; + } + """ + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._monitoring = False + self._capturing = False + self._capture_file = None + self._monitor_worker: Worker | None = None + + # Accumulated stats (written by worker thread, read by UI thread) + self._total_packets = 0 + self._total_bytes = 0 + self._tei_count = 0 + self._pid_counts: dict[int, int] = {} + self._cc_last: dict[int, int] = {} + self._cc_errors: dict[int, int] = {} + self._start_time = 0.0 + + def compose(self) -> ComposeResult: + with Horizontal(id="stream-main"): + with Vertical(id="stream-pid-col"): + yield Static( + "[bold #00d4aa]PID Distribution[/]", classes="panel-title" + ) + yield PidTable(id="stream-pid-table") + with Vertical(id="stream-psi-col"): + yield Static( + "[bold #00d4aa]Program Structure[/]", classes="panel-title" + ) + yield PsiTree(id="stream-psi-tree") + with Horizontal(id="stream-stats"): + yield Static("[#506878]Packets:[/] [#00d4aa]0[/]", id="stat-packets") + yield Static("[#506878]Bytes:[/] [#00d4aa]0[/]", id="stat-bytes") + yield Static("[#506878]PIDs:[/] [#00d4aa]0[/]", id="stat-pids") + yield Static("[#506878]CC Errors:[/] [#00d4aa]0[/]", id="stat-cc") + yield Static( + "[#506878]Duration:[/] [#00d4aa]0.0s[/]", id="stat-duration" + ) + with Horizontal(id="stream-controls"): + yield Button("Start Monitor", id="stream-start", variant="success") + yield Button("Stop", id="stream-stop", variant="error") + yield Label("Capture:") + yield Input("capture.ts", id="stream-capture-file") + yield Button("Capture", id="stream-capture", variant="warning") + yield Static("[#506878]Idle[/]", id="stream-status") + + def on_show(self) -> None: + """Auto-start monitoring in demo mode when this screen becomes visible.""" + if self._bridge.is_demo and not self._monitoring: + self._start_monitor() + + def on_hide(self) -> None: + """Stop monitoring when navigating away from this screen.""" + self._stop_monitor() + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "stream-start": + self._start_monitor() + elif event.button.id == "stream-stop": + self._stop_monitor() + elif event.button.id == "stream-capture": + self._toggle_capture() + + # -- Monitor lifecycle -- + + def _start_monitor(self) -> None: + """Begin streaming from the device bulk endpoint.""" + if self._monitoring: + return + self._monitoring = True + self._reset_stats() + self._start_time = time.monotonic() + try: + self.query_one("#stream-status", Static).update( + "[bold #00d4aa]Monitoring[/]" + ) + except Exception: + pass + self._monitor_worker = self._do_monitor() + + def _stop_monitor(self) -> None: + """Stop the monitor worker and clean up capture state.""" + self._monitoring = False + self._capturing = False + # Cancel worker BEFORE closing file — worker may still be writing + if self._monitor_worker is not None: + self._monitor_worker.cancel() + self._monitor_worker = None + if self._capture_file is not None: + self._capture_file.close() + self._capture_file = None + try: + self.query_one("#stream-status", Static).update( + "[#506878]Stopped[/]" + ) + except Exception: + pass + + def _toggle_capture(self) -> None: + """Toggle file capture on/off. Starts monitor if not already running.""" + if self._capturing: + self._capturing = False + if self._capture_file is not None: + self._capture_file.close() + self._capture_file = None + try: + self.query_one("#stream-capture", Button).label = "Capture" + except Exception: + pass + return + + # Start capture + if not self._monitoring: + self._start_monitor() + filename = self.query_one("#stream-capture-file", Input).value or "capture.ts" + try: + self._capture_file = open(filename, "wb") + self._capturing = True + self.query_one("#stream-capture", Button).label = "Stop Capture" + except OSError as e: + self.app.notify(f"Cannot open {filename}: {e}", severity="error") + + def _reset_stats(self) -> None: + """Zero all counters and clear display widgets.""" + self._total_packets = 0 + self._total_bytes = 0 + self._tei_count = 0 + self._pid_counts.clear() + self._cc_last.clear() + self._cc_errors.clear() + try: + self.query_one("#stream-pid-table", PidTable).clear_table() + self.query_one("#stream-psi-tree", PsiTree).clear_tree() + except Exception: + pass + + # -- Background worker -- + + @work(thread=True) + def _do_monitor(self) -> None: + """Background worker: read TS stream, parse packets, post UI updates. + + Runs in a dedicated thread via Textual's @work(thread=True) decorator. + Calls arm_transfer(True) on entry and arm_transfer(False) in finally + to guarantee the bulk endpoint is always disarmed on exit. + """ + psi_pat = PSIParser() + psi_pmt = PSIParser() + pat = None + pmt_pids: set[int] = set() + last_ui_update = 0.0 + + try: + self._bridge.ensure_booted() + self._bridge.arm_transfer(True) + + while self._monitoring: + data = self._bridge.read_stream(8192, timeout=1000) + if not data: + time.sleep(0.05) + continue + + self._total_bytes += len(data) + + # Write raw data to capture file if active + if self._capturing and self._capture_file is not None: + try: + self._capture_file.write(data) + except OSError: + self._capturing = False + + # Parse 188-byte TS packets from the chunk + offset = 0 + while offset + TS_PACKET_SIZE <= len(data): + if data[offset] != 0x47: + # Lost sync, scan forward for next sync byte + offset += 1 + continue + + try: + pkt = TSPacket(data[offset:offset + TS_PACKET_SIZE]) + except (ValueError, IndexError): + offset += 1 + continue + + offset += TS_PACKET_SIZE + self._total_packets += 1 + pid = pkt.pid + + # PID counting + self._pid_counts[pid] = self._pid_counts.get(pid, 0) + 1 + + # TEI (Transport Error Indicator) check + if pkt.tei: + self._tei_count += 1 + + # Continuity counter check (payload-bearing, non-null PIDs) + if pkt.adaptation & 0x01 and pid != 0x1FFF: + if pid in self._cc_last: + expected = (self._cc_last[pid] + 1) & 0x0F + if (pkt.continuity != expected + and pkt.continuity != self._cc_last[pid]): + self._cc_errors[pid] = ( + self._cc_errors.get(pid, 0) + 1 + ) + self._cc_last[pid] = pkt.continuity + + # PAT parsing (PID 0x0000) + if pid == 0x0000: + section = psi_pat.feed(pkt) + if section is not None: + parsed = parse_pat(section) + if parsed is not None: + pat = parsed + for prog_num, pmt_pid in pat.get( + "programs", {} + ).items(): + if prog_num != 0: + pmt_pids.add(pmt_pid) + + # PMT parsing (PIDs discovered from PAT) + if pid in pmt_pids: + section = psi_pmt.feed(pkt) + if section is not None: + parsed = parse_pmt(section) + if parsed is not None: + self.app.call_from_thread( + self._update_pmt, pid, parsed + ) + + # Batch UI updates every 500ms to avoid flooding the event loop + now = time.monotonic() + if now - last_ui_update >= 0.5: + last_ui_update = now + self.app.call_from_thread( + self._update_ui, + pat, + dict(self._pid_counts), + dict(self._cc_errors), + self._total_packets, + self._total_bytes, + self._tei_count, + ) + + finally: + for _attempt in range(2): + try: + self._bridge.arm_transfer(False) + break + except Exception: + time.sleep(0.1) + + # -- UI update methods (called from main thread) -- + + def _update_ui( + self, + pat: dict | None, + pid_counts: dict[int, int], + cc_errors: dict[int, int], + total_pkts: int, + total_bytes: int, + tei_count: int, + ) -> None: + """Push accumulated stats to display widgets.""" + if not self.is_mounted: + return + + # Build known PID names by merging standard table with PAT-discovered PMTs + known = dict(KNOWN_PIDS) + if pat: + for prog_num, pmt_pid in pat.get("programs", {}).items(): + if prog_num == 0: + known[pmt_pid] = "NIT" + else: + known[pmt_pid] = f"PMT (prog {prog_num})" + + self.query_one("#stream-pid-table", PidTable).update_pids( + pid_counts, cc_errors, total_pkts, known + ) + + if pat: + self.query_one("#stream-psi-tree", PsiTree).update_pat(pat) + + total_cc = sum(cc_errors.values()) + duration = time.monotonic() - self._start_time + + self.query_one("#stat-packets", Static).update( + f"[#506878]Packets:[/] [#00d4aa]{total_pkts:,}[/]" + ) + + if total_bytes >= 1_000_000: + bytes_str = f"{total_bytes / 1_000_000:.1f} MB" + elif total_bytes >= 1_000: + bytes_str = f"{total_bytes / 1_000:.1f} KB" + else: + bytes_str = str(total_bytes) + self.query_one("#stat-bytes", Static).update( + f"[#506878]Bytes:[/] [#00d4aa]{bytes_str}[/]" + ) + + self.query_one("#stat-pids", Static).update( + f"[#506878]PIDs:[/] [#00d4aa]{len(pid_counts)}[/]" + ) + + cc_color = "#e04040" if total_cc > 0 else "#00d4aa" + self.query_one("#stat-cc", Static).update( + f"[#506878]CC Errors:[/] [{cc_color}]{total_cc}[/]" + ) + + self.query_one("#stat-duration", Static).update( + f"[#506878]Duration:[/] [#00d4aa]{duration:.1f}s[/]" + ) + + def _update_pmt(self, pmt_pid: int, pmt: dict) -> None: + """Push a newly parsed PMT to the PSI tree widget.""" + if not self.is_mounted: + return + self.query_one("#stream-psi-tree", PsiTree).update_pmt(pmt_pid, pmt) diff --git a/tui/src/skywalker_tui/screens/survey.py b/tui/src/skywalker_tui/screens/survey.py index 9903fdb..6b47034 100644 --- a/tui/src/skywalker_tui/screens/survey.py +++ b/tui/src/skywalker_tui/screens/survey.py @@ -1,593 +1,593 @@ -"""Survey screen — carrier survey + QO-100 DATV reception (tabbed). - -Two tabs within the Survey screen: -- Full Band: automated carrier discovery across the entire IF range -- QO-100: focused on the Es'hail-2 wideband DATV transponder - -Both share a spectrum visualization + results table pattern. -""" - -from textual.app import ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import ( - Label, Input, Button, Static, ProgressBar, - TabbedContent, TabPane, -) -from textual import work -from textual.worker import Worker - -from skywalker_tui.widgets.spectrum_plot import SpectrumPlot -from skywalker_tui.widgets.frequency_table import FrequencyTable - - -# QO-100 IF range for common LNB LOs -_QO100_WB_RF_START = 10491.0 -_QO100_WB_RF_STOP = 10499.0 - - -class SurveyScreen(Container): - """Carrier survey with full-band and QO-100 tabs.""" - - DEFAULT_CSS = """ - SurveyScreen { - layout: vertical; - } - SurveyScreen TabbedContent { - height: 1fr; - } - SurveyScreen .survey-tab { - layout: vertical; - height: 1fr; - } - SurveyScreen .survey-upper { - height: 1fr; - layout: horizontal; - } - SurveyScreen .survey-spectrum-col { - width: 1fr; - } - SurveyScreen .survey-results-col { - width: 1fr; - } - SurveyScreen .survey-progress { - height: auto; - padding: 1 2; - background: #0e1018; - layout: vertical; - } - SurveyScreen .survey-progress Static { - width: auto; - margin: 0 1 0 0; - } - SurveyScreen .survey-progress-row { - height: 3; - layout: horizontal; - } - SurveyScreen .survey-progress ProgressBar { - width: 1fr; - margin: 0 1; - } - SurveyScreen .survey-controls { - height: auto; - padding: 1 2; - background: #0e1018; - border-top: solid #1a2a3a; - layout: horizontal; - } - SurveyScreen .survey-controls Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - SurveyScreen .survey-controls Input { - width: 10; - margin: 0 1; - } - SurveyScreen .survey-controls Button { - margin: 0 1; - } - SurveyScreen .qo100-info { - height: auto; - padding: 1; - background: #0e1420; - border: round #1a2a3a; - margin: 0 0 1 0; - } - SurveyScreen .qo100-info-title { - color: #00d4aa; - text-style: bold; - margin: 0 0 1 0; - } - SurveyScreen .qo100-station { - color: #c8d0d8; - } - SurveyScreen .qo100-detectable { - color: #00e060; - } - SurveyScreen .qo100-not-lockable { - color: #e8a020; - } - """ - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._scanning = False - self._scan_worker: Worker | None = None - - def compose(self) -> ComposeResult: - with TabbedContent(): - with TabPane("Full Band", id="tab-fullband"): - yield self._compose_fullband() - with TabPane("QO-100 DATV", id="tab-qo100"): - yield self._compose_qo100() - - def _compose_fullband(self) -> Container: - c = Vertical(classes="survey-tab") - c._nodes = [] - return _FullBandTab(self._bridge, id="fullband-tab") - - def _compose_qo100(self) -> Container: - return _QO100Tab(self._bridge, id="qo100-tab") - - def on_hide(self) -> None: - self._stop_scan() - - def _stop_scan(self) -> None: - self._scanning = False - if self._scan_worker: - self._scan_worker.cancel() - self._scan_worker = None - - -class _FullBandTab(Container): - """Full IF band carrier survey.""" - - DEFAULT_CSS = """ - _FullBandTab { - layout: vertical; - height: 1fr; - } - """ - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._scanning = False - self._scan_worker: Worker | None = None - - def compose(self) -> ComposeResult: - with Horizontal(classes="survey-upper"): - with Vertical(classes="survey-spectrum-col"): - yield SpectrumPlot(title="Survey Sweep", id="survey-spectrum") - with Vertical(classes="survey-results-col"): - yield Static("[#00d4aa bold]Carriers Found[/]") - yield FrequencyTable(id="survey-table") - - with Vertical(classes="survey-progress"): - yield Static("[#506878]Ready[/]", id="survey-phase") - with Horizontal(classes="survey-progress-row"): - yield ProgressBar(total=100, show_eta=False, id="survey-pbar") - - with Horizontal(classes="survey-controls"): - yield Label("Start IF:") - yield Input("950", id="survey-start") - yield Label("Stop IF:") - yield Input("2150", id="survey-stop") - yield Label("LNB LO:") - yield Input("9750", id="survey-lnb") - yield Label("Step:") - yield Input("5", id="survey-step") - yield Button("Full Scan", id="survey-full", variant="success") - yield Button("Quick Scan", id="survey-quick", variant="primary") - yield Button("Stop", id="survey-stop-btn", variant="error") - - def on_button_pressed(self, event: Button.Pressed) -> None: - btn = event.button.id or "" - if btn == "survey-full": - self._start_full_scan() - elif btn == "survey-quick": - self._start_quick_scan() - elif btn == "survey-stop-btn": - self._stop() - - def _stop(self) -> None: - self._scanning = False - if self._scan_worker: - self._scan_worker.cancel() - self._scan_worker = None - - def _start_full_scan(self) -> None: - if self._scanning: - return - self._scanning = True - start = float(self.query_one("#survey-start", Input).value or "950") - stop = float(self.query_one("#survey-stop", Input).value or "2150") - lnb_lo = float(self.query_one("#survey-lnb", Input).value or "9750") - step = float(self.query_one("#survey-step", Input).value or "5") - - self.query_one("#survey-table", FrequencyTable).clear_table() - self._scan_worker = self._do_full_scan(start, stop, lnb_lo, step) - - def _start_quick_scan(self) -> None: - if self._scanning: - return - self._scanning = True - start = float(self.query_one("#survey-start", Input).value or "950") - stop = float(self.query_one("#survey-stop", Input).value or "2150") - lnb_lo = float(self.query_one("#survey-lnb", Input).value or "9750") - step = float(self.query_one("#survey-step", Input).value or "5") - - self._scan_worker = self._do_quick_scan(start, stop, lnb_lo, step) - - @work(thread=True) - def _do_full_scan(self, start: float, stop: float, - lnb_lo: float, step: float) -> None: - """Six-stage carrier survey in background thread.""" - import sys, os - sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "tools")) - from skywalker_lib import detect_peaks, if_to_rf - - try: - self._bridge.ensure_booted() - except Exception: - pass - - # Stage 1: Coarse sweep - self.app.call_from_thread(self._set_phase, "Stage 1/6: Coarse sweep", 0) - - def sweep_cb(freq, step_num, total, result): - pct = (step_num + 1) / total * 15 - self.app.call_from_thread(self._set_progress, pct) - - freqs, powers, results = self._bridge.sweep_spectrum( - start, stop, step, dwell_ms=15, callback=sweep_cb, - ) - if not self._scanning: - return - - self.app.call_from_thread(self._update_spectrum, freqs, powers, results, lnb_lo) - - # Stage 2: Peak detection - self.app.call_from_thread(self._set_phase, "Stage 2/6: Peak detection", 15) - peaks = detect_peaks(freqs, powers, threshold_db=5.0) - if not peaks: - self.app.call_from_thread(self._set_phase, "No carriers detected", 100) - self._scanning = False - return - - # Stage 3: Fine sweep - self.app.call_from_thread( - self._set_phase, - f"Stage 3/6: Fine sweep ({len(peaks)} peaks)", 25, - ) - refined = [] - for i, (freq, pwr, idx) in enumerate(peaks): - if not self._scanning: - return - fine_start = max(start, freq - 10) - fine_stop = min(stop, freq + 10) - fine_freqs, fine_powers, fine_results = self._bridge.sweep_spectrum( - fine_start, fine_stop, step_mhz=1.0, dwell_ms=20, - ) - if fine_powers: - best_idx = fine_powers.index(max(fine_powers)) - refined.append(( - fine_freqs[best_idx], fine_powers[best_idx], - fine_results[best_idx], - )) - pct = 25 + (i + 1) / len(peaks) * 20 - self.app.call_from_thread(self._set_progress, pct) - - # Stage 4: Blind scan - self.app.call_from_thread( - self._set_phase, - f"Stage 4/6: Blind scan ({len(refined)} candidates)", 45, - ) - locked_carriers = [] - for i, (freq, pwr, result) in enumerate(refined): - if not self._scanning: - return - freq_khz = int(freq * 1000) - bs_result = self._bridge.blind_scan(freq_khz, 1000000, 30000000, 500000) - if bs_result and bs_result.get("locked"): - carrier = { - "if_mhz": bs_result.get("freq_khz", freq_khz) / 1000.0, - "rf_mhz": if_to_rf( - bs_result.get("freq_khz", freq_khz) / 1000.0, lnb_lo - ), - "sr_ksps": bs_result.get("sr_sps", 0) // 1000, - "power_db": pwr, - "locked": True, - } - locked_carriers.append(carrier) - self.app.call_from_thread(self._add_carrier, carrier) - - pct = 45 + (i + 1) / len(refined) * 25 - self.app.call_from_thread(self._set_progress, pct) - - # Stage 5: TS sample (simplified — just report locked carriers) - self.app.call_from_thread( - self._set_phase, - f"Stage 5/6: TS sampling ({len(locked_carriers)} locked)", 70, - ) - # In a full implementation, we'd tune to each carrier, arm_transfer, - # capture 3s of TS, and parse PAT/PMT/SDT for service names. - # For the TUI demo, the locked carrier list is sufficient. - - # Stage 6: Catalog - self.app.call_from_thread(self._set_phase, "Stage 6/6: Catalog assembly", 90) - self.app.call_from_thread(self._set_progress, 95) - - total = len(locked_carriers) - self.app.call_from_thread( - self._set_phase, - f"Survey complete: {total} carrier{'s' if total != 1 else ''} cataloged", - 100, - ) - self._scanning = False - - @work(thread=True) - def _do_quick_scan(self, start: float, stop: float, - lnb_lo: float, step: float) -> None: - """Quick sweep + peak detection only.""" - import sys, os - sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "tools")) - from skywalker_lib import detect_peaks, if_to_rf - - try: - self._bridge.ensure_booted() - except Exception: - pass - - self.app.call_from_thread(self._set_phase, "Quick scan: sweeping", 0) - - def cb(freq, step_num, total, result): - pct = (step_num + 1) / total * 80 - self.app.call_from_thread(self._set_progress, pct) - - freqs, powers, results = self._bridge.sweep_spectrum( - start, stop, step, dwell_ms=10, callback=cb, - ) - if not self._scanning: - return - - self.app.call_from_thread(self._update_spectrum, freqs, powers, results, lnb_lo) - self.app.call_from_thread(self._set_phase, "Quick scan: detecting peaks", 80) - - peaks = detect_peaks(freqs, powers, threshold_db=5.0) - for freq, pwr, idx in peaks: - carrier = { - "if_mhz": freq, - "rf_mhz": if_to_rf(freq, lnb_lo), - "sr_ksps": 0, - "power_db": pwr, - "locked": False, - } - self.app.call_from_thread(self._add_carrier, carrier) - - self.app.call_from_thread( - self._set_phase, - f"Quick scan: {len(peaks)} peaks found", 100, - ) - self._scanning = False - - def _set_phase(self, text: str, progress: float) -> None: - if not self.is_mounted: - return - self.query_one("#survey-phase", Static).update(f"[#00d4aa]{text}[/]") - self.query_one("#survey-pbar", ProgressBar).update(progress=progress) - - def _set_progress(self, pct: float) -> None: - if not self.is_mounted: - return - self.query_one("#survey-pbar", ProgressBar).update(progress=pct) - - def _update_spectrum(self, freqs, powers, results, lnb_lo) -> None: - if not self.is_mounted: - return - self.query_one("#survey-spectrum", SpectrumPlot).update_data( - freqs, powers, results, lnb_lo=lnb_lo, - ) - - def _add_carrier(self, carrier: dict) -> None: - if not self.is_mounted: - return - self.query_one("#survey-table", FrequencyTable).add_transponder(carrier) - - -class _QO100Tab(Container): - """QO-100 DATV focused scan.""" - - DEFAULT_CSS = """ - _QO100Tab { - layout: vertical; - height: 1fr; - } - """ - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._scanning = False - self._scan_worker: Worker | None = None - - def compose(self) -> ComposeResult: - # Info panel with known stations - with Vertical(classes="qo100-info"): - yield Label("QO-100 Wideband Transponder (Es'hail-2, 25.9E)", - classes="qo100-info-title") - yield Static( - "[#506878]RF range:[/] [#00d4aa]10491-10499 MHz[/] " - "[#506878]BCM4500 min SR:[/] [#e8a020]256 ksps[/]" - ) - yield Static( - "[#506878]Known stations:[/]\n" - " [#00e060]BATC beacon 10491.5 MHz SR 1500 ksps QPSK 3/4[/]\n" - " [#00e060]DATV 10492.0 MHz SR 1000 ksps QPSK 1/2[/]\n" - " [#e8a020]Low-power 10493.0 MHz SR 500 ksps QPSK 1/2[/]\n" - " [#e8a020]Minimum 10494.0 MHz SR 333 ksps QPSK 1/2[/]\n" - " [#506878]Beacon 10489.75 MHz CW (not lockable)[/]" - ) - - with Horizontal(classes="survey-upper"): - with Vertical(classes="survey-spectrum-col"): - yield SpectrumPlot(title="QO-100 Sweep", id="qo100-spectrum") - with Vertical(classes="survey-results-col"): - yield Static("[#00d4aa bold]QO-100 Carriers[/]") - yield FrequencyTable(id="qo100-table") - - with Vertical(classes="survey-progress"): - yield Static("[#506878]Ready — enter LNB LO frequency[/]", id="qo100-phase") - with Horizontal(classes="survey-progress-row"): - yield ProgressBar(total=100, show_eta=False, id="qo100-pbar") - - with Horizontal(classes="survey-controls"): - yield Label("LNB LO (MHz):") - yield Input("9361", id="qo100-lnb") - yield Label("Step (kHz):") - yield Input("500", id="qo100-step") - yield Label("Dwell (ms):") - yield Input("50", id="qo100-dwell") - yield Button("Scan QO-100", id="qo100-scan", variant="success") - yield Button("Stop", id="qo100-stop", variant="error") - - def on_button_pressed(self, event: Button.Pressed) -> None: - btn = event.button.id or "" - if btn == "qo100-scan": - self._start_scan() - elif btn == "qo100-stop": - self._stop() - - def _stop(self) -> None: - self._scanning = False - if self._scan_worker: - self._scan_worker.cancel() - self._scan_worker = None - - def _start_scan(self) -> None: - if self._scanning: - return - self._scanning = True - - lnb_lo = float(self.query_one("#qo100-lnb", Input).value or "9361") - step_khz = float(self.query_one("#qo100-step", Input).value or "500") - dwell_ms = int(self.query_one("#qo100-dwell", Input).value or "50") - - self.query_one("#qo100-table", FrequencyTable).clear_table() - self._scan_worker = self._do_qo100_scan(lnb_lo, step_khz, dwell_ms) - - @work(thread=True) - def _do_qo100_scan(self, lnb_lo: float, step_khz: float, - dwell_ms: int) -> None: - """Scan QO-100 wideband transponder with optimized parameters.""" - import sys, os - sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "tools")) - from skywalker_lib import detect_peaks, if_to_rf - - try: - self._bridge.ensure_booted() - except Exception: - pass - - # Calculate IF range from LO - if_start = _QO100_WB_RF_START - lnb_lo - if_stop = _QO100_WB_RF_STOP - lnb_lo - step_mhz = step_khz / 1000.0 - - # Validate IF range is within receiver's capabilities - if if_start < 950 or if_stop > 2150: - self.app.call_from_thread( - self._set_phase, - f"IF range {if_start:.0f}-{if_stop:.0f} MHz out of receiver range (950-2150)", - 0, - ) - self._scanning = False - return - - self.app.call_from_thread( - self._set_phase, - f"Scanning IF {if_start:.0f}-{if_stop:.0f} MHz (LO {lnb_lo:.0f})", 0, - ) - - # Sweep with low SR for better sensitivity to narrow signals - def cb(freq, step_num, total, result): - pct = (step_num + 1) / total * 60 - self.app.call_from_thread(self._set_progress, pct) - - freqs, powers, results = self._bridge.sweep_spectrum( - if_start, if_stop, step_mhz, dwell_ms=dwell_ms, - sr_ksps=1000, # Lower SR for QO-100 sensitivity - callback=cb, - ) - if not self._scanning: - return - - self.app.call_from_thread( - self._update_spectrum, freqs, powers, results, lnb_lo, - ) - - # Peak detection with lower threshold for weak DATV signals - self.app.call_from_thread(self._set_phase, "Detecting QO-100 carriers", 60) - peaks = detect_peaks(freqs, powers, threshold_db=3.0) - - # Try blind scan on each peak - for i, (freq, pwr, idx) in enumerate(peaks): - if not self._scanning: - return - freq_khz = int(freq * 1000) - rf_mhz = if_to_rf(freq, lnb_lo) - - # Try common QO-100 SRs: 333, 500, 1000, 1500, 2000 ksps - locked = False - locked_sr = 0 - for sr_ksps in [1500, 1000, 500, 333, 2000]: - sr_sps = sr_ksps * 1000 - bs = self._bridge.blind_scan(freq_khz, sr_sps, sr_sps, 1) - if bs and bs.get("locked"): - locked = True - locked_sr = sr_ksps - break - - carrier = { - "if_mhz": freq, - "rf_mhz": rf_mhz, - "sr_ksps": locked_sr, - "power_db": pwr, - "locked": locked, - } - self.app.call_from_thread(self._add_carrier, carrier) - pct = 60 + (i + 1) / len(peaks) * 35 - self.app.call_from_thread(self._set_progress, pct) - - total = len(peaks) - locked_count = sum(1 for f, p, i in peaks) # simplified - self.app.call_from_thread( - self._set_phase, - f"QO-100 scan complete: {total} carrier{'s' if total != 1 else ''} detected", - 100, - ) - self._scanning = False - - def _set_phase(self, text: str, progress: float) -> None: - if not self.is_mounted: - return - self.query_one("#qo100-phase", Static).update(f"[#00d4aa]{text}[/]") - self.query_one("#qo100-pbar", ProgressBar).update(progress=progress) - - def _set_progress(self, pct: float) -> None: - if not self.is_mounted: - return - self.query_one("#qo100-pbar", ProgressBar).update(progress=pct) - - def _update_spectrum(self, freqs, powers, results, lnb_lo) -> None: - if not self.is_mounted: - return - self.query_one("#qo100-spectrum", SpectrumPlot).update_data( - freqs, powers, results, lnb_lo=lnb_lo, - ) - - def _add_carrier(self, carrier: dict) -> None: - if not self.is_mounted: - return - self.query_one("#qo100-table", FrequencyTable).add_transponder(carrier) +"""Survey screen — carrier survey + QO-100 DATV reception (tabbed). + +Two tabs within the Survey screen: +- Full Band: automated carrier discovery across the entire IF range +- QO-100: focused on the Es'hail-2 wideband DATV transponder + +Both share a spectrum visualization + results table pattern. +""" + +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, Vertical +from textual.widgets import ( + Label, Input, Button, Static, ProgressBar, + TabbedContent, TabPane, +) +from textual import work +from textual.worker import Worker + +from skywalker_tui.widgets.spectrum_plot import SpectrumPlot +from skywalker_tui.widgets.frequency_table import FrequencyTable + + +# QO-100 IF range for common LNB LOs +_QO100_WB_RF_START = 10491.0 +_QO100_WB_RF_STOP = 10499.0 + + +class SurveyScreen(Container): + """Carrier survey with full-band and QO-100 tabs.""" + + DEFAULT_CSS = """ + SurveyScreen { + layout: vertical; + } + SurveyScreen TabbedContent { + height: 1fr; + } + SurveyScreen .survey-tab { + layout: vertical; + height: 1fr; + } + SurveyScreen .survey-upper { + height: 1fr; + layout: horizontal; + } + SurveyScreen .survey-spectrum-col { + width: 1fr; + } + SurveyScreen .survey-results-col { + width: 1fr; + } + SurveyScreen .survey-progress { + height: auto; + padding: 1 2; + background: #0e1018; + layout: vertical; + } + SurveyScreen .survey-progress Static { + width: auto; + margin: 0 1 0 0; + } + SurveyScreen .survey-progress-row { + height: 3; + layout: horizontal; + } + SurveyScreen .survey-progress ProgressBar { + width: 1fr; + margin: 0 1; + } + SurveyScreen .survey-controls { + height: auto; + padding: 1 2; + background: #0e1018; + border-top: solid #1a2a3a; + layout: horizontal; + } + SurveyScreen .survey-controls Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + SurveyScreen .survey-controls Input { + width: 10; + margin: 0 1; + } + SurveyScreen .survey-controls Button { + margin: 0 1; + } + SurveyScreen .qo100-info { + height: auto; + padding: 1; + background: #0e1420; + border: round #1a2a3a; + margin: 0 0 1 0; + } + SurveyScreen .qo100-info-title { + color: #00d4aa; + text-style: bold; + margin: 0 0 1 0; + } + SurveyScreen .qo100-station { + color: #c8d0d8; + } + SurveyScreen .qo100-detectable { + color: #00e060; + } + SurveyScreen .qo100-not-lockable { + color: #e8a020; + } + """ + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._scanning = False + self._scan_worker: Worker | None = None + + def compose(self) -> ComposeResult: + with TabbedContent(): + with TabPane("Full Band", id="tab-fullband"): + yield self._compose_fullband() + with TabPane("QO-100 DATV", id="tab-qo100"): + yield self._compose_qo100() + + def _compose_fullband(self) -> Container: + c = Vertical(classes="survey-tab") + c._nodes = [] + return _FullBandTab(self._bridge, id="fullband-tab") + + def _compose_qo100(self) -> Container: + return _QO100Tab(self._bridge, id="qo100-tab") + + def on_hide(self) -> None: + self._stop_scan() + + def _stop_scan(self) -> None: + self._scanning = False + if self._scan_worker: + self._scan_worker.cancel() + self._scan_worker = None + + +class _FullBandTab(Container): + """Full IF band carrier survey.""" + + DEFAULT_CSS = """ + _FullBandTab { + layout: vertical; + height: 1fr; + } + """ + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._scanning = False + self._scan_worker: Worker | None = None + + def compose(self) -> ComposeResult: + with Horizontal(classes="survey-upper"): + with Vertical(classes="survey-spectrum-col"): + yield SpectrumPlot(title="Survey Sweep", id="survey-spectrum") + with Vertical(classes="survey-results-col"): + yield Static("[#00d4aa bold]Carriers Found[/]") + yield FrequencyTable(id="survey-table") + + with Vertical(classes="survey-progress"): + yield Static("[#506878]Ready[/]", id="survey-phase") + with Horizontal(classes="survey-progress-row"): + yield ProgressBar(total=100, show_eta=False, id="survey-pbar") + + with Horizontal(classes="survey-controls"): + yield Label("Start IF:") + yield Input("950", id="survey-start") + yield Label("Stop IF:") + yield Input("2150", id="survey-stop") + yield Label("LNB LO:") + yield Input("9750", id="survey-lnb") + yield Label("Step:") + yield Input("5", id="survey-step") + yield Button("Full Scan", id="survey-full", variant="success") + yield Button("Quick Scan", id="survey-quick", variant="primary") + yield Button("Stop", id="survey-stop-btn", variant="error") + + def on_button_pressed(self, event: Button.Pressed) -> None: + btn = event.button.id or "" + if btn == "survey-full": + self._start_full_scan() + elif btn == "survey-quick": + self._start_quick_scan() + elif btn == "survey-stop-btn": + self._stop() + + def _stop(self) -> None: + self._scanning = False + if self._scan_worker: + self._scan_worker.cancel() + self._scan_worker = None + + def _start_full_scan(self) -> None: + if self._scanning: + return + self._scanning = True + start = float(self.query_one("#survey-start", Input).value or "950") + stop = float(self.query_one("#survey-stop", Input).value or "2150") + lnb_lo = float(self.query_one("#survey-lnb", Input).value or "9750") + step = float(self.query_one("#survey-step", Input).value or "5") + + self.query_one("#survey-table", FrequencyTable).clear_table() + self._scan_worker = self._do_full_scan(start, stop, lnb_lo, step) + + def _start_quick_scan(self) -> None: + if self._scanning: + return + self._scanning = True + start = float(self.query_one("#survey-start", Input).value or "950") + stop = float(self.query_one("#survey-stop", Input).value or "2150") + lnb_lo = float(self.query_one("#survey-lnb", Input).value or "9750") + step = float(self.query_one("#survey-step", Input).value or "5") + + self._scan_worker = self._do_quick_scan(start, stop, lnb_lo, step) + + @work(thread=True) + def _do_full_scan(self, start: float, stop: float, + lnb_lo: float, step: float) -> None: + """Six-stage carrier survey in background thread.""" + import sys, os + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "tools")) + from skywalker_lib import detect_peaks, if_to_rf + + try: + self._bridge.ensure_booted() + except Exception: + pass + + # Stage 1: Coarse sweep + self.app.call_from_thread(self._set_phase, "Stage 1/6: Coarse sweep", 0) + + def sweep_cb(freq, step_num, total, result): + pct = (step_num + 1) / total * 15 + self.app.call_from_thread(self._set_progress, pct) + + freqs, powers, results = self._bridge.sweep_spectrum( + start, stop, step, dwell_ms=15, callback=sweep_cb, + ) + if not self._scanning: + return + + self.app.call_from_thread(self._update_spectrum, freqs, powers, results, lnb_lo) + + # Stage 2: Peak detection + self.app.call_from_thread(self._set_phase, "Stage 2/6: Peak detection", 15) + peaks = detect_peaks(freqs, powers, threshold_db=5.0) + if not peaks: + self.app.call_from_thread(self._set_phase, "No carriers detected", 100) + self._scanning = False + return + + # Stage 3: Fine sweep + self.app.call_from_thread( + self._set_phase, + f"Stage 3/6: Fine sweep ({len(peaks)} peaks)", 25, + ) + refined = [] + for i, (freq, pwr, idx) in enumerate(peaks): + if not self._scanning: + return + fine_start = max(start, freq - 10) + fine_stop = min(stop, freq + 10) + fine_freqs, fine_powers, fine_results = self._bridge.sweep_spectrum( + fine_start, fine_stop, step_mhz=1.0, dwell_ms=20, + ) + if fine_powers: + best_idx = fine_powers.index(max(fine_powers)) + refined.append(( + fine_freqs[best_idx], fine_powers[best_idx], + fine_results[best_idx], + )) + pct = 25 + (i + 1) / len(peaks) * 20 + self.app.call_from_thread(self._set_progress, pct) + + # Stage 4: Blind scan + self.app.call_from_thread( + self._set_phase, + f"Stage 4/6: Blind scan ({len(refined)} candidates)", 45, + ) + locked_carriers = [] + for i, (freq, pwr, result) in enumerate(refined): + if not self._scanning: + return + freq_khz = int(freq * 1000) + bs_result = self._bridge.blind_scan(freq_khz, 1000000, 30000000, 500000) + if bs_result and bs_result.get("locked"): + carrier = { + "if_mhz": bs_result.get("freq_khz", freq_khz) / 1000.0, + "rf_mhz": if_to_rf( + bs_result.get("freq_khz", freq_khz) / 1000.0, lnb_lo + ), + "sr_ksps": bs_result.get("sr_sps", 0) // 1000, + "power_db": pwr, + "locked": True, + } + locked_carriers.append(carrier) + self.app.call_from_thread(self._add_carrier, carrier) + + pct = 45 + (i + 1) / len(refined) * 25 + self.app.call_from_thread(self._set_progress, pct) + + # Stage 5: TS sample (simplified — just report locked carriers) + self.app.call_from_thread( + self._set_phase, + f"Stage 5/6: TS sampling ({len(locked_carriers)} locked)", 70, + ) + # In a full implementation, we'd tune to each carrier, arm_transfer, + # capture 3s of TS, and parse PAT/PMT/SDT for service names. + # For the TUI demo, the locked carrier list is sufficient. + + # Stage 6: Catalog + self.app.call_from_thread(self._set_phase, "Stage 6/6: Catalog assembly", 90) + self.app.call_from_thread(self._set_progress, 95) + + total = len(locked_carriers) + self.app.call_from_thread( + self._set_phase, + f"Survey complete: {total} carrier{'s' if total != 1 else ''} cataloged", + 100, + ) + self._scanning = False + + @work(thread=True) + def _do_quick_scan(self, start: float, stop: float, + lnb_lo: float, step: float) -> None: + """Quick sweep + peak detection only.""" + import sys, os + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "tools")) + from skywalker_lib import detect_peaks, if_to_rf + + try: + self._bridge.ensure_booted() + except Exception: + pass + + self.app.call_from_thread(self._set_phase, "Quick scan: sweeping", 0) + + def cb(freq, step_num, total, result): + pct = (step_num + 1) / total * 80 + self.app.call_from_thread(self._set_progress, pct) + + freqs, powers, results = self._bridge.sweep_spectrum( + start, stop, step, dwell_ms=10, callback=cb, + ) + if not self._scanning: + return + + self.app.call_from_thread(self._update_spectrum, freqs, powers, results, lnb_lo) + self.app.call_from_thread(self._set_phase, "Quick scan: detecting peaks", 80) + + peaks = detect_peaks(freqs, powers, threshold_db=5.0) + for freq, pwr, idx in peaks: + carrier = { + "if_mhz": freq, + "rf_mhz": if_to_rf(freq, lnb_lo), + "sr_ksps": 0, + "power_db": pwr, + "locked": False, + } + self.app.call_from_thread(self._add_carrier, carrier) + + self.app.call_from_thread( + self._set_phase, + f"Quick scan: {len(peaks)} peaks found", 100, + ) + self._scanning = False + + def _set_phase(self, text: str, progress: float) -> None: + if not self.is_mounted: + return + self.query_one("#survey-phase", Static).update(f"[#00d4aa]{text}[/]") + self.query_one("#survey-pbar", ProgressBar).update(progress=progress) + + def _set_progress(self, pct: float) -> None: + if not self.is_mounted: + return + self.query_one("#survey-pbar", ProgressBar).update(progress=pct) + + def _update_spectrum(self, freqs, powers, results, lnb_lo) -> None: + if not self.is_mounted: + return + self.query_one("#survey-spectrum", SpectrumPlot).update_data( + freqs, powers, results, lnb_lo=lnb_lo, + ) + + def _add_carrier(self, carrier: dict) -> None: + if not self.is_mounted: + return + self.query_one("#survey-table", FrequencyTable).add_transponder(carrier) + + +class _QO100Tab(Container): + """QO-100 DATV focused scan.""" + + DEFAULT_CSS = """ + _QO100Tab { + layout: vertical; + height: 1fr; + } + """ + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._scanning = False + self._scan_worker: Worker | None = None + + def compose(self) -> ComposeResult: + # Info panel with known stations + with Vertical(classes="qo100-info"): + yield Label("QO-100 Wideband Transponder (Es'hail-2, 25.9E)", + classes="qo100-info-title") + yield Static( + "[#506878]RF range:[/] [#00d4aa]10491-10499 MHz[/] " + "[#506878]BCM4500 min SR:[/] [#e8a020]256 ksps[/]" + ) + yield Static( + "[#506878]Known stations:[/]\n" + " [#00e060]BATC beacon 10491.5 MHz SR 1500 ksps QPSK 3/4[/]\n" + " [#00e060]DATV 10492.0 MHz SR 1000 ksps QPSK 1/2[/]\n" + " [#e8a020]Low-power 10493.0 MHz SR 500 ksps QPSK 1/2[/]\n" + " [#e8a020]Minimum 10494.0 MHz SR 333 ksps QPSK 1/2[/]\n" + " [#506878]Beacon 10489.75 MHz CW (not lockable)[/]" + ) + + with Horizontal(classes="survey-upper"): + with Vertical(classes="survey-spectrum-col"): + yield SpectrumPlot(title="QO-100 Sweep", id="qo100-spectrum") + with Vertical(classes="survey-results-col"): + yield Static("[#00d4aa bold]QO-100 Carriers[/]") + yield FrequencyTable(id="qo100-table") + + with Vertical(classes="survey-progress"): + yield Static("[#506878]Ready — enter LNB LO frequency[/]", id="qo100-phase") + with Horizontal(classes="survey-progress-row"): + yield ProgressBar(total=100, show_eta=False, id="qo100-pbar") + + with Horizontal(classes="survey-controls"): + yield Label("LNB LO (MHz):") + yield Input("9361", id="qo100-lnb") + yield Label("Step (kHz):") + yield Input("500", id="qo100-step") + yield Label("Dwell (ms):") + yield Input("50", id="qo100-dwell") + yield Button("Scan QO-100", id="qo100-scan", variant="success") + yield Button("Stop", id="qo100-stop", variant="error") + + def on_button_pressed(self, event: Button.Pressed) -> None: + btn = event.button.id or "" + if btn == "qo100-scan": + self._start_scan() + elif btn == "qo100-stop": + self._stop() + + def _stop(self) -> None: + self._scanning = False + if self._scan_worker: + self._scan_worker.cancel() + self._scan_worker = None + + def _start_scan(self) -> None: + if self._scanning: + return + self._scanning = True + + lnb_lo = float(self.query_one("#qo100-lnb", Input).value or "9361") + step_khz = float(self.query_one("#qo100-step", Input).value or "500") + dwell_ms = int(self.query_one("#qo100-dwell", Input).value or "50") + + self.query_one("#qo100-table", FrequencyTable).clear_table() + self._scan_worker = self._do_qo100_scan(lnb_lo, step_khz, dwell_ms) + + @work(thread=True) + def _do_qo100_scan(self, lnb_lo: float, step_khz: float, + dwell_ms: int) -> None: + """Scan QO-100 wideband transponder with optimized parameters.""" + import sys, os + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "tools")) + from skywalker_lib import detect_peaks, if_to_rf + + try: + self._bridge.ensure_booted() + except Exception: + pass + + # Calculate IF range from LO + if_start = _QO100_WB_RF_START - lnb_lo + if_stop = _QO100_WB_RF_STOP - lnb_lo + step_mhz = step_khz / 1000.0 + + # Validate IF range is within receiver's capabilities + if if_start < 950 or if_stop > 2150: + self.app.call_from_thread( + self._set_phase, + f"IF range {if_start:.0f}-{if_stop:.0f} MHz out of receiver range (950-2150)", + 0, + ) + self._scanning = False + return + + self.app.call_from_thread( + self._set_phase, + f"Scanning IF {if_start:.0f}-{if_stop:.0f} MHz (LO {lnb_lo:.0f})", 0, + ) + + # Sweep with low SR for better sensitivity to narrow signals + def cb(freq, step_num, total, result): + pct = (step_num + 1) / total * 60 + self.app.call_from_thread(self._set_progress, pct) + + freqs, powers, results = self._bridge.sweep_spectrum( + if_start, if_stop, step_mhz, dwell_ms=dwell_ms, + sr_ksps=1000, # Lower SR for QO-100 sensitivity + callback=cb, + ) + if not self._scanning: + return + + self.app.call_from_thread( + self._update_spectrum, freqs, powers, results, lnb_lo, + ) + + # Peak detection with lower threshold for weak DATV signals + self.app.call_from_thread(self._set_phase, "Detecting QO-100 carriers", 60) + peaks = detect_peaks(freqs, powers, threshold_db=3.0) + + # Try blind scan on each peak + for i, (freq, pwr, idx) in enumerate(peaks): + if not self._scanning: + return + freq_khz = int(freq * 1000) + rf_mhz = if_to_rf(freq, lnb_lo) + + # Try common QO-100 SRs: 333, 500, 1000, 1500, 2000 ksps + locked = False + locked_sr = 0 + for sr_ksps in [1500, 1000, 500, 333, 2000]: + sr_sps = sr_ksps * 1000 + bs = self._bridge.blind_scan(freq_khz, sr_sps, sr_sps, 1) + if bs and bs.get("locked"): + locked = True + locked_sr = sr_ksps + break + + carrier = { + "if_mhz": freq, + "rf_mhz": rf_mhz, + "sr_ksps": locked_sr, + "power_db": pwr, + "locked": locked, + } + self.app.call_from_thread(self._add_carrier, carrier) + pct = 60 + (i + 1) / len(peaks) * 35 + self.app.call_from_thread(self._set_progress, pct) + + total = len(peaks) + locked_count = sum(1 for f, p, i in peaks) # simplified + self.app.call_from_thread( + self._set_phase, + f"QO-100 scan complete: {total} carrier{'s' if total != 1 else ''} detected", + 100, + ) + self._scanning = False + + def _set_phase(self, text: str, progress: float) -> None: + if not self.is_mounted: + return + self.query_one("#qo100-phase", Static).update(f"[#00d4aa]{text}[/]") + self.query_one("#qo100-pbar", ProgressBar).update(progress=progress) + + def _set_progress(self, pct: float) -> None: + if not self.is_mounted: + return + self.query_one("#qo100-pbar", ProgressBar).update(progress=pct) + + def _update_spectrum(self, freqs, powers, results, lnb_lo) -> None: + if not self.is_mounted: + return + self.query_one("#qo100-spectrum", SpectrumPlot).update_data( + freqs, powers, results, lnb_lo=lnb_lo, + ) + + def _add_carrier(self, carrier: dict) -> None: + if not self.is_mounted: + return + self.query_one("#qo100-table", FrequencyTable).add_transponder(carrier) diff --git a/tui/src/skywalker_tui/screens/track.py b/tui/src/skywalker_tui/screens/track.py index e0055c2..41670df 100644 --- a/tui/src/skywalker_tui/screens/track.py +++ b/tui/src/skywalker_tui/screens/track.py @@ -1,392 +1,392 @@ -"""Track screen — carrier/beacon tracker with radar scope, logging, and export. - -Locks to a single frequency and records SNR, power, lock state over time. -Features a hero radar scope (P1 phosphor CRT aesthetic), dual sparklines, -event log for lock transitions, and stats. Supports CSV/JSONL export. -""" - -import csv -import json -import time -from collections import deque -from datetime import datetime -from pathlib import Path - -from textual.app import ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import Label, Input, Button, Static, RichLog -from textual import work -from textual.worker import Worker - -from skywalker_tui.widgets.radar_scope import RadarScope -from skywalker_tui.widgets.sparkline_widget import SparklineWidget - - -class TrackScreen(Container): - """Long-running carrier tracker with radar scope and event log.""" - - DEFAULT_CSS = """ - TrackScreen { - layout: vertical; - } - TrackScreen #track-main { - height: 1fr; - layout: horizontal; - padding: 1 2; - } - TrackScreen #track-radar-col { - width: 1fr; - min-width: 30; - layout: vertical; - } - TrackScreen #track-radar-label { - height: 1; - color: #0a5a10; - text-style: bold; - padding: 0 1; - } - TrackScreen #track-data-col { - width: 1fr; - layout: vertical; - padding: 0 0 0 1; - } - TrackScreen #track-sparklines { - height: auto; - layout: vertical; - } - TrackScreen #track-log-container { - height: 10; - background: #0e1018; - border: round #1a2a3a; - margin: 1 0; - } - TrackScreen #track-log-title { - height: 1; - color: #00d4aa; - text-style: bold; - padding: 0 1; - } - TrackScreen #track-log { - height: 1fr; - } - TrackScreen #track-stats { - height: 3; - layout: horizontal; - } - TrackScreen #track-stats Static { - width: 1fr; - height: 3; - content-align: center middle; - background: #121c2a; - border: round #1a3050; - margin: 0 1 0 0; - } - TrackScreen #track-controls { - height: auto; - padding: 1 2; - background: #0e1018; - border-top: solid #1a2a3a; - layout: horizontal; - } - TrackScreen #track-controls Label { - width: auto; - margin: 1 1 0 0; - color: #506878; - } - TrackScreen #track-controls Input { - width: 14; - margin: 0 1; - } - TrackScreen #track-controls Button { - margin: 0 1; - } - """ - - def __init__(self, bridge, **kwargs): - super().__init__(**kwargs) - self._bridge = bridge - self._tracking = False - self._track_worker: Worker | None = None - self._sample_count = 0 - self._peak_snr = 0.0 - self._start_time = 0.0 - self._was_locked: bool | None = None - self._records: deque[dict] = deque(maxlen=360_000) # ~10h at 10Hz - - def compose(self) -> ComposeResult: - with Horizontal(id="track-main"): - with Vertical(id="track-radar-col"): - yield Static("[#0a5a10 bold]Radar Scope[/]", id="track-radar-label") - yield RadarScope(id="track-radar") - - with Vertical(id="track-data-col"): - with Vertical(id="track-sparklines"): - yield SparklineWidget(title="SNR (dB)", color="#00d4aa", - id="track-snr-spark") - yield SparklineWidget(title="Power (dB)", color="#2196f3", - id="track-power-spark") - - with Vertical(id="track-log-container"): - yield Static("[#00d4aa bold]Event Log[/]", id="track-log-title") - yield RichLog(id="track-log", wrap=True, markup=True) - - with Horizontal(id="track-stats"): - yield Static("[#506878]Samples:[/] [#00d4aa]0[/]", id="trk-samples") - yield Static("[#506878]Elapsed:[/] [#00d4aa]0s[/]", id="trk-elapsed") - yield Static("[#506878]Peak SNR:[/] [#00d4aa]0.0 dB[/]", id="trk-peak") - yield Static("[#506878]Status:[/] [#e8a020]Stopped[/]", id="trk-status") - - with Horizontal(id="track-controls"): - yield Label("Freq (MHz):") - yield Input("1200", id="trk-freq") - yield Label("SR (ksps):") - yield Input("20000", id="trk-sr") - yield Label("Rate (Hz):") - yield Input("1", id="trk-rate") - yield Button("Start", id="trk-start-btn", variant="success") - yield Button("Stop", id="trk-stop-btn", variant="error") - yield Button("Export CSV", id="trk-csv-btn") - yield Button("Export JSONL", id="trk-jsonl-btn") - - def on_show(self) -> None: - if self._bridge.is_demo and not self._tracking: - self._start_tracking() - - def on_hide(self) -> None: - self._stop_tracking() - - def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "trk-start-btn": - self._start_tracking() - elif event.button.id == "trk-stop-btn": - self._stop_tracking() - elif event.button.id == "trk-csv-btn": - self._export_csv() - elif event.button.id == "trk-jsonl-btn": - self._export_jsonl() - - def _start_tracking(self) -> None: - if self._tracking: - return - - log = self.query_one("#track-log", RichLog) - - # C3: Validate inputs before starting - try: - freq = float(self.query_one("#trk-freq", Input).value or "1200") - except ValueError: - log.write("[bold #e04040]Invalid frequency — must be a number[/]") - return - try: - sr = int(float(self.query_one("#trk-sr", Input).value or "20000")) - except ValueError: - log.write("[bold #e04040]Invalid symbol rate — must be a number[/]") - return - try: - rate = float(self.query_one("#trk-rate", Input).value or "1") - except ValueError: - log.write("[bold #e04040]Invalid rate — must be a number[/]") - return - - if not (950 <= freq <= 2150): - log.write("[bold #e04040]Frequency out of range (950–2150 MHz)[/]") - return - if not (256 <= sr <= 30000): - log.write("[bold #e04040]Symbol rate out of range (256–30000 ksps)[/]") - return - if not (0.1 <= rate <= 100): - log.write("[bold #e04040]Rate out of range (0.1–100 Hz)[/]") - return - - self._tracking = True - self._sample_count = 0 - self._peak_snr = 0.0 - self._was_locked = None - self._records.clear() - self._start_time = time.monotonic() - - self.query_one("#trk-status", Static).update( - "[#506878]Status:[/] [bold #00d4aa]Tracking[/]" - ) - log.clear() - log.write("[#506878]Tracking started[/]") - - self._track_worker = self._do_track(freq, sr, rate) - - def _stop_tracking(self) -> None: - self._tracking = False - if self._track_worker: - self._track_worker.cancel() - self._track_worker = None - try: - self.query_one("#trk-status", Static).update( - "[#506878]Status:[/] [#e8a020]Stopped[/]" - ) - log = self.query_one("#track-log", RichLog) - log.write(f"[#506878]Stopped. {self._sample_count} samples.[/]") - except Exception: - pass - - @work(thread=True) - def _do_track(self, freq_mhz: float, sr_ksps: int, rate: float) -> None: - """Background tracking loop with circuit breaker.""" - max_consecutive_errors = 10 - interval = 1.0 / max(0.1, rate) - freq_khz = int(freq_mhz * 1000) - sr_sps = sr_ksps * 1000 - - try: - self._bridge.ensure_booted() - self._bridge.tune(sr_sps, freq_khz, 0, 5) - time.sleep(0.3) - except Exception as e: - self.app.call_from_thread( - self._log_event, - f"[bold #e04040]Boot/tune failed:[/] [#e04040]{e}[/]", - ) - - consecutive_errors = 0 - while self._tracking: - t0 = time.monotonic() - try: - sig = self._bridge.signal_monitor() - consecutive_errors = 0 # reset on success - except Exception: - consecutive_errors += 1 - if consecutive_errors >= max_consecutive_errors: - self.app.call_from_thread( - self._log_event, - f"[bold #e04040]Circuit breaker: " - f"{max_consecutive_errors} consecutive errors, stopping[/]", - ) - self._tracking = False - self.app.call_from_thread(self._mark_stopped) - return - time.sleep(interval) - continue - - self._sample_count += 1 - snr_db = sig.get("snr_db", 0.0) - locked = sig.get("locked", False) - self._peak_snr = max(self._peak_snr, snr_db) - elapsed = time.monotonic() - self._start_time - - record = { - "ts": datetime.now().isoformat(), - "elapsed": round(elapsed, 3), - "snr_db": round(snr_db, 2), - "agc1": sig.get("agc1", 0), - "agc2": sig.get("agc2", 0), - "power_db": round(sig.get("power_db", -40), 2), - "locked": locked, - } - self._records.append(record) - - # Lock transition detection - lock_event = None - if self._was_locked is not None and locked != self._was_locked: - if locked: - lock_event = ("lock", snr_db) - else: - lock_event = ("unlock", snr_db) - self._was_locked = locked - - self.app.call_from_thread( - self._update_ui, sig, elapsed, lock_event, - ) - - sleep = interval - (time.monotonic() - t0) - if sleep > 0: - time.sleep(sleep) - - def _update_ui(self, sig: dict, elapsed: float, - lock_event: tuple | None) -> None: - if not self.is_mounted: - return - - snr_db = sig.get("snr_db", 0.0) - locked = sig.get("locked", False) - - # Feed radar scope - radar = self.query_one("#track-radar", RadarScope) - radar.push(snr_db) - radar.set_locked(locked) - - # Feed sparklines - self.query_one("#track-snr-spark", SparklineWidget).push(snr_db) - self.query_one("#track-power-spark", SparklineWidget).push(sig.get("power_db", -40)) - - # Update stats - self.query_one("#trk-samples", Static).update( - f"[#506878]Samples:[/] [#00d4aa]{self._sample_count}[/]" - ) - self.query_one("#trk-elapsed", Static).update( - f"[#506878]Elapsed:[/] [#00d4aa]{elapsed:.0f}s[/]" - ) - self.query_one("#trk-peak", Static).update( - f"[#506878]Peak SNR:[/] [#00d4aa]{self._peak_snr:.1f} dB[/]" - ) - - if lock_event: - ts = datetime.now().strftime("%H:%M:%S.%f")[:-3] - log = self.query_one("#track-log", RichLog) - if lock_event[0] == "lock": - log.write( - f"[#506878]{ts}[/] [bold #00e060]LOCK ACQUIRED[/]" - f" SNR {lock_event[1]:.1f} dB" - ) - else: - log.write( - f"[#506878]{ts}[/] [bold #e04040]LOCK LOST[/]" - ) - - def _log_event(self, markup: str) -> None: - """Write a message to the event log (safe from any thread via call_from_thread).""" - if not self.is_mounted: - return - try: - self.query_one("#track-log", RichLog).write(markup) - except Exception: - pass - - def _mark_stopped(self) -> None: - """Update UI to stopped state (called from circuit breaker).""" - if not self.is_mounted: - return - try: - self.query_one("#trk-status", Static).update( - "[#506878]Status:[/] [bold #e04040]Error[/]" - ) - except Exception: - pass - - def _export_csv(self) -> None: - if not self._records: - return - log = self.query_one("#track-log", RichLog) - path = Path(f"skywalker-track-{datetime.now().strftime('%Y%m%d-%H%M%S')}.csv") - try: - with open(path, "w", newline="") as f: - w = csv.DictWriter(f, fieldnames=list(self._records[0].keys())) - w.writeheader() - w.writerows(self._records) - log.write(f"[#00d4aa]CSV exported: {path}[/]") - except PermissionError: - log.write(f"[bold #e04040]Permission denied: {path}[/]") - except OSError as e: - log.write(f"[bold #e04040]Export failed: {e}[/]") - - def _export_jsonl(self) -> None: - if not self._records: - return - log = self.query_one("#track-log", RichLog) - path = Path(f"skywalker-track-{datetime.now().strftime('%Y%m%d-%H%M%S')}.jsonl") - try: - with open(path, "w") as f: - for rec in self._records: - f.write(json.dumps(rec) + "\n") - log.write(f"[#00d4aa]JSONL exported: {path}[/]") - except PermissionError: - log.write(f"[bold #e04040]Permission denied: {path}[/]") - except OSError as e: - log.write(f"[bold #e04040]Export failed: {e}[/]") +"""Track screen — carrier/beacon tracker with radar scope, logging, and export. + +Locks to a single frequency and records SNR, power, lock state over time. +Features a hero radar scope (P1 phosphor CRT aesthetic), dual sparklines, +event log for lock transitions, and stats. Supports CSV/JSONL export. +""" + +import csv +import json +import time +from collections import deque +from datetime import datetime +from pathlib import Path + +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, Vertical +from textual.widgets import Label, Input, Button, Static, RichLog +from textual import work +from textual.worker import Worker + +from skywalker_tui.widgets.radar_scope import RadarScope +from skywalker_tui.widgets.sparkline_widget import SparklineWidget + + +class TrackScreen(Container): + """Long-running carrier tracker with radar scope and event log.""" + + DEFAULT_CSS = """ + TrackScreen { + layout: vertical; + } + TrackScreen #track-main { + height: 1fr; + layout: horizontal; + padding: 1 2; + } + TrackScreen #track-radar-col { + width: 1fr; + min-width: 30; + layout: vertical; + } + TrackScreen #track-radar-label { + height: 1; + color: #0a5a10; + text-style: bold; + padding: 0 1; + } + TrackScreen #track-data-col { + width: 1fr; + layout: vertical; + padding: 0 0 0 1; + } + TrackScreen #track-sparklines { + height: auto; + layout: vertical; + } + TrackScreen #track-log-container { + height: 10; + background: #0e1018; + border: round #1a2a3a; + margin: 1 0; + } + TrackScreen #track-log-title { + height: 1; + color: #00d4aa; + text-style: bold; + padding: 0 1; + } + TrackScreen #track-log { + height: 1fr; + } + TrackScreen #track-stats { + height: 3; + layout: horizontal; + } + TrackScreen #track-stats Static { + width: 1fr; + height: 3; + content-align: center middle; + background: #121c2a; + border: round #1a3050; + margin: 0 1 0 0; + } + TrackScreen #track-controls { + height: auto; + padding: 1 2; + background: #0e1018; + border-top: solid #1a2a3a; + layout: horizontal; + } + TrackScreen #track-controls Label { + width: auto; + margin: 1 1 0 0; + color: #506878; + } + TrackScreen #track-controls Input { + width: 14; + margin: 0 1; + } + TrackScreen #track-controls Button { + margin: 0 1; + } + """ + + def __init__(self, bridge, **kwargs): + super().__init__(**kwargs) + self._bridge = bridge + self._tracking = False + self._track_worker: Worker | None = None + self._sample_count = 0 + self._peak_snr = 0.0 + self._start_time = 0.0 + self._was_locked: bool | None = None + self._records: deque[dict] = deque(maxlen=360_000) # ~10h at 10Hz + + def compose(self) -> ComposeResult: + with Horizontal(id="track-main"): + with Vertical(id="track-radar-col"): + yield Static("[#0a5a10 bold]Radar Scope[/]", id="track-radar-label") + yield RadarScope(id="track-radar") + + with Vertical(id="track-data-col"): + with Vertical(id="track-sparklines"): + yield SparklineWidget(title="SNR (dB)", color="#00d4aa", + id="track-snr-spark") + yield SparklineWidget(title="Power (dB)", color="#2196f3", + id="track-power-spark") + + with Vertical(id="track-log-container"): + yield Static("[#00d4aa bold]Event Log[/]", id="track-log-title") + yield RichLog(id="track-log", wrap=True, markup=True) + + with Horizontal(id="track-stats"): + yield Static("[#506878]Samples:[/] [#00d4aa]0[/]", id="trk-samples") + yield Static("[#506878]Elapsed:[/] [#00d4aa]0s[/]", id="trk-elapsed") + yield Static("[#506878]Peak SNR:[/] [#00d4aa]0.0 dB[/]", id="trk-peak") + yield Static("[#506878]Status:[/] [#e8a020]Stopped[/]", id="trk-status") + + with Horizontal(id="track-controls"): + yield Label("Freq (MHz):") + yield Input("1200", id="trk-freq") + yield Label("SR (ksps):") + yield Input("20000", id="trk-sr") + yield Label("Rate (Hz):") + yield Input("1", id="trk-rate") + yield Button("Start", id="trk-start-btn", variant="success") + yield Button("Stop", id="trk-stop-btn", variant="error") + yield Button("Export CSV", id="trk-csv-btn") + yield Button("Export JSONL", id="trk-jsonl-btn") + + def on_show(self) -> None: + if self._bridge.is_demo and not self._tracking: + self._start_tracking() + + def on_hide(self) -> None: + self._stop_tracking() + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "trk-start-btn": + self._start_tracking() + elif event.button.id == "trk-stop-btn": + self._stop_tracking() + elif event.button.id == "trk-csv-btn": + self._export_csv() + elif event.button.id == "trk-jsonl-btn": + self._export_jsonl() + + def _start_tracking(self) -> None: + if self._tracking: + return + + log = self.query_one("#track-log", RichLog) + + # C3: Validate inputs before starting + try: + freq = float(self.query_one("#trk-freq", Input).value or "1200") + except ValueError: + log.write("[bold #e04040]Invalid frequency — must be a number[/]") + return + try: + sr = int(float(self.query_one("#trk-sr", Input).value or "20000")) + except ValueError: + log.write("[bold #e04040]Invalid symbol rate — must be a number[/]") + return + try: + rate = float(self.query_one("#trk-rate", Input).value or "1") + except ValueError: + log.write("[bold #e04040]Invalid rate — must be a number[/]") + return + + if not (950 <= freq <= 2150): + log.write("[bold #e04040]Frequency out of range (950–2150 MHz)[/]") + return + if not (256 <= sr <= 30000): + log.write("[bold #e04040]Symbol rate out of range (256–30000 ksps)[/]") + return + if not (0.1 <= rate <= 100): + log.write("[bold #e04040]Rate out of range (0.1–100 Hz)[/]") + return + + self._tracking = True + self._sample_count = 0 + self._peak_snr = 0.0 + self._was_locked = None + self._records.clear() + self._start_time = time.monotonic() + + self.query_one("#trk-status", Static).update( + "[#506878]Status:[/] [bold #00d4aa]Tracking[/]" + ) + log.clear() + log.write("[#506878]Tracking started[/]") + + self._track_worker = self._do_track(freq, sr, rate) + + def _stop_tracking(self) -> None: + self._tracking = False + if self._track_worker: + self._track_worker.cancel() + self._track_worker = None + try: + self.query_one("#trk-status", Static).update( + "[#506878]Status:[/] [#e8a020]Stopped[/]" + ) + log = self.query_one("#track-log", RichLog) + log.write(f"[#506878]Stopped. {self._sample_count} samples.[/]") + except Exception: + pass + + @work(thread=True) + def _do_track(self, freq_mhz: float, sr_ksps: int, rate: float) -> None: + """Background tracking loop with circuit breaker.""" + max_consecutive_errors = 10 + interval = 1.0 / max(0.1, rate) + freq_khz = int(freq_mhz * 1000) + sr_sps = sr_ksps * 1000 + + try: + self._bridge.ensure_booted() + self._bridge.tune(sr_sps, freq_khz, 0, 5) + time.sleep(0.3) + except Exception as e: + self.app.call_from_thread( + self._log_event, + f"[bold #e04040]Boot/tune failed:[/] [#e04040]{e}[/]", + ) + + consecutive_errors = 0 + while self._tracking: + t0 = time.monotonic() + try: + sig = self._bridge.signal_monitor() + consecutive_errors = 0 # reset on success + except Exception: + consecutive_errors += 1 + if consecutive_errors >= max_consecutive_errors: + self.app.call_from_thread( + self._log_event, + f"[bold #e04040]Circuit breaker: " + f"{max_consecutive_errors} consecutive errors, stopping[/]", + ) + self._tracking = False + self.app.call_from_thread(self._mark_stopped) + return + time.sleep(interval) + continue + + self._sample_count += 1 + snr_db = sig.get("snr_db", 0.0) + locked = sig.get("locked", False) + self._peak_snr = max(self._peak_snr, snr_db) + elapsed = time.monotonic() - self._start_time + + record = { + "ts": datetime.now().isoformat(), + "elapsed": round(elapsed, 3), + "snr_db": round(snr_db, 2), + "agc1": sig.get("agc1", 0), + "agc2": sig.get("agc2", 0), + "power_db": round(sig.get("power_db", -40), 2), + "locked": locked, + } + self._records.append(record) + + # Lock transition detection + lock_event = None + if self._was_locked is not None and locked != self._was_locked: + if locked: + lock_event = ("lock", snr_db) + else: + lock_event = ("unlock", snr_db) + self._was_locked = locked + + self.app.call_from_thread( + self._update_ui, sig, elapsed, lock_event, + ) + + sleep = interval - (time.monotonic() - t0) + if sleep > 0: + time.sleep(sleep) + + def _update_ui(self, sig: dict, elapsed: float, + lock_event: tuple | None) -> None: + if not self.is_mounted: + return + + snr_db = sig.get("snr_db", 0.0) + locked = sig.get("locked", False) + + # Feed radar scope + radar = self.query_one("#track-radar", RadarScope) + radar.push(snr_db) + radar.set_locked(locked) + + # Feed sparklines + self.query_one("#track-snr-spark", SparklineWidget).push(snr_db) + self.query_one("#track-power-spark", SparklineWidget).push(sig.get("power_db", -40)) + + # Update stats + self.query_one("#trk-samples", Static).update( + f"[#506878]Samples:[/] [#00d4aa]{self._sample_count}[/]" + ) + self.query_one("#trk-elapsed", Static).update( + f"[#506878]Elapsed:[/] [#00d4aa]{elapsed:.0f}s[/]" + ) + self.query_one("#trk-peak", Static).update( + f"[#506878]Peak SNR:[/] [#00d4aa]{self._peak_snr:.1f} dB[/]" + ) + + if lock_event: + ts = datetime.now().strftime("%H:%M:%S.%f")[:-3] + log = self.query_one("#track-log", RichLog) + if lock_event[0] == "lock": + log.write( + f"[#506878]{ts}[/] [bold #00e060]LOCK ACQUIRED[/]" + f" SNR {lock_event[1]:.1f} dB" + ) + else: + log.write( + f"[#506878]{ts}[/] [bold #e04040]LOCK LOST[/]" + ) + + def _log_event(self, markup: str) -> None: + """Write a message to the event log (safe from any thread via call_from_thread).""" + if not self.is_mounted: + return + try: + self.query_one("#track-log", RichLog).write(markup) + except Exception: + pass + + def _mark_stopped(self) -> None: + """Update UI to stopped state (called from circuit breaker).""" + if not self.is_mounted: + return + try: + self.query_one("#trk-status", Static).update( + "[#506878]Status:[/] [bold #e04040]Error[/]" + ) + except Exception: + pass + + def _export_csv(self) -> None: + if not self._records: + return + log = self.query_one("#track-log", RichLog) + path = Path(f"skywalker-track-{datetime.now().strftime('%Y%m%d-%H%M%S')}.csv") + try: + with open(path, "w", newline="") as f: + w = csv.DictWriter(f, fieldnames=list(self._records[0].keys())) + w.writeheader() + w.writerows(self._records) + log.write(f"[#00d4aa]CSV exported: {path}[/]") + except PermissionError: + log.write(f"[bold #e04040]Permission denied: {path}[/]") + except OSError as e: + log.write(f"[bold #e04040]Export failed: {e}[/]") + + def _export_jsonl(self) -> None: + if not self._records: + return + log = self.query_one("#track-log", RichLog) + path = Path(f"skywalker-track-{datetime.now().strftime('%Y%m%d-%H%M%S')}.jsonl") + try: + with open(path, "w") as f: + for rec in self._records: + f.write(json.dumps(rec) + "\n") + log.write(f"[#00d4aa]JSONL exported: {path}[/]") + except PermissionError: + log.write(f"[bold #e04040]Permission denied: {path}[/]") + except OSError as e: + log.write(f"[bold #e04040]Export failed: {e}[/]") diff --git a/tui/src/skywalker_tui/theme.tcss b/tui/src/skywalker_tui/theme.tcss index 67db46c..6c7c5fc 100644 --- a/tui/src/skywalker_tui/theme.tcss +++ b/tui/src/skywalker_tui/theme.tcss @@ -1,424 +1,424 @@ -/* SkyWalker-1 TUI — dark RF theme - * - * Design: dark background with teal/cyan data accents. - * Signal gradient: blue → green → yellow → red (cold → hot). - * No purple per user preference. - */ - -/* ─── Global ─── */ - -Screen { - background: #0a0a12; - color: #c8d0d8; -} - -Header { - background: #0e1018; - color: #00d4aa; - dock: top; -} - -Footer { - background: #0e1018; - dock: bottom; -} - -/* ─── Sidebar ─── */ - -#sidebar { - width: 26; - background: #0e1420; - border-right: solid #1a2a3a; - padding: 1 1; -} - -#sidebar .mode-button { - width: 100%; - margin: 0 0 1 0; - min-height: 3; - background: #121c2a; - color: #7090a8; - border: round #1a3050; - text-align: center; -} - -#sidebar .mode-button:hover { - background: #1a2a40; - color: #00d4aa; - border: round #00d4aa; -} - -#sidebar .mode-button.-active { - background: #0a2a3a; - color: #00d4aa; - border: round #00d4aa; - text-style: bold; -} - -#sidebar Label.sidebar-heading { - color: #506878; - text-style: bold; - margin: 1 0 0 0; - text-align: center; -} - -/* ─── Content area ─── */ - -#content-area { - background: #0a0a12; -} - -/* ─── Status bar widget ─── */ - -#device-status { - height: 3; - background: #0e1420; - border-top: solid #1a2a3a; - padding: 0 1; - dock: bottom; -} - -#device-status .status-label { - color: #506878; -} - -#device-status .status-value { - color: #00d4aa; -} - -#device-status .status-connected { - color: #00d4aa; - text-style: bold; -} - -#device-status .status-demo { - color: #e8a020; - text-style: bold; -} - -#device-status .status-disconnected { - color: #e04040; - text-style: bold; -} - -/* ─── Signal gauge ─── */ - -.signal-gauge { - height: auto; - padding: 1; -} - -.signal-gauge .snr-value { - color: #00d4aa; - text-style: bold; -} - -.signal-gauge .lock-yes { - color: #00e060; - text-style: bold; -} - -.signal-gauge .lock-no { - color: #e04040; -} - -/* ─── Spectrum plot ─── */ - -.spectrum-plot { - min-height: 12; -} - -/* ─── Panels and containers ─── */ - -.panel { - background: #0e1420; - border: round #1a2a3a; - padding: 1; - margin: 0 0 1 0; -} - -.panel-title { - color: #00d4aa; - text-style: bold; - margin: 0 0 1 0; -} - -/* ─── Controls / Input areas ─── */ - -.controls { - height: auto; - padding: 1; - background: #0e1018; - border-top: solid #1a2a3a; - dock: bottom; -} - -.controls Label { - color: #506878; - width: auto; - margin: 0 1 0 0; -} - -.controls Input { - width: 14; - background: #121c2a; - border: round #1a3050; - color: #c8d0d8; -} - -.controls Input:focus { - border: round #00d4aa; -} - -.controls Button { - margin: 0 1; - background: #1a2a40; - color: #00d4aa; - border: round #1a3050; -} - -.controls Button:hover { - background: #00d4aa; - color: #0a0a12; -} - -/* ─── Data table ─── */ - -DataTable { - background: #0a0a12; -} - -DataTable > .datatable--header { - background: #0e1420; - color: #00d4aa; - text-style: bold; -} - -DataTable > .datatable--cursor { - background: #1a2a40; - color: #ffffff; -} - -/* ─── Progress bar ─── */ - -ProgressBar Bar { - color: #00d4aa; - background: #121c2a; -} - -/* ─── Sparkline ─── */ - -.sparkline-widget { - height: 3; - padding: 0 1; -} - -/* ─── Waterfall ─── */ - -.waterfall { - min-height: 10; -} - -/* ─── Log / event list ─── */ - -#event-log { - height: 8; - background: #0e1018; - border: round #1a2a3a; - padding: 0 1; - overflow-y: auto; -} - -#event-log .log-lock { - color: #00e060; -} - -#event-log .log-unlock { - color: #e04040; -} - -#event-log .log-time { - color: #506878; -} - -/* ─── Stats panel ─── */ - -.stats-grid { - layout: grid; - grid-size: 4; - grid-gutter: 1; - height: auto; - padding: 1; -} - -.stat-box { - height: 3; - background: #121c2a; - border: round #1a3050; - padding: 0 1; - content-align: center middle; -} - -.stat-box .stat-value { - color: #00d4aa; - text-style: bold; -} - -.stat-box .stat-label { - color: #506878; -} - -/* ─── L-band allocation ─── */ - -.alloc-tag { - background: #1a2a40; - color: #60a0c0; - padding: 0 1; - margin: 0 1 0 0; -} - -/* ─── Mode-specific screen layouts ─── */ - -.mode-screen { - layout: vertical; -} - -.top-panel { - height: 1fr; - min-height: 10; -} - -.bottom-panel { - height: auto; -} - -.split-horizontal { - layout: horizontal; -} - -.left-panel { - width: 1fr; -} - -.right-panel { - width: 1fr; -} - -/* ─── Radar scope ─── */ - -RadarScope { - min-height: 12; - min-width: 24; - height: 1fr; - background: #0a0a0a; - border: round #0a2a0a; -} - -#track-radar-col { - width: 1fr; - min-width: 30; -} - -/* ─── Splash screen overlay ─── */ - -SplashScreen { - align: center middle; - background: #000000; -} - -SplashScreen #splash-container { - width: 100%; - height: 100%; - align: center middle; - background: #000000; -} - -SplashScreen #splash-image { - width: 100%; - height: 1fr; - content-align: center middle; -} - -/* ─── Hex view ─── */ - -HexView { - min-height: 6; -} - -/* ─── PID table ─── */ - -PidTable { - min-height: 8; -} - -/* ─── PSI tree ─── */ - -PsiTree { - min-height: 8; -} - -/* ─── Countdown timer ─── */ - -CountdownTimer { - margin: 1 0; -} - -/* ─── Config bits display ─── */ - -ConfigBitsDisplay { - height: auto; -} - -/* ─── Star Wars overlay ─── */ - -StarWarsScreen { - align: center middle; - background: #000000 90%; -} - -StarWarsScreen #sw-container { - width: 90%; - height: 90%; - background: #000000; - border: round #1a3050; -} - -/* ─── Motor screen ─── */ - -MotorScreen .jog-row Button { - min-height: 3; -} - -MotorScreen #jog-halt { - background: #3a1010; - color: #e04040; - border: round #e04040; -} - -MotorScreen #jog-halt:hover { - background: #e04040; - color: #0a0a12; -} - -MotorScreen #jog-east, -MotorScreen #jog-west { - background: #1a2a40; - color: #e8a020; - border: round #e8a020; -} - -MotorScreen #jog-east:hover, -MotorScreen #jog-west:hover { - background: #e8a020; - color: #0a0a12; -} - -/* ─── Survey / QO-100 screen ─── */ - -SurveyScreen TabbedContent ContentSwitcher { - height: 1fr; -} - -SurveyScreen TabPane { - padding: 0; -} +/* SkyWalker-1 TUI — dark RF theme + * + * Design: dark background with teal/cyan data accents. + * Signal gradient: blue → green → yellow → red (cold → hot). + * No purple per user preference. + */ + +/* ─── Global ─── */ + +Screen { + background: #0a0a12; + color: #c8d0d8; +} + +Header { + background: #0e1018; + color: #00d4aa; + dock: top; +} + +Footer { + background: #0e1018; + dock: bottom; +} + +/* ─── Sidebar ─── */ + +#sidebar { + width: 26; + background: #0e1420; + border-right: solid #1a2a3a; + padding: 1 1; +} + +#sidebar .mode-button { + width: 100%; + margin: 0 0 1 0; + min-height: 3; + background: #121c2a; + color: #7090a8; + border: round #1a3050; + text-align: center; +} + +#sidebar .mode-button:hover { + background: #1a2a40; + color: #00d4aa; + border: round #00d4aa; +} + +#sidebar .mode-button.-active { + background: #0a2a3a; + color: #00d4aa; + border: round #00d4aa; + text-style: bold; +} + +#sidebar Label.sidebar-heading { + color: #506878; + text-style: bold; + margin: 1 0 0 0; + text-align: center; +} + +/* ─── Content area ─── */ + +#content-area { + background: #0a0a12; +} + +/* ─── Status bar widget ─── */ + +#device-status { + height: 3; + background: #0e1420; + border-top: solid #1a2a3a; + padding: 0 1; + dock: bottom; +} + +#device-status .status-label { + color: #506878; +} + +#device-status .status-value { + color: #00d4aa; +} + +#device-status .status-connected { + color: #00d4aa; + text-style: bold; +} + +#device-status .status-demo { + color: #e8a020; + text-style: bold; +} + +#device-status .status-disconnected { + color: #e04040; + text-style: bold; +} + +/* ─── Signal gauge ─── */ + +.signal-gauge { + height: auto; + padding: 1; +} + +.signal-gauge .snr-value { + color: #00d4aa; + text-style: bold; +} + +.signal-gauge .lock-yes { + color: #00e060; + text-style: bold; +} + +.signal-gauge .lock-no { + color: #e04040; +} + +/* ─── Spectrum plot ─── */ + +.spectrum-plot { + min-height: 12; +} + +/* ─── Panels and containers ─── */ + +.panel { + background: #0e1420; + border: round #1a2a3a; + padding: 1; + margin: 0 0 1 0; +} + +.panel-title { + color: #00d4aa; + text-style: bold; + margin: 0 0 1 0; +} + +/* ─── Controls / Input areas ─── */ + +.controls { + height: auto; + padding: 1; + background: #0e1018; + border-top: solid #1a2a3a; + dock: bottom; +} + +.controls Label { + color: #506878; + width: auto; + margin: 0 1 0 0; +} + +.controls Input { + width: 14; + background: #121c2a; + border: round #1a3050; + color: #c8d0d8; +} + +.controls Input:focus { + border: round #00d4aa; +} + +.controls Button { + margin: 0 1; + background: #1a2a40; + color: #00d4aa; + border: round #1a3050; +} + +.controls Button:hover { + background: #00d4aa; + color: #0a0a12; +} + +/* ─── Data table ─── */ + +DataTable { + background: #0a0a12; +} + +DataTable > .datatable--header { + background: #0e1420; + color: #00d4aa; + text-style: bold; +} + +DataTable > .datatable--cursor { + background: #1a2a40; + color: #ffffff; +} + +/* ─── Progress bar ─── */ + +ProgressBar Bar { + color: #00d4aa; + background: #121c2a; +} + +/* ─── Sparkline ─── */ + +.sparkline-widget { + height: 3; + padding: 0 1; +} + +/* ─── Waterfall ─── */ + +.waterfall { + min-height: 10; +} + +/* ─── Log / event list ─── */ + +#event-log { + height: 8; + background: #0e1018; + border: round #1a2a3a; + padding: 0 1; + overflow-y: auto; +} + +#event-log .log-lock { + color: #00e060; +} + +#event-log .log-unlock { + color: #e04040; +} + +#event-log .log-time { + color: #506878; +} + +/* ─── Stats panel ─── */ + +.stats-grid { + layout: grid; + grid-size: 4; + grid-gutter: 1; + height: auto; + padding: 1; +} + +.stat-box { + height: 3; + background: #121c2a; + border: round #1a3050; + padding: 0 1; + content-align: center middle; +} + +.stat-box .stat-value { + color: #00d4aa; + text-style: bold; +} + +.stat-box .stat-label { + color: #506878; +} + +/* ─── L-band allocation ─── */ + +.alloc-tag { + background: #1a2a40; + color: #60a0c0; + padding: 0 1; + margin: 0 1 0 0; +} + +/* ─── Mode-specific screen layouts ─── */ + +.mode-screen { + layout: vertical; +} + +.top-panel { + height: 1fr; + min-height: 10; +} + +.bottom-panel { + height: auto; +} + +.split-horizontal { + layout: horizontal; +} + +.left-panel { + width: 1fr; +} + +.right-panel { + width: 1fr; +} + +/* ─── Radar scope ─── */ + +RadarScope { + min-height: 12; + min-width: 24; + height: 1fr; + background: #0a0a0a; + border: round #0a2a0a; +} + +#track-radar-col { + width: 1fr; + min-width: 30; +} + +/* ─── Splash screen overlay ─── */ + +SplashScreen { + align: center middle; + background: #000000; +} + +SplashScreen #splash-container { + width: 100%; + height: 100%; + align: center middle; + background: #000000; +} + +SplashScreen #splash-image { + width: 100%; + height: 1fr; + content-align: center middle; +} + +/* ─── Hex view ─── */ + +HexView { + min-height: 6; +} + +/* ─── PID table ─── */ + +PidTable { + min-height: 8; +} + +/* ─── PSI tree ─── */ + +PsiTree { + min-height: 8; +} + +/* ─── Countdown timer ─── */ + +CountdownTimer { + margin: 1 0; +} + +/* ─── Config bits display ─── */ + +ConfigBitsDisplay { + height: auto; +} + +/* ─── Star Wars overlay ─── */ + +StarWarsScreen { + align: center middle; + background: #000000 90%; +} + +StarWarsScreen #sw-container { + width: 90%; + height: 90%; + background: #000000; + border: round #1a3050; +} + +/* ─── Motor screen ─── */ + +MotorScreen .jog-row Button { + min-height: 3; +} + +MotorScreen #jog-halt { + background: #3a1010; + color: #e04040; + border: round #e04040; +} + +MotorScreen #jog-halt:hover { + background: #e04040; + color: #0a0a12; +} + +MotorScreen #jog-east, +MotorScreen #jog-west { + background: #1a2a40; + color: #e8a020; + border: round #e8a020; +} + +MotorScreen #jog-east:hover, +MotorScreen #jog-west:hover { + background: #e8a020; + color: #0a0a12; +} + +/* ─── Survey / QO-100 screen ─── */ + +SurveyScreen TabbedContent ContentSwitcher { + height: 1fr; +} + +SurveyScreen TabPane { + padding: 0; +} diff --git a/tui/src/skywalker_tui/widgets/__init__.py b/tui/src/skywalker_tui/widgets/__init__.py index 00f5419..c6adf28 100644 --- a/tui/src/skywalker_tui/widgets/__init__.py +++ b/tui/src/skywalker_tui/widgets/__init__.py @@ -1 +1 @@ -"""Custom widgets for SkyWalker-1 TUI.""" +"""Custom widgets for SkyWalker-1 TUI.""" diff --git a/tui/src/skywalker_tui/widgets/config_bits.py b/tui/src/skywalker_tui/widgets/config_bits.py index 0f419a8..5a877b6 100644 --- a/tui/src/skywalker_tui/widgets/config_bits.py +++ b/tui/src/skywalker_tui/widgets/config_bits.py @@ -1,46 +1,46 @@ -"""Config byte flag display with colored indicators. - -Renders the 8PSK config byte as a horizontal row of labeled flags, -each shown as a filled (set) or hollow (clear) circle with color coding. -""" - -from textual.widget import Widget -from textual.widgets import Static -from textual.app import ComposeResult - -from skywalker_lib import CONFIG_BITS - - -class ConfigBitsDisplay(Widget): - """Renders the 8PSK config byte as labeled flags with colored indicators.""" - - DEFAULT_CSS = """ - ConfigBitsDisplay { - height: auto; - padding: 0 1; - } - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._config = 0 - - def compose(self) -> ComposeResult: - yield Static("", id="config-bits-content") - - def update_config(self, config: int) -> None: - """Update the displayed config byte value.""" - self._config = config - self._refresh() - - def _refresh(self) -> None: - if not self.is_mounted: - return - parts = [] - for bit_mask, (name, _field) in CONFIG_BITS.items(): - is_set = bool(self._config & bit_mask) - if is_set: - parts.append(f"[bold #00e060]\u25cf[/] [#c8d0d8]{name}[/]") - else: - parts.append(f"[#3a3a3a]\u25cb[/] [#506878]{name}[/]") - self.query_one("#config-bits-content", Static).update(" ".join(parts)) +"""Config byte flag display with colored indicators. + +Renders the 8PSK config byte as a horizontal row of labeled flags, +each shown as a filled (set) or hollow (clear) circle with color coding. +""" + +from textual.widget import Widget +from textual.widgets import Static +from textual.app import ComposeResult + +from skywalker_lib import CONFIG_BITS + + +class ConfigBitsDisplay(Widget): + """Renders the 8PSK config byte as labeled flags with colored indicators.""" + + DEFAULT_CSS = """ + ConfigBitsDisplay { + height: auto; + padding: 0 1; + } + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._config = 0 + + def compose(self) -> ComposeResult: + yield Static("", id="config-bits-content") + + def update_config(self, config: int) -> None: + """Update the displayed config byte value.""" + self._config = config + self._refresh() + + def _refresh(self) -> None: + if not self.is_mounted: + return + parts = [] + for bit_mask, (name, _field) in CONFIG_BITS.items(): + is_set = bool(self._config & bit_mask) + if is_set: + parts.append(f"[bold #00e060]\u25cf[/] [#c8d0d8]{name}[/]") + else: + parts.append(f"[#3a3a3a]\u25cb[/] [#506878]{name}[/]") + self.query_one("#config-bits-content", Static).update(" ".join(parts)) diff --git a/tui/src/skywalker_tui/widgets/countdown_timer.py b/tui/src/skywalker_tui/widgets/countdown_timer.py index e601e3d..c9b83f4 100644 --- a/tui/src/skywalker_tui/widgets/countdown_timer.py +++ b/tui/src/skywalker_tui/widgets/countdown_timer.py @@ -1,112 +1,112 @@ -"""Countdown timer widget with ABORT button for safety-critical operations. - -Used before EEPROM write to give the operator 3 seconds to abort. -The ABORT button is auto-focused on mount so a single Enter/Space press cancels. -""" - -import time - -from textual.widget import Widget -from textual.widgets import Button, Static, ProgressBar -from textual.app import ComposeResult -from textual.message import Message -from textual import work - - -class CountdownTimer(Widget): - """3-second countdown with prominent ABORT button. - - Posts CountdownTimer.Completed when the countdown finishes, or - CountdownTimer.Aborted if the operator presses ABORT. - """ - - class Completed(Message): - """Fired when countdown finishes without abort.""" - pass - - class Aborted(Message): - """Fired when user presses ABORT.""" - pass - - DEFAULT_CSS = """ - CountdownTimer { - height: auto; - background: #1a0a0a; - border: round #e04040; - padding: 1 2; - } - CountdownTimer #countdown-label { - text-align: center; - color: #e8a020; - text-style: bold; - margin: 0 0 1 0; - } - CountdownTimer #countdown-bar { - margin: 0 0 1 0; - } - CountdownTimer #countdown-abort { - width: 100%; - min-height: 3; - background: #e04040; - color: #ffffff; - text-style: bold; - border: round #ff6060; - } - CountdownTimer #countdown-abort:hover { - background: #ff4040; - } - CountdownTimer #countdown-abort:focus { - background: #ff2020; - border: round #ffffff; - } - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._running = False - self._aborted = False - - def compose(self) -> ComposeResult: - yield Static("EEPROM WRITE in 3...", id="countdown-label") - yield ProgressBar( - total=30, show_eta=False, show_percentage=False, id="countdown-bar" - ) - yield Button("ABORT", id="countdown-abort", variant="error") - - def on_mount(self) -> None: - self.query_one("#countdown-abort", Button).focus() - - def start(self) -> None: - """Begin the 3-second countdown. Call after mounting.""" - self._running = True - self._aborted = False - self._do_countdown() - - def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "countdown-abort": - self._aborted = True - self._running = False - self.post_message(self.Aborted()) - - @work(thread=True) - def _do_countdown(self) -> None: - """Tick at 100ms intervals for smooth progress bar animation.""" - for tick in range(30, -1, -1): - if not self._running or self._aborted: - return - secs = tick / 10 - self.app.call_from_thread(self._update_display, secs, 30 - tick) - time.sleep(0.1) - if self._running and not self._aborted: - self.app.call_from_thread(self._fire_completed) - - def _update_display(self, secs: float, progress: int) -> None: - if not self.is_mounted: - return - label = self.query_one("#countdown-label", Static) - label.update(f"EEPROM WRITE in {secs:.1f}s \u2014 press ABORT to cancel") - self.query_one("#countdown-bar", ProgressBar).update(progress=progress) - - def _fire_completed(self) -> None: - if self.is_mounted and not self._aborted: - self.post_message(self.Completed()) +"""Countdown timer widget with ABORT button for safety-critical operations. + +Used before EEPROM write to give the operator 3 seconds to abort. +The ABORT button is auto-focused on mount so a single Enter/Space press cancels. +""" + +import time + +from textual.widget import Widget +from textual.widgets import Button, Static, ProgressBar +from textual.app import ComposeResult +from textual.message import Message +from textual import work + + +class CountdownTimer(Widget): + """3-second countdown with prominent ABORT button. + + Posts CountdownTimer.Completed when the countdown finishes, or + CountdownTimer.Aborted if the operator presses ABORT. + """ + + class Completed(Message): + """Fired when countdown finishes without abort.""" + pass + + class Aborted(Message): + """Fired when user presses ABORT.""" + pass + + DEFAULT_CSS = """ + CountdownTimer { + height: auto; + background: #1a0a0a; + border: round #e04040; + padding: 1 2; + } + CountdownTimer #countdown-label { + text-align: center; + color: #e8a020; + text-style: bold; + margin: 0 0 1 0; + } + CountdownTimer #countdown-bar { + margin: 0 0 1 0; + } + CountdownTimer #countdown-abort { + width: 100%; + min-height: 3; + background: #e04040; + color: #ffffff; + text-style: bold; + border: round #ff6060; + } + CountdownTimer #countdown-abort:hover { + background: #ff4040; + } + CountdownTimer #countdown-abort:focus { + background: #ff2020; + border: round #ffffff; + } + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._running = False + self._aborted = False + + def compose(self) -> ComposeResult: + yield Static("EEPROM WRITE in 3...", id="countdown-label") + yield ProgressBar( + total=30, show_eta=False, show_percentage=False, id="countdown-bar" + ) + yield Button("ABORT", id="countdown-abort", variant="error") + + def on_mount(self) -> None: + self.query_one("#countdown-abort", Button).focus() + + def start(self) -> None: + """Begin the 3-second countdown. Call after mounting.""" + self._running = True + self._aborted = False + self._do_countdown() + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "countdown-abort": + self._aborted = True + self._running = False + self.post_message(self.Aborted()) + + @work(thread=True) + def _do_countdown(self) -> None: + """Tick at 100ms intervals for smooth progress bar animation.""" + for tick in range(30, -1, -1): + if not self._running or self._aborted: + return + secs = tick / 10 + self.app.call_from_thread(self._update_display, secs, 30 - tick) + time.sleep(0.1) + if self._running and not self._aborted: + self.app.call_from_thread(self._fire_completed) + + def _update_display(self, secs: float, progress: int) -> None: + if not self.is_mounted: + return + label = self.query_one("#countdown-label", Static) + label.update(f"EEPROM WRITE in {secs:.1f}s \u2014 press ABORT to cancel") + self.query_one("#countdown-bar", ProgressBar).update(progress=progress) + + def _fire_completed(self) -> None: + if self.is_mounted and not self._aborted: + self.post_message(self.Completed()) diff --git a/tui/src/skywalker_tui/widgets/frequency_table.py b/tui/src/skywalker_tui/widgets/frequency_table.py index 442b4f0..995be2c 100644 --- a/tui/src/skywalker_tui/widgets/frequency_table.py +++ b/tui/src/skywalker_tui/widgets/frequency_table.py @@ -1,81 +1,81 @@ -"""DataTable wrapper for transponder scan results.""" - -import sys -import os - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "tools")) - -from textual.widget import Widget -from textual.widgets import DataTable -from textual.app import ComposeResult - -from skywalker_lib import LBAND_ALLOCATIONS - - -def _freq_allocation(freq_mhz: float) -> str: - """Return allocation name for a given frequency.""" - for lo, hi, name in LBAND_ALLOCATIONS: - if lo <= freq_mhz <= hi: - return name - return "" - - -class FrequencyTable(Widget): - """Sortable table of discovered transponders or sweep results.""" - - DEFAULT_CSS = """ - FrequencyTable { - height: 1fr; - min-height: 6; - } - """ - - def __init__(self, show_allocation: bool = False, **kwargs): - super().__init__(**kwargs) - self._show_allocation = show_allocation - self._rows: list[dict] = [] - - def compose(self) -> ComposeResult: - table = DataTable(id="freq-table") - table.cursor_type = "row" - yield table - - def on_mount(self) -> None: - table = self.query_one("#freq-table", DataTable) - cols = ["IF MHz", "RF MHz", "SR ksps", "Power dB", "Locked"] - if self._show_allocation: - cols.append("Allocation") - for col in cols: - table.add_column(col, key=col) - - def add_transponder(self, tp: dict) -> None: - """Add a single transponder result.""" - self._rows.append(tp) - table = self.query_one("#freq-table", DataTable) - - if_mhz = tp.get("if_mhz", 0) - rf_mhz = tp.get("rf_mhz", 0) - sr_ksps = tp.get("sr_ksps", 0) - power_db = tp.get("power_db", 0) - locked = "Yes" if tp.get("locked", False) else "No" - - row = [f"{if_mhz:.1f}", f"{rf_mhz:.0f}", str(sr_ksps), - f"{power_db:.1f}", locked] - if self._show_allocation: - row.append(_freq_allocation(if_mhz)) - - table.add_row(*row) - - def clear_table(self) -> None: - """Remove all rows.""" - self._rows.clear() - table = self.query_one("#freq-table", DataTable) - table.clear() - - def get_selected_transponder(self) -> dict | None: - """Return the currently selected transponder dict.""" - table = self.query_one("#freq-table", DataTable) - cursor_row = table.cursor_row - if cursor_row is not None and 0 <= cursor_row < len(self._rows): - return self._rows[cursor_row] - return None +"""DataTable wrapper for transponder scan results.""" + +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "tools")) + +from textual.widget import Widget +from textual.widgets import DataTable +from textual.app import ComposeResult + +from skywalker_lib import LBAND_ALLOCATIONS + + +def _freq_allocation(freq_mhz: float) -> str: + """Return allocation name for a given frequency.""" + for lo, hi, name in LBAND_ALLOCATIONS: + if lo <= freq_mhz <= hi: + return name + return "" + + +class FrequencyTable(Widget): + """Sortable table of discovered transponders or sweep results.""" + + DEFAULT_CSS = """ + FrequencyTable { + height: 1fr; + min-height: 6; + } + """ + + def __init__(self, show_allocation: bool = False, **kwargs): + super().__init__(**kwargs) + self._show_allocation = show_allocation + self._rows: list[dict] = [] + + def compose(self) -> ComposeResult: + table = DataTable(id="freq-table") + table.cursor_type = "row" + yield table + + def on_mount(self) -> None: + table = self.query_one("#freq-table", DataTable) + cols = ["IF MHz", "RF MHz", "SR ksps", "Power dB", "Locked"] + if self._show_allocation: + cols.append("Allocation") + for col in cols: + table.add_column(col, key=col) + + def add_transponder(self, tp: dict) -> None: + """Add a single transponder result.""" + self._rows.append(tp) + table = self.query_one("#freq-table", DataTable) + + if_mhz = tp.get("if_mhz", 0) + rf_mhz = tp.get("rf_mhz", 0) + sr_ksps = tp.get("sr_ksps", 0) + power_db = tp.get("power_db", 0) + locked = "Yes" if tp.get("locked", False) else "No" + + row = [f"{if_mhz:.1f}", f"{rf_mhz:.0f}", str(sr_ksps), + f"{power_db:.1f}", locked] + if self._show_allocation: + row.append(_freq_allocation(if_mhz)) + + table.add_row(*row) + + def clear_table(self) -> None: + """Remove all rows.""" + self._rows.clear() + table = self.query_one("#freq-table", DataTable) + table.clear() + + def get_selected_transponder(self) -> dict | None: + """Return the currently selected transponder dict.""" + table = self.query_one("#freq-table", DataTable) + cursor_row = table.cursor_row + if cursor_row is not None and 0 <= cursor_row < len(self._rows): + return self._rows[cursor_row] + return None diff --git a/tui/src/skywalker_tui/widgets/hex_view.py b/tui/src/skywalker_tui/widgets/hex_view.py index 1be8faf..5b10aa2 100644 --- a/tui/src/skywalker_tui/widgets/hex_view.py +++ b/tui/src/skywalker_tui/widgets/hex_view.py @@ -1,90 +1,90 @@ -"""Scrollable hex dump widget with diff byte highlighting.""" - -from textual.widget import Widget -from textual.widgets import Static -from textual.app import ComposeResult -from textual.containers import VerticalScroll - - -class HexView(Widget): - """Displays a hex dump of binary data with optional diff highlighting. - - Set data via set_data(), optionally passing a set of byte offsets to - highlight in red (for verify mismatches). Each row shows 16 bytes in - traditional offset : hex : ASCII layout. - """ - - DEFAULT_CSS = """ - HexView { - height: 1fr; - min-height: 6; - background: #0e1420; - border: round #1a2a3a; - } - HexView #hex-scroll { - height: 1fr; - padding: 0 1; - } - HexView #hex-content { - width: auto; - } - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._data = b'' - self._diff_offsets: set[int] = set() - - def compose(self) -> ComposeResult: - with VerticalScroll(id="hex-scroll"): - yield Static("", id="hex-content") - - def set_data(self, data: bytes, diff_offsets: set[int] | None = None) -> None: - """Set data to display. diff_offsets highlights those bytes in red.""" - self._data = data - self._diff_offsets = diff_offsets or set() - self._refresh_display() - - def clear(self) -> None: - """Clear the hex display.""" - self._data = b'' - self._diff_offsets.clear() - if self.is_mounted: - self.query_one("#hex-content", Static).update("") - - def _refresh_display(self) -> None: - if not self.is_mounted: - return - lines = [] - for row_off in range(0, len(self._data), 16): - row = self._data[row_off:row_off + 16] - # Offset column - line = f"[#506878]{row_off:04X}:[/] " - # Hex bytes - hex_parts = [] - for i, b in enumerate(row): - abs_off = row_off + i - if abs_off in self._diff_offsets: - hex_parts.append(f"[bold #e04040]{b:02X}[/]") - else: - hex_parts.append(f"[#7090a8]{b:02X}[/]") - line += " ".join(hex_parts) - # Pad if short row - if len(row) < 16: - line += " " * (16 - len(row)) - # ASCII column - line += " " - ascii_parts = [] - for i, b in enumerate(row): - abs_off = row_off + i - ch = chr(b) if 0x20 <= b < 0x7F else "." - if abs_off in self._diff_offsets: - ascii_parts.append(f"[bold #e04040]{ch}[/]") - else: - ascii_parts.append(f"[#506878]{ch}[/]") - line += "".join(ascii_parts) - lines.append(line) - - self.query_one("#hex-content", Static).update( - "\n".join(lines) if lines else "[#506878]No data[/]" - ) +"""Scrollable hex dump widget with diff byte highlighting.""" + +from textual.widget import Widget +from textual.widgets import Static +from textual.app import ComposeResult +from textual.containers import VerticalScroll + + +class HexView(Widget): + """Displays a hex dump of binary data with optional diff highlighting. + + Set data via set_data(), optionally passing a set of byte offsets to + highlight in red (for verify mismatches). Each row shows 16 bytes in + traditional offset : hex : ASCII layout. + """ + + DEFAULT_CSS = """ + HexView { + height: 1fr; + min-height: 6; + background: #0e1420; + border: round #1a2a3a; + } + HexView #hex-scroll { + height: 1fr; + padding: 0 1; + } + HexView #hex-content { + width: auto; + } + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._data = b'' + self._diff_offsets: set[int] = set() + + def compose(self) -> ComposeResult: + with VerticalScroll(id="hex-scroll"): + yield Static("", id="hex-content") + + def set_data(self, data: bytes, diff_offsets: set[int] | None = None) -> None: + """Set data to display. diff_offsets highlights those bytes in red.""" + self._data = data + self._diff_offsets = diff_offsets or set() + self._refresh_display() + + def clear(self) -> None: + """Clear the hex display.""" + self._data = b'' + self._diff_offsets.clear() + if self.is_mounted: + self.query_one("#hex-content", Static).update("") + + def _refresh_display(self) -> None: + if not self.is_mounted: + return + lines = [] + for row_off in range(0, len(self._data), 16): + row = self._data[row_off:row_off + 16] + # Offset column + line = f"[#506878]{row_off:04X}:[/] " + # Hex bytes + hex_parts = [] + for i, b in enumerate(row): + abs_off = row_off + i + if abs_off in self._diff_offsets: + hex_parts.append(f"[bold #e04040]{b:02X}[/]") + else: + hex_parts.append(f"[#7090a8]{b:02X}[/]") + line += " ".join(hex_parts) + # Pad if short row + if len(row) < 16: + line += " " * (16 - len(row)) + # ASCII column + line += " " + ascii_parts = [] + for i, b in enumerate(row): + abs_off = row_off + i + ch = chr(b) if 0x20 <= b < 0x7F else "." + if abs_off in self._diff_offsets: + ascii_parts.append(f"[bold #e04040]{ch}[/]") + else: + ascii_parts.append(f"[#506878]{ch}[/]") + line += "".join(ascii_parts) + lines.append(line) + + self.query_one("#hex-content", Static).update( + "\n".join(lines) if lines else "[#506878]No data[/]" + ) diff --git a/tui/src/skywalker_tui/widgets/pid_table.py b/tui/src/skywalker_tui/widgets/pid_table.py index b500bd5..cde5942 100644 --- a/tui/src/skywalker_tui/widgets/pid_table.py +++ b/tui/src/skywalker_tui/widgets/pid_table.py @@ -1,74 +1,74 @@ -"""DataTable wrapper for MPEG-2 TS PID distribution statistics. - -Displays per-PID packet counts, percentage share, continuity counter errors, -and well-known PID names. Table is rebuilt on each update to keep the sort -order stable (by PID number ascending). -""" - -from textual.widget import Widget -from textual.widgets import DataTable -from textual.app import ComposeResult - - -class PidTable(Widget): - """Sortable PID statistics table for transport stream analysis.""" - - DEFAULT_CSS = """ - PidTable { - height: 1fr; - min-height: 8; - } - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._pid_data: dict[int, dict] = {} - self._total_packets = 0 - - def compose(self) -> ComposeResult: - table = DataTable(id="pid-stats-table") - table.cursor_type = "row" - yield table - - def on_mount(self) -> None: - table = self.query_one("#pid-stats-table", DataTable) - for col in ["PID", "Count", "%", "CC Errors", "Name"]: - table.add_column(col, key=col) - - def update_pids( - self, - pid_counts: dict[int, int], - cc_errors: dict[int, int], - total: int, - known_pids: dict[int, str], - ) -> None: - """Rebuild the table from accumulated stats. - - Args: - pid_counts: Mapping of PID number to total packet count. - cc_errors: Mapping of PID number to continuity counter error count. - total: Total packet count across all PIDs. - known_pids: Mapping of PID number to human-readable name. - """ - self._total_packets = total - table = self.query_one("#pid-stats-table", DataTable) - table.clear() - - for pid in sorted(pid_counts.keys()): - count = pid_counts[pid] - pct = (count / total * 100) if total > 0 else 0.0 - cc_err = cc_errors.get(pid, 0) - name = known_pids.get(pid, "") - table.add_row( - f"0x{pid:04X}", - f"{count:,}", - f"{pct:.1f}%", - str(cc_err) if cc_err > 0 else "-", - name, - ) - - def clear_table(self) -> None: - """Remove all rows and reset internal state.""" - self._pid_data.clear() - self._total_packets = 0 - self.query_one("#pid-stats-table", DataTable).clear() +"""DataTable wrapper for MPEG-2 TS PID distribution statistics. + +Displays per-PID packet counts, percentage share, continuity counter errors, +and well-known PID names. Table is rebuilt on each update to keep the sort +order stable (by PID number ascending). +""" + +from textual.widget import Widget +from textual.widgets import DataTable +from textual.app import ComposeResult + + +class PidTable(Widget): + """Sortable PID statistics table for transport stream analysis.""" + + DEFAULT_CSS = """ + PidTable { + height: 1fr; + min-height: 8; + } + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._pid_data: dict[int, dict] = {} + self._total_packets = 0 + + def compose(self) -> ComposeResult: + table = DataTable(id="pid-stats-table") + table.cursor_type = "row" + yield table + + def on_mount(self) -> None: + table = self.query_one("#pid-stats-table", DataTable) + for col in ["PID", "Count", "%", "CC Errors", "Name"]: + table.add_column(col, key=col) + + def update_pids( + self, + pid_counts: dict[int, int], + cc_errors: dict[int, int], + total: int, + known_pids: dict[int, str], + ) -> None: + """Rebuild the table from accumulated stats. + + Args: + pid_counts: Mapping of PID number to total packet count. + cc_errors: Mapping of PID number to continuity counter error count. + total: Total packet count across all PIDs. + known_pids: Mapping of PID number to human-readable name. + """ + self._total_packets = total + table = self.query_one("#pid-stats-table", DataTable) + table.clear() + + for pid in sorted(pid_counts.keys()): + count = pid_counts[pid] + pct = (count / total * 100) if total > 0 else 0.0 + cc_err = cc_errors.get(pid, 0) + name = known_pids.get(pid, "") + table.add_row( + f"0x{pid:04X}", + f"{count:,}", + f"{pct:.1f}%", + str(cc_err) if cc_err > 0 else "-", + name, + ) + + def clear_table(self) -> None: + """Remove all rows and reset internal state.""" + self._pid_data.clear() + self._total_packets = 0 + self.query_one("#pid-stats-table", DataTable).clear() diff --git a/tui/src/skywalker_tui/widgets/psi_tree.py b/tui/src/skywalker_tui/widgets/psi_tree.py index 65670d3..f1579fb 100644 --- a/tui/src/skywalker_tui/widgets/psi_tree.py +++ b/tui/src/skywalker_tui/widgets/psi_tree.py @@ -1,111 +1,111 @@ -"""Tree-style display of MPEG-2 PSI structure (PAT/PMT). - -Renders a hierarchical view of the Program Association Table and its -child Program Map Tables using Rich markup inside a Static widget. -Shows transport stream ID, program numbers, PMT PIDs, PCR PIDs, -and elementary stream types with their PIDs. -""" - -from textual.widget import Widget -from textual.widgets import Static -from textual.app import ComposeResult - - -class PsiTree(Widget): - """Hierarchical PAT/PMT display for transport stream program structure.""" - - DEFAULT_CSS = """ - PsiTree { - height: 1fr; - min-height: 8; - background: #0e1420; - border: round #1a2a3a; - padding: 1; - overflow-y: auto; - } - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._pat: dict | None = None - self._pmts: dict[int, dict] = {} - - def compose(self) -> ComposeResult: - yield Static( - "[#506878]Waiting for PSI data...[/]", id="psi-content" - ) - - def update_pat(self, pat: dict) -> None: - """Update the Program Association Table and redraw.""" - self._pat = pat - self._refresh() - - def update_pmt(self, pmt_pid: int, pmt: dict) -> None: - """Update a Program Map Table and redraw.""" - self._pmts[pmt_pid] = pmt - self._refresh() - - def clear_tree(self) -> None: - """Reset all PSI state and show placeholder.""" - self._pat = None - self._pmts.clear() - if self.is_mounted: - self.query_one("#psi-content", Static).update( - "[#506878]Waiting for PSI data...[/]" - ) - - def _refresh(self) -> None: - """Rebuild the tree markup from current PAT/PMT data.""" - if not self.is_mounted: - return - - lines: list[str] = [] - - if self._pat: - tsid = self._pat.get("transport_stream_id", 0) - ver = self._pat.get("version", 0) - lines.append( - f"[bold #00d4aa]PAT[/] [#506878]TSID=0x{tsid:04X} v{ver}[/]" - ) - - programs = self._pat.get("programs", {}) - # Separate NIT (program 0) from real programs - real_progs = {k: v for k, v in programs.items() if k != 0} - - for prog_num, pmt_pid in sorted(programs.items()): - if prog_num == 0: - lines.append( - f" [#7090a8]\u251c\u2500[/] [#506878]NIT[/] " - f"PID=0x{pmt_pid:04X}" - ) - else: - is_last = prog_num == max(real_progs.keys()) - prefix = "\u2514\u2500" if is_last else "\u251c\u2500" - lines.append( - f" [#7090a8]{prefix}[/] " - f"[bold #c8d0d8]Program {prog_num}[/] " - f"PMT=0x{pmt_pid:04X}" - ) - # Expand PMT details if available - if pmt_pid in self._pmts: - pmt = self._pmts[pmt_pid] - pcr_pid = pmt.get("pcr_pid", 0) - indent = " " if is_last else "\u2502 " - lines.append( - f" {indent} [#506878]PCR PID=0x{pcr_pid:04X}[/]" - ) - streams = pmt.get("streams", []) - for j, s in enumerate(streams): - s_last = j == len(streams) - 1 - s_prefix = "\u2514\u2500" if s_last else "\u251c\u2500" - type_name = s.get("type_name", "Unknown") - epid = s.get("elementary_pid", 0) - lines.append( - f" {indent} [#7090a8]{s_prefix}[/] " - f"[#c8d0d8]{type_name}[/] " - f"PID=0x{epid:04X}" - ) - else: - lines.append("[#506878]No PAT received yet[/]") - - self.query_one("#psi-content", Static).update("\n".join(lines)) +"""Tree-style display of MPEG-2 PSI structure (PAT/PMT). + +Renders a hierarchical view of the Program Association Table and its +child Program Map Tables using Rich markup inside a Static widget. +Shows transport stream ID, program numbers, PMT PIDs, PCR PIDs, +and elementary stream types with their PIDs. +""" + +from textual.widget import Widget +from textual.widgets import Static +from textual.app import ComposeResult + + +class PsiTree(Widget): + """Hierarchical PAT/PMT display for transport stream program structure.""" + + DEFAULT_CSS = """ + PsiTree { + height: 1fr; + min-height: 8; + background: #0e1420; + border: round #1a2a3a; + padding: 1; + overflow-y: auto; + } + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._pat: dict | None = None + self._pmts: dict[int, dict] = {} + + def compose(self) -> ComposeResult: + yield Static( + "[#506878]Waiting for PSI data...[/]", id="psi-content" + ) + + def update_pat(self, pat: dict) -> None: + """Update the Program Association Table and redraw.""" + self._pat = pat + self._refresh() + + def update_pmt(self, pmt_pid: int, pmt: dict) -> None: + """Update a Program Map Table and redraw.""" + self._pmts[pmt_pid] = pmt + self._refresh() + + def clear_tree(self) -> None: + """Reset all PSI state and show placeholder.""" + self._pat = None + self._pmts.clear() + if self.is_mounted: + self.query_one("#psi-content", Static).update( + "[#506878]Waiting for PSI data...[/]" + ) + + def _refresh(self) -> None: + """Rebuild the tree markup from current PAT/PMT data.""" + if not self.is_mounted: + return + + lines: list[str] = [] + + if self._pat: + tsid = self._pat.get("transport_stream_id", 0) + ver = self._pat.get("version", 0) + lines.append( + f"[bold #00d4aa]PAT[/] [#506878]TSID=0x{tsid:04X} v{ver}[/]" + ) + + programs = self._pat.get("programs", {}) + # Separate NIT (program 0) from real programs + real_progs = {k: v for k, v in programs.items() if k != 0} + + for prog_num, pmt_pid in sorted(programs.items()): + if prog_num == 0: + lines.append( + f" [#7090a8]\u251c\u2500[/] [#506878]NIT[/] " + f"PID=0x{pmt_pid:04X}" + ) + else: + is_last = prog_num == max(real_progs.keys()) + prefix = "\u2514\u2500" if is_last else "\u251c\u2500" + lines.append( + f" [#7090a8]{prefix}[/] " + f"[bold #c8d0d8]Program {prog_num}[/] " + f"PMT=0x{pmt_pid:04X}" + ) + # Expand PMT details if available + if pmt_pid in self._pmts: + pmt = self._pmts[pmt_pid] + pcr_pid = pmt.get("pcr_pid", 0) + indent = " " if is_last else "\u2502 " + lines.append( + f" {indent} [#506878]PCR PID=0x{pcr_pid:04X}[/]" + ) + streams = pmt.get("streams", []) + for j, s in enumerate(streams): + s_last = j == len(streams) - 1 + s_prefix = "\u2514\u2500" if s_last else "\u251c\u2500" + type_name = s.get("type_name", "Unknown") + epid = s.get("elementary_pid", 0) + lines.append( + f" {indent} [#7090a8]{s_prefix}[/] " + f"[#c8d0d8]{type_name}[/] " + f"PID=0x{epid:04X}" + ) + else: + lines.append("[#506878]No PAT received yet[/]") + + self.query_one("#psi-content", Static).update("\n".join(lines)) diff --git a/tui/src/skywalker_tui/widgets/radar_scope.py b/tui/src/skywalker_tui/widgets/radar_scope.py index bdae576..49c54c5 100644 --- a/tui/src/skywalker_tui/widgets/radar_scope.py +++ b/tui/src/skywalker_tui/widgets/radar_scope.py @@ -1,276 +1,276 @@ -"""Radar scope widget — P1 green phosphor CRT aesthetic. - -Renders a circular radar display using half-block characters for 2x vertical -resolution. Each terminal cell becomes two pixel rows via the ▀ character -with independent fg (top) and bg (bottom) colors. - -The sweep beam rotates through 360 positions. Signal strength determines -radial distance from center. Older samples decay in brightness (phosphor -persistence). Concentric range rings and a crosshair provide scale reference. -""" - -import math -from collections import deque - -from textual.widget import Widget -from textual.strip import Strip -from rich.segment import Segment -from rich.style import Style - - -# P1 green phosphor palette — 8 intensity levels from dead to peak burn -PHOSPHOR = [ - "#0a0a0a", # background / dead pixel - "#0a1a0a", # very faint trace - "#0a2a0a", # dim afterglow - "#0a3a0a", # fading - "#0a4a0f", # moderate - "#0a5a10", # bright trace - "#0a6a15", # very bright - "#10ff30", # peak phosphor burn -] - -PHOSPHOR_STYLES = [Style(color=c) for c in PHOSPHOR] -BG_COLOR = "#0a0a0a" -BG_STYLE = Style(color=BG_COLOR, bgcolor=BG_COLOR) -RING_COLOR = "#0a2a0a" -CROSSHAIR_COLOR = "#0a3a0a" -LOCK_RING_COLOR = "#10ff30" - - -class RadarScope(Widget): - """Circular radar scope with phosphor decay and sweep beam.""" - - DEFAULT_CSS = """ - RadarScope { - min-height: 12; - min-width: 24; - background: #0a0a0a; - } - """ - - def __init__(self, max_samples: int = 360, **kwargs): - super().__init__(**kwargs) - self._samples: deque[float] = deque([0.0] * max_samples, maxlen=max_samples) - self._max_samples = max_samples - self._angle_idx = 0 # current sweep position (0..max_samples-1) - self._locked = False - - # Pre-computed geometry + LUTs (rebuilt on resize) - self._cx = 0.0 - self._cy = 0.0 - self._radius = 0.0 - self._cols = 0 - self._rows = 0 # pixel rows (2x terminal rows) - # Per-pixel LUTs: distance from center, angle-to-sample index - self._dist_lut: list[list[float]] = [] - self._angle_lut: list[list[int]] = [] - # Per-pixel dx/dy for crosshair checks - self._dx_lut: list[list[float]] = [] - self._dy_lut: list[list[float]] = [] - - def push(self, snr_db: float, max_snr: float = 16.0) -> None: - """Push a new signal sample. SNR is normalized to 0.0-1.0 range.""" - normalized = max(0.0, min(1.0, snr_db / max(0.1, max_snr))) - self._samples.append(normalized) - self._angle_idx = (self._angle_idx + 1) % self._max_samples - self.refresh() - - def set_locked(self, locked: bool) -> None: - if locked != self._locked: - self._locked = locked - self.refresh() - - def _recompute_geometry(self, width: int, height: int) -> None: - """Recompute center, radius, and per-pixel LUTs for current dimensions. - - Pre-computes dist/angle for every pixel so render_line() avoids - math.sqrt/math.atan2 per pixel per frame — ~O(1) lookup instead. - """ - self._cols = width - self._rows = height * 2 # 2x vertical resolution via half-blocks - # Radius fits within both dimensions, accounting for ~2:1 terminal char aspect - rx = (width - 2) / 2.0 - ry = (height * 2 - 2) / 2.0 - self._radius = min(rx, ry) - cx = width / 2.0 - cy = height # center in pixel rows (height * 2 / 2) - self._cx = cx - self._cy = cy - - # Build LUTs for all pixel rows (height * 2) × columns - pixel_rows = height * 2 - dist_lut = [] - angle_lut = [] - dx_lut = [] - dy_lut = [] - sqrt = math.sqrt - atan2 = math.atan2 - degrees = math.degrees - ms = self._max_samples - - for py in range(pixel_rows): - d_row = [] - a_row = [] - dxr = [] - dyr = [] - dy = py - cy - for px in range(width): - dx = px - cx - dist = sqrt(dx * dx + dy * dy) - d_row.append(dist) - dxr.append(dx) - dyr.append(dy) - if dist >= 1.0: - angle = atan2(dy, dx) - angle_deg = (degrees(angle) + 360) % 360 - a_row.append(int(angle_deg / 360 * ms) % ms) - else: - a_row.append(0) # center pixel — angle irrelevant - dist_lut.append(d_row) - angle_lut.append(a_row) - dx_lut.append(dxr) - dy_lut.append(dyr) - - self._dist_lut = dist_lut - self._angle_lut = angle_lut - self._dx_lut = dx_lut - self._dy_lut = dy_lut - - def render_line(self, y: int) -> Strip: - """Render one terminal row of the radar scope.""" - width = self.size.width - height = self.size.height - - if width < 4 or height < 4: - return Strip([Segment(" " * width, BG_STYLE)]) - - if self._cols != width or self._rows != height * 2: - self._recompute_geometry(width, height) - - radius = self._radius - if radius < 2: - return Strip([Segment(" " * width, BG_STYLE)]) - - # Snapshot mutable state for consistent rendering across the frame - samples = list(self._samples) - angle_idx = self._angle_idx - locked = self._locked - - # Two pixel rows per terminal row - py_top = y * 2 - py_bot = y * 2 + 1 - - # LUT rows for this terminal row - dist_top = self._dist_lut[py_top] if py_top < len(self._dist_lut) else None - dist_bot = self._dist_lut[py_bot] if py_bot < len(self._dist_lut) else None - angle_top = self._angle_lut[py_top] if py_top < len(self._angle_lut) else None - angle_bot = self._angle_lut[py_bot] if py_bot < len(self._angle_lut) else None - dx_top = self._dx_lut[py_top] if py_top < len(self._dx_lut) else None - dx_bot = self._dx_lut[py_bot] if py_bot < len(self._dx_lut) else None - dy_top = self._dy_lut[py_top] if py_top < len(self._dy_lut) else None - dy_bot = self._dy_lut[py_bot] if py_bot < len(self._dy_lut) else None - - if not dist_top or not dist_bot: - return Strip([Segment(" " * width, BG_STYLE)]) - - _pi = self._pixel_intensity - segments = [] - for x in range(width): - top_intensity = _pi( - dist_top[x], angle_top[x], dx_top[x], dy_top[x], - radius, samples, angle_idx, locked, - ) - bot_intensity = _pi( - dist_bot[x], angle_bot[x], dx_bot[x], dy_bot[x], - radius, samples, angle_idx, locked, - ) - - top_color = PHOSPHOR[top_intensity] - bot_color = PHOSPHOR[bot_intensity] - - # ▀ char: fg = top pixel, bg = bottom pixel - style = Style(color=top_color, bgcolor=bot_color) - segments.append(Segment("\u2580", style)) - - return Strip(segments) - - def _pixel_intensity(self, dist: float, sample_idx: int, - dx: float, dy: float, - radius: float, samples: list[float], - angle_idx: int, locked: bool) -> int: - """Compute phosphor intensity (0-7) for a single pixel. - - Uses pre-computed dist/sample_idx/dx/dy from LUTs (no trig per pixel). - Uses snapshot data (samples, angle_idx, locked) rather than mutable - instance state for frame-consistent rendering. - """ - # Outside the scope circle - if dist > radius + 1: - return 0 - - # Lock ring — outer edge glow - if locked and abs(dist - radius) < 1.5: - return 7 - - # Range rings at 25%, 50%, 75% radius - for ring_r in (0.25, 0.5, 0.75): - if abs(dist - radius * ring_r) < 0.7: - return 2 # dim ring - - # Outer boundary ring - if abs(dist - radius) < 0.7: - return 2 - - # Crosshair (horizontal and vertical through center) - if abs(dx) < 0.7 and dist < radius: - return 2 - if abs(dy) < 0.7 and dist < radius: - return 2 - - # Signal trace — map pixel angle to sample buffer - if dist < 1.0: - return 1 # center dot - - if dist > radius: - return 0 - - # sample_idx already computed in LUT via atan2 → degrees → index - strength = samples[sample_idx] - - # Signal renders as a blip at radial distance proportional to strength - signal_dist = strength * radius * 0.85 # 85% of radius at max - noise_floor_dist = radius * 0.05 # tiny noise floor ring - - # Distance from the signal blip center - blip_dist = abs(dist - signal_dist) - noise_dist = abs(dist - noise_floor_dist) - - # Age-based decay: how far behind the sweep beam is this angle? - age = (angle_idx - sample_idx) % self._max_samples - age_ratio = age / self._max_samples # 0 = newest, 1 = oldest - - # Intensity from signal blip proximity - if strength > 0.02 and blip_dist < 2.0: - proximity = max(0.0, 1.0 - blip_dist / 2.0) - freshness = max(0.0, 1.0 - age_ratio * 1.2) - raw_intensity = proximity * freshness * strength - return max(1, min(7, int(raw_intensity * 7 + 0.5))) - - # Sweep beam — the most recent angle is brightest - if age < 3: - beam_intensity = max(0.0, 1.0 - age / 3.0) - if dist < radius * 0.9: - return max(1, min(5, int(beam_intensity * 5))) - - # Noise floor glow - if noise_dist < 1.0 and strength > 0: - return 1 - - # Fill between center and signal (faint trail) - if strength > 0.1 and dist < signal_dist and age_ratio < 0.5: - trail = max(0.0, (1.0 - age_ratio * 2) * 0.3) - if trail > 0.05: - return 1 - - return 0 +"""Radar scope widget — P1 green phosphor CRT aesthetic. + +Renders a circular radar display using half-block characters for 2x vertical +resolution. Each terminal cell becomes two pixel rows via the ▀ character +with independent fg (top) and bg (bottom) colors. + +The sweep beam rotates through 360 positions. Signal strength determines +radial distance from center. Older samples decay in brightness (phosphor +persistence). Concentric range rings and a crosshair provide scale reference. +""" + +import math +from collections import deque + +from textual.widget import Widget +from textual.strip import Strip +from rich.segment import Segment +from rich.style import Style + + +# P1 green phosphor palette — 8 intensity levels from dead to peak burn +PHOSPHOR = [ + "#0a0a0a", # background / dead pixel + "#0a1a0a", # very faint trace + "#0a2a0a", # dim afterglow + "#0a3a0a", # fading + "#0a4a0f", # moderate + "#0a5a10", # bright trace + "#0a6a15", # very bright + "#10ff30", # peak phosphor burn +] + +PHOSPHOR_STYLES = [Style(color=c) for c in PHOSPHOR] +BG_COLOR = "#0a0a0a" +BG_STYLE = Style(color=BG_COLOR, bgcolor=BG_COLOR) +RING_COLOR = "#0a2a0a" +CROSSHAIR_COLOR = "#0a3a0a" +LOCK_RING_COLOR = "#10ff30" + + +class RadarScope(Widget): + """Circular radar scope with phosphor decay and sweep beam.""" + + DEFAULT_CSS = """ + RadarScope { + min-height: 12; + min-width: 24; + background: #0a0a0a; + } + """ + + def __init__(self, max_samples: int = 360, **kwargs): + super().__init__(**kwargs) + self._samples: deque[float] = deque([0.0] * max_samples, maxlen=max_samples) + self._max_samples = max_samples + self._angle_idx = 0 # current sweep position (0..max_samples-1) + self._locked = False + + # Pre-computed geometry + LUTs (rebuilt on resize) + self._cx = 0.0 + self._cy = 0.0 + self._radius = 0.0 + self._cols = 0 + self._rows = 0 # pixel rows (2x terminal rows) + # Per-pixel LUTs: distance from center, angle-to-sample index + self._dist_lut: list[list[float]] = [] + self._angle_lut: list[list[int]] = [] + # Per-pixel dx/dy for crosshair checks + self._dx_lut: list[list[float]] = [] + self._dy_lut: list[list[float]] = [] + + def push(self, snr_db: float, max_snr: float = 16.0) -> None: + """Push a new signal sample. SNR is normalized to 0.0-1.0 range.""" + normalized = max(0.0, min(1.0, snr_db / max(0.1, max_snr))) + self._samples.append(normalized) + self._angle_idx = (self._angle_idx + 1) % self._max_samples + self.refresh() + + def set_locked(self, locked: bool) -> None: + if locked != self._locked: + self._locked = locked + self.refresh() + + def _recompute_geometry(self, width: int, height: int) -> None: + """Recompute center, radius, and per-pixel LUTs for current dimensions. + + Pre-computes dist/angle for every pixel so render_line() avoids + math.sqrt/math.atan2 per pixel per frame — ~O(1) lookup instead. + """ + self._cols = width + self._rows = height * 2 # 2x vertical resolution via half-blocks + # Radius fits within both dimensions, accounting for ~2:1 terminal char aspect + rx = (width - 2) / 2.0 + ry = (height * 2 - 2) / 2.0 + self._radius = min(rx, ry) + cx = width / 2.0 + cy = height # center in pixel rows (height * 2 / 2) + self._cx = cx + self._cy = cy + + # Build LUTs for all pixel rows (height * 2) × columns + pixel_rows = height * 2 + dist_lut = [] + angle_lut = [] + dx_lut = [] + dy_lut = [] + sqrt = math.sqrt + atan2 = math.atan2 + degrees = math.degrees + ms = self._max_samples + + for py in range(pixel_rows): + d_row = [] + a_row = [] + dxr = [] + dyr = [] + dy = py - cy + for px in range(width): + dx = px - cx + dist = sqrt(dx * dx + dy * dy) + d_row.append(dist) + dxr.append(dx) + dyr.append(dy) + if dist >= 1.0: + angle = atan2(dy, dx) + angle_deg = (degrees(angle) + 360) % 360 + a_row.append(int(angle_deg / 360 * ms) % ms) + else: + a_row.append(0) # center pixel — angle irrelevant + dist_lut.append(d_row) + angle_lut.append(a_row) + dx_lut.append(dxr) + dy_lut.append(dyr) + + self._dist_lut = dist_lut + self._angle_lut = angle_lut + self._dx_lut = dx_lut + self._dy_lut = dy_lut + + def render_line(self, y: int) -> Strip: + """Render one terminal row of the radar scope.""" + width = self.size.width + height = self.size.height + + if width < 4 or height < 4: + return Strip([Segment(" " * width, BG_STYLE)]) + + if self._cols != width or self._rows != height * 2: + self._recompute_geometry(width, height) + + radius = self._radius + if radius < 2: + return Strip([Segment(" " * width, BG_STYLE)]) + + # Snapshot mutable state for consistent rendering across the frame + samples = list(self._samples) + angle_idx = self._angle_idx + locked = self._locked + + # Two pixel rows per terminal row + py_top = y * 2 + py_bot = y * 2 + 1 + + # LUT rows for this terminal row + dist_top = self._dist_lut[py_top] if py_top < len(self._dist_lut) else None + dist_bot = self._dist_lut[py_bot] if py_bot < len(self._dist_lut) else None + angle_top = self._angle_lut[py_top] if py_top < len(self._angle_lut) else None + angle_bot = self._angle_lut[py_bot] if py_bot < len(self._angle_lut) else None + dx_top = self._dx_lut[py_top] if py_top < len(self._dx_lut) else None + dx_bot = self._dx_lut[py_bot] if py_bot < len(self._dx_lut) else None + dy_top = self._dy_lut[py_top] if py_top < len(self._dy_lut) else None + dy_bot = self._dy_lut[py_bot] if py_bot < len(self._dy_lut) else None + + if not dist_top or not dist_bot: + return Strip([Segment(" " * width, BG_STYLE)]) + + _pi = self._pixel_intensity + segments = [] + for x in range(width): + top_intensity = _pi( + dist_top[x], angle_top[x], dx_top[x], dy_top[x], + radius, samples, angle_idx, locked, + ) + bot_intensity = _pi( + dist_bot[x], angle_bot[x], dx_bot[x], dy_bot[x], + radius, samples, angle_idx, locked, + ) + + top_color = PHOSPHOR[top_intensity] + bot_color = PHOSPHOR[bot_intensity] + + # ▀ char: fg = top pixel, bg = bottom pixel + style = Style(color=top_color, bgcolor=bot_color) + segments.append(Segment("\u2580", style)) + + return Strip(segments) + + def _pixel_intensity(self, dist: float, sample_idx: int, + dx: float, dy: float, + radius: float, samples: list[float], + angle_idx: int, locked: bool) -> int: + """Compute phosphor intensity (0-7) for a single pixel. + + Uses pre-computed dist/sample_idx/dx/dy from LUTs (no trig per pixel). + Uses snapshot data (samples, angle_idx, locked) rather than mutable + instance state for frame-consistent rendering. + """ + # Outside the scope circle + if dist > radius + 1: + return 0 + + # Lock ring — outer edge glow + if locked and abs(dist - radius) < 1.5: + return 7 + + # Range rings at 25%, 50%, 75% radius + for ring_r in (0.25, 0.5, 0.75): + if abs(dist - radius * ring_r) < 0.7: + return 2 # dim ring + + # Outer boundary ring + if abs(dist - radius) < 0.7: + return 2 + + # Crosshair (horizontal and vertical through center) + if abs(dx) < 0.7 and dist < radius: + return 2 + if abs(dy) < 0.7 and dist < radius: + return 2 + + # Signal trace — map pixel angle to sample buffer + if dist < 1.0: + return 1 # center dot + + if dist > radius: + return 0 + + # sample_idx already computed in LUT via atan2 → degrees → index + strength = samples[sample_idx] + + # Signal renders as a blip at radial distance proportional to strength + signal_dist = strength * radius * 0.85 # 85% of radius at max + noise_floor_dist = radius * 0.05 # tiny noise floor ring + + # Distance from the signal blip center + blip_dist = abs(dist - signal_dist) + noise_dist = abs(dist - noise_floor_dist) + + # Age-based decay: how far behind the sweep beam is this angle? + age = (angle_idx - sample_idx) % self._max_samples + age_ratio = age / self._max_samples # 0 = newest, 1 = oldest + + # Intensity from signal blip proximity + if strength > 0.02 and blip_dist < 2.0: + proximity = max(0.0, 1.0 - blip_dist / 2.0) + freshness = max(0.0, 1.0 - age_ratio * 1.2) + raw_intensity = proximity * freshness * strength + return max(1, min(7, int(raw_intensity * 7 + 0.5))) + + # Sweep beam — the most recent angle is brightest + if age < 3: + beam_intensity = max(0.0, 1.0 - age / 3.0) + if dist < radius * 0.9: + return max(1, min(5, int(beam_intensity * 5))) + + # Noise floor glow + if noise_dist < 1.0 and strength > 0: + return 1 + + # Fill between center and signal (faint trail) + if strength > 0.1 and dist < signal_dist and age_ratio < 0.5: + trail = max(0.0, (1.0 - age_ratio * 2) * 0.3) + if trail > 0.05: + return 1 + + return 0 diff --git a/tui/src/skywalker_tui/widgets/signal_gauge.py b/tui/src/skywalker_tui/widgets/signal_gauge.py index 80b6c33..8d1f26e 100644 --- a/tui/src/skywalker_tui/widgets/signal_gauge.py +++ b/tui/src/skywalker_tui/widgets/signal_gauge.py @@ -1,120 +1,120 @@ -"""Large signal strength gauge with SNR bar and lock indicator.""" - -from textual.app import ComposeResult -from textual.widget import Widget -from textual.widgets import Static -from textual.reactive import reactive - - -# Bar characters for sub-block resolution -_BARS = " ▏▎▍▌▋▊▉█" - - -def _snr_color(snr_db: float) -> str: - """Map SNR to a hex color: blue → cyan → green → yellow → red.""" - if snr_db < 2: - return "#1565c0" - elif snr_db < 4: - return "#0097a7" - elif snr_db < 6: - return "#00bfa5" - elif snr_db < 8: - return "#00d4aa" - elif snr_db < 10: - return "#4caf50" - elif snr_db < 12: - return "#8bc34a" - elif snr_db < 14: - return "#cddc39" - elif snr_db < 16: - return "#ffc107" - else: - return "#f44336" - - -def _build_bar(pct: float, width: int = 40) -> str: - """Build a Unicode block bar string with sub-character precision.""" - pct = max(0.0, min(100.0, pct)) - ratio = pct / 100.0 - full = int(ratio * width) - remainder = (ratio * width) - full - partial = int(remainder * (len(_BARS) - 1)) - bar = "█" * full - if full < width: - bar += _BARS[partial] - bar += " " * (width - full - 1) - return bar - - -class SignalGauge(Widget): - """Large signal strength display with SNR, power, and lock state.""" - - DEFAULT_CSS = """ - SignalGauge { - height: auto; - padding: 1 2; - background: #0e1420; - border: round #1a2a3a; - margin: 0 0 1 0; - } - SignalGauge #gauge-header { - height: 1; - margin: 0 0 1 0; - } - SignalGauge #gauge-bar-line { - height: 1; - } - SignalGauge #gauge-details { - height: 1; - margin: 1 0 0 0; - color: #506878; - } - """ - - snr_db = reactive(0.0) - snr_pct = reactive(0.0) - power_db = reactive(-40.0) - locked = reactive(False) - agc1 = reactive(0) - - def compose(self) -> ComposeResult: - yield Static("", id="gauge-header") - yield Static("", id="gauge-bar-line") - yield Static("", id="gauge-details") - - def watch_snr_db(self) -> None: - self._refresh_display() - - def watch_locked(self) -> None: - self._refresh_display() - - def update_signal(self, sig: dict) -> None: - """Update from a signal_monitor() result dict.""" - self.snr_db = sig.get("snr_db", 0.0) - self.snr_pct = sig.get("snr_pct", 0.0) - self.power_db = sig.get("power_db", -40.0) - self.locked = sig.get("locked", False) - self.agc1 = sig.get("agc1", 0) - - def _refresh_display(self) -> None: - if not self.is_mounted: - return - - color = _snr_color(self.snr_db) - lock_str = "[bold #00e060]LOCK[/]" if self.locked else "[#e04040]NO LOCK[/]" - - header = self.query_one("#gauge-header", Static) - header.update( - f" {lock_str} " - f"[bold {color}]{self.snr_db:6.1f} dB[/] " - f"[#506878]{self.snr_pct:5.1f}%[/]" - ) - - bar_str = _build_bar(self.snr_pct, width=50) - bar_line = self.query_one("#gauge-bar-line", Static) - bar_line.update(f" [{color}]{bar_str}[/]") - - details = self.query_one("#gauge-details", Static) - details.update( - f" Power: {self.power_db:6.1f} dB AGC: {self.agc1:5d}" - ) +"""Large signal strength gauge with SNR bar and lock indicator.""" + +from textual.app import ComposeResult +from textual.widget import Widget +from textual.widgets import Static +from textual.reactive import reactive + + +# Bar characters for sub-block resolution +_BARS = " ▏▎▍▌▋▊▉█" + + +def _snr_color(snr_db: float) -> str: + """Map SNR to a hex color: blue → cyan → green → yellow → red.""" + if snr_db < 2: + return "#1565c0" + elif snr_db < 4: + return "#0097a7" + elif snr_db < 6: + return "#00bfa5" + elif snr_db < 8: + return "#00d4aa" + elif snr_db < 10: + return "#4caf50" + elif snr_db < 12: + return "#8bc34a" + elif snr_db < 14: + return "#cddc39" + elif snr_db < 16: + return "#ffc107" + else: + return "#f44336" + + +def _build_bar(pct: float, width: int = 40) -> str: + """Build a Unicode block bar string with sub-character precision.""" + pct = max(0.0, min(100.0, pct)) + ratio = pct / 100.0 + full = int(ratio * width) + remainder = (ratio * width) - full + partial = int(remainder * (len(_BARS) - 1)) + bar = "█" * full + if full < width: + bar += _BARS[partial] + bar += " " * (width - full - 1) + return bar + + +class SignalGauge(Widget): + """Large signal strength display with SNR, power, and lock state.""" + + DEFAULT_CSS = """ + SignalGauge { + height: auto; + padding: 1 2; + background: #0e1420; + border: round #1a2a3a; + margin: 0 0 1 0; + } + SignalGauge #gauge-header { + height: 1; + margin: 0 0 1 0; + } + SignalGauge #gauge-bar-line { + height: 1; + } + SignalGauge #gauge-details { + height: 1; + margin: 1 0 0 0; + color: #506878; + } + """ + + snr_db = reactive(0.0) + snr_pct = reactive(0.0) + power_db = reactive(-40.0) + locked = reactive(False) + agc1 = reactive(0) + + def compose(self) -> ComposeResult: + yield Static("", id="gauge-header") + yield Static("", id="gauge-bar-line") + yield Static("", id="gauge-details") + + def watch_snr_db(self) -> None: + self._refresh_display() + + def watch_locked(self) -> None: + self._refresh_display() + + def update_signal(self, sig: dict) -> None: + """Update from a signal_monitor() result dict.""" + self.snr_db = sig.get("snr_db", 0.0) + self.snr_pct = sig.get("snr_pct", 0.0) + self.power_db = sig.get("power_db", -40.0) + self.locked = sig.get("locked", False) + self.agc1 = sig.get("agc1", 0) + + def _refresh_display(self) -> None: + if not self.is_mounted: + return + + color = _snr_color(self.snr_db) + lock_str = "[bold #00e060]LOCK[/]" if self.locked else "[#e04040]NO LOCK[/]" + + header = self.query_one("#gauge-header", Static) + header.update( + f" {lock_str} " + f"[bold {color}]{self.snr_db:6.1f} dB[/] " + f"[#506878]{self.snr_pct:5.1f}%[/]" + ) + + bar_str = _build_bar(self.snr_pct, width=50) + bar_line = self.query_one("#gauge-bar-line", Static) + bar_line.update(f" [{color}]{bar_str}[/]") + + details = self.query_one("#gauge-details", Static) + details.update( + f" Power: {self.power_db:6.1f} dB AGC: {self.agc1:5d}" + ) diff --git a/tui/src/skywalker_tui/widgets/sparkline_widget.py b/tui/src/skywalker_tui/widgets/sparkline_widget.py index b485310..1026de2 100644 --- a/tui/src/skywalker_tui/widgets/sparkline_widget.py +++ b/tui/src/skywalker_tui/widgets/sparkline_widget.py @@ -1,71 +1,71 @@ -"""Rolling sparkline time series widget using Unicode spark characters.""" - -from collections import deque - -from textual.widget import Widget -from textual.widgets import Static -from textual.app import ComposeResult - -_SPARKS = "▁▂▃▄▅▆▇█" - - -class SparklineWidget(Widget): - """Rolling time-series display using Unicode block characters.""" - - DEFAULT_CSS = """ - SparklineWidget { - height: 3; - padding: 0 1; - background: #0e1420; - border: round #1a2a3a; - margin: 0 0 1 0; - } - SparklineWidget #spark-label { - height: 1; - color: #506878; - } - SparklineWidget #spark-line { - height: 1; - } - """ - - def __init__(self, title: str = "History", max_width: int = 80, - color: str = "#00d4aa", **kwargs): - super().__init__(**kwargs) - self._title = title - self._max_width = max_width - self._color = color - self._values: deque[float] = deque(maxlen=max_width) - - def compose(self) -> ComposeResult: - yield Static(f"[#506878]{self._title}[/]", id="spark-label") - yield Static("", id="spark-line") - - def push(self, value: float) -> None: - """Add a new data point and refresh the display.""" - self._values.append(value) - self._refresh() - - def clear(self) -> None: - self._values.clear() - if self.is_mounted: - self.query_one("#spark-line", Static).update("") - - def _refresh(self) -> None: - if not self.is_mounted or not self._values: - return - - vals = list(self._values) - mn = min(vals) - mx = max(vals) - rng = mx - mn if mx != mn else 1.0 - - chars = [] - for v in vals: - idx = int((v - mn) / rng * (len(_SPARKS) - 1)) - idx = max(0, min(len(_SPARKS) - 1, idx)) - chars.append(_SPARKS[idx]) - - spark_str = "".join(chars) - line = self.query_one("#spark-line", Static) - line.update(f"[{self._color}]{spark_str}[/] [{mn:.1f} .. {mx:.1f}]") +"""Rolling sparkline time series widget using Unicode spark characters.""" + +from collections import deque + +from textual.widget import Widget +from textual.widgets import Static +from textual.app import ComposeResult + +_SPARKS = "▁▂▃▄▅▆▇█" + + +class SparklineWidget(Widget): + """Rolling time-series display using Unicode block characters.""" + + DEFAULT_CSS = """ + SparklineWidget { + height: 3; + padding: 0 1; + background: #0e1420; + border: round #1a2a3a; + margin: 0 0 1 0; + } + SparklineWidget #spark-label { + height: 1; + color: #506878; + } + SparklineWidget #spark-line { + height: 1; + } + """ + + def __init__(self, title: str = "History", max_width: int = 80, + color: str = "#00d4aa", **kwargs): + super().__init__(**kwargs) + self._title = title + self._max_width = max_width + self._color = color + self._values: deque[float] = deque(maxlen=max_width) + + def compose(self) -> ComposeResult: + yield Static(f"[#506878]{self._title}[/]", id="spark-label") + yield Static("", id="spark-line") + + def push(self, value: float) -> None: + """Add a new data point and refresh the display.""" + self._values.append(value) + self._refresh() + + def clear(self) -> None: + self._values.clear() + if self.is_mounted: + self.query_one("#spark-line", Static).update("") + + def _refresh(self) -> None: + if not self.is_mounted or not self._values: + return + + vals = list(self._values) + mn = min(vals) + mx = max(vals) + rng = mx - mn if mx != mn else 1.0 + + chars = [] + for v in vals: + idx = int((v - mn) / rng * (len(_SPARKS) - 1)) + idx = max(0, min(len(_SPARKS) - 1, idx)) + chars.append(_SPARKS[idx]) + + spark_str = "".join(chars) + line = self.query_one("#spark-line", Static) + line.update(f"[{self._color}]{spark_str}[/] [{mn:.1f} .. {mx:.1f}]") diff --git a/tui/src/skywalker_tui/widgets/spectrum_plot.py b/tui/src/skywalker_tui/widgets/spectrum_plot.py index 647f372..c5d495f 100644 --- a/tui/src/skywalker_tui/widgets/spectrum_plot.py +++ b/tui/src/skywalker_tui/widgets/spectrum_plot.py @@ -1,155 +1,155 @@ -"""Terminal-native spectrum plot using Unicode block characters and Rich markup. - -Renders a horizontal bar chart where each frequency bin gets a colored bar -proportional to its power level. The color gradient goes from cold (blue) -to hot (red), same concept as the CLI tool's WATERFALL_COLORS but using -Rich style strings instead of raw ANSI escapes. -""" - -import sys -import os - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "tools")) - -from textual.widget import Widget -from textual.widgets import Static -from textual.app import ComposeResult -from textual.containers import VerticalScroll - -from skywalker_lib import detect_peaks, if_to_rf - -# Sub-block characters for fractional bar width -_BARS = " ▏▎▍▌▋▊▉█" - -# Power-to-color gradient (16 steps: blue → cyan → green → yellow → red) -_POWER_COLORS = [ - "#1a237e", # dark blue (weakest) - "#1565c0", - "#0277bd", - "#00838f", - "#00897b", # teal - "#2e7d32", # green - "#558b2f", - "#9e9d24", - "#f9a825", # yellow - "#ff8f00", - "#ef6c00", # orange - "#e65100", - "#d84315", - "#c62828", # red - "#b71c1c", - "#880e0e", # dark red (strongest) -] - - -def _power_to_color(power_db: float, floor: float, ceiling: float) -> str: - """Map a power value to a color from the gradient.""" - if ceiling == floor: - return _POWER_COLORS[len(_POWER_COLORS) // 2] - ratio = (power_db - floor) / (ceiling - floor) - ratio = max(0.0, min(1.0, ratio)) - idx = int(ratio * (len(_POWER_COLORS) - 1)) - return _POWER_COLORS[idx] - - -class SpectrumPlot(Widget): - """Bar-chart spectrum display rendered with Unicode blocks and Rich styles.""" - - DEFAULT_CSS = """ - SpectrumPlot { - height: 1fr; - min-height: 12; - background: #0a0a12; - padding: 0 1; - } - SpectrumPlot #spectrum-title { - height: 1; - color: #00d4aa; - text-style: bold; - } - SpectrumPlot #spectrum-body { - height: 1fr; - } - """ - - def __init__(self, title: str = "Spectrum", bar_width: int = 40, - lnb_lo: float = 0.0, **kwargs): - super().__init__(**kwargs) - self._title = title - self._bar_width = bar_width - self._lnb_lo = lnb_lo - self._freqs: list[float] = [] - self._powers: list[float] = [] - self._results: list[dict] = [] - - def compose(self) -> ComposeResult: - yield Static(f"[#00d4aa bold]{self._title}[/]", id="spectrum-title") - yield VerticalScroll(Static("", id="spectrum-lines"), id="spectrum-body") - - def update_data(self, freqs: list[float], powers: list[float], - results: list[dict] | None = None, lnb_lo: float | None = None): - """Update with new sweep data and redraw.""" - self._freqs = freqs - self._powers = powers - self._results = results or [{} for _ in freqs] - if lnb_lo is not None: - self._lnb_lo = lnb_lo - self._refresh() - - def _refresh(self) -> None: - if not self.is_mounted or not self._freqs: - return - - p_min = min(self._powers) - p_max = max(self._powers) - p_range = p_max - p_min if p_max != p_min else 1.0 - - # Detect peaks for markers - peaks_set = set() - peaks = detect_peaks(self._freqs, self._powers, threshold_db=3.0) - for _f, _p, idx in peaks: - peaks_set.add(idx) - - lines = [] - for i, (f, p) in enumerate(zip(self._freqs, self._powers)): - # Frequency label (RF or IF) - if self._lnb_lo > 0: - label = f"{if_to_rf(f, self._lnb_lo):7.0f}" - else: - label = f"{f:7.1f}" - - # Bar - ratio = max(0.0, min(1.0, (p - p_min) / p_range)) - full = int(ratio * self._bar_width) - remainder = (ratio * self._bar_width) - full - partial = int(remainder * (len(_BARS) - 1)) - color = _power_to_color(p, p_min, p_max) - - bar = "█" * full - if full < self._bar_width: - bar += _BARS[partial] - bar += " " * (self._bar_width - full - 1) - - locked = self._results[i].get("locked", False) if i < len(self._results) else False - lock_mark = " [bold #00e060]*[/]" if locked else "" - peak_mark = " [bold #f44336]^[/]" if i in peaks_set else "" - - lines.append( - f"[#506878]{label}[/] [{color}]{bar}[/] [#7090a8]{p:6.1f}[/]{lock_mark}{peak_mark}" - ) - - # Peak summary at bottom - if peaks: - lines.append("") - lines.append(f"[#00d4aa bold]Peaks ({len(peaks)}):[/]") - for freq, pwr, idx in peaks: - if self._lnb_lo > 0: - fl = f"{if_to_rf(freq, self._lnb_lo):.0f} MHz RF" - else: - fl = f"{freq:.1f} MHz" - locked = self._results[idx].get("locked", False) if idx < len(self._results) else False - lock_s = " [bold #00e060]LOCKED[/]" if locked else "" - lines.append(f" [#c8d0d8]{fl} {pwr:.1f} dB{lock_s}[/]") - - body = self.query_one("#spectrum-lines", Static) - body.update("\n".join(lines)) +"""Terminal-native spectrum plot using Unicode block characters and Rich markup. + +Renders a horizontal bar chart where each frequency bin gets a colored bar +proportional to its power level. The color gradient goes from cold (blue) +to hot (red), same concept as the CLI tool's WATERFALL_COLORS but using +Rich style strings instead of raw ANSI escapes. +""" + +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "tools")) + +from textual.widget import Widget +from textual.widgets import Static +from textual.app import ComposeResult +from textual.containers import VerticalScroll + +from skywalker_lib import detect_peaks, if_to_rf + +# Sub-block characters for fractional bar width +_BARS = " ▏▎▍▌▋▊▉█" + +# Power-to-color gradient (16 steps: blue → cyan → green → yellow → red) +_POWER_COLORS = [ + "#1a237e", # dark blue (weakest) + "#1565c0", + "#0277bd", + "#00838f", + "#00897b", # teal + "#2e7d32", # green + "#558b2f", + "#9e9d24", + "#f9a825", # yellow + "#ff8f00", + "#ef6c00", # orange + "#e65100", + "#d84315", + "#c62828", # red + "#b71c1c", + "#880e0e", # dark red (strongest) +] + + +def _power_to_color(power_db: float, floor: float, ceiling: float) -> str: + """Map a power value to a color from the gradient.""" + if ceiling == floor: + return _POWER_COLORS[len(_POWER_COLORS) // 2] + ratio = (power_db - floor) / (ceiling - floor) + ratio = max(0.0, min(1.0, ratio)) + idx = int(ratio * (len(_POWER_COLORS) - 1)) + return _POWER_COLORS[idx] + + +class SpectrumPlot(Widget): + """Bar-chart spectrum display rendered with Unicode blocks and Rich styles.""" + + DEFAULT_CSS = """ + SpectrumPlot { + height: 1fr; + min-height: 12; + background: #0a0a12; + padding: 0 1; + } + SpectrumPlot #spectrum-title { + height: 1; + color: #00d4aa; + text-style: bold; + } + SpectrumPlot #spectrum-body { + height: 1fr; + } + """ + + def __init__(self, title: str = "Spectrum", bar_width: int = 40, + lnb_lo: float = 0.0, **kwargs): + super().__init__(**kwargs) + self._title = title + self._bar_width = bar_width + self._lnb_lo = lnb_lo + self._freqs: list[float] = [] + self._powers: list[float] = [] + self._results: list[dict] = [] + + def compose(self) -> ComposeResult: + yield Static(f"[#00d4aa bold]{self._title}[/]", id="spectrum-title") + yield VerticalScroll(Static("", id="spectrum-lines"), id="spectrum-body") + + def update_data(self, freqs: list[float], powers: list[float], + results: list[dict] | None = None, lnb_lo: float | None = None): + """Update with new sweep data and redraw.""" + self._freqs = freqs + self._powers = powers + self._results = results or [{} for _ in freqs] + if lnb_lo is not None: + self._lnb_lo = lnb_lo + self._refresh() + + def _refresh(self) -> None: + if not self.is_mounted or not self._freqs: + return + + p_min = min(self._powers) + p_max = max(self._powers) + p_range = p_max - p_min if p_max != p_min else 1.0 + + # Detect peaks for markers + peaks_set = set() + peaks = detect_peaks(self._freqs, self._powers, threshold_db=3.0) + for _f, _p, idx in peaks: + peaks_set.add(idx) + + lines = [] + for i, (f, p) in enumerate(zip(self._freqs, self._powers)): + # Frequency label (RF or IF) + if self._lnb_lo > 0: + label = f"{if_to_rf(f, self._lnb_lo):7.0f}" + else: + label = f"{f:7.1f}" + + # Bar + ratio = max(0.0, min(1.0, (p - p_min) / p_range)) + full = int(ratio * self._bar_width) + remainder = (ratio * self._bar_width) - full + partial = int(remainder * (len(_BARS) - 1)) + color = _power_to_color(p, p_min, p_max) + + bar = "█" * full + if full < self._bar_width: + bar += _BARS[partial] + bar += " " * (self._bar_width - full - 1) + + locked = self._results[i].get("locked", False) if i < len(self._results) else False + lock_mark = " [bold #00e060]*[/]" if locked else "" + peak_mark = " [bold #f44336]^[/]" if i in peaks_set else "" + + lines.append( + f"[#506878]{label}[/] [{color}]{bar}[/] [#7090a8]{p:6.1f}[/]{lock_mark}{peak_mark}" + ) + + # Peak summary at bottom + if peaks: + lines.append("") + lines.append(f"[#00d4aa bold]Peaks ({len(peaks)}):[/]") + for freq, pwr, idx in peaks: + if self._lnb_lo > 0: + fl = f"{if_to_rf(freq, self._lnb_lo):.0f} MHz RF" + else: + fl = f"{freq:.1f} MHz" + locked = self._results[idx].get("locked", False) if idx < len(self._results) else False + lock_s = " [bold #00e060]LOCKED[/]" if locked else "" + lines.append(f" [#c8d0d8]{fl} {pwr:.1f} dB{lock_s}[/]") + + body = self.query_one("#spectrum-lines", Static) + body.update("\n".join(lines)) diff --git a/tui/src/skywalker_tui/widgets/status_bar.py b/tui/src/skywalker_tui/widgets/status_bar.py index edc7c9d..0d50d90 100644 --- a/tui/src/skywalker_tui/widgets/status_bar.py +++ b/tui/src/skywalker_tui/widgets/status_bar.py @@ -1,65 +1,65 @@ -"""Device status bar — connection state, firmware version, config bits.""" - -from textual.app import ComposeResult -from textual.widget import Widget -from textual.widgets import Label - -from skywalker_lib import format_config_bits - - -class DeviceStatusBar(Widget): - """Bottom status bar showing device connection and configuration.""" - - DEFAULT_CSS = """ - DeviceStatusBar { - height: 3; - background: #0e1420; - border-top: solid #1a2a3a; - padding: 0 1; - dock: bottom; - layout: horizontal; - } - DeviceStatusBar Label { - width: auto; - margin: 1 2 0 0; - } - """ - - def __init__(self, bridge=None): - super().__init__(id="device-status") - self._bridge = bridge - - def compose(self) -> ComposeResult: - yield Label("", id="status-conn") - yield Label("", id="status-fw") - yield Label("", id="status-config") - - def update_status(self, bridge=None): - if bridge is not None: - self._bridge = bridge - if self._bridge is None: - return - - conn_label = self.query_one("#status-conn", Label) - fw_label = self.query_one("#status-fw", Label) - config_label = self.query_one("#status-config", Label) - - if self._bridge.is_demo: - conn_label.update("[bold #e8a020]DEMO[/]") - else: - conn_label.update("[bold #00d4aa]CONNECTED[/]") - - try: - fw = self._bridge.get_fw_version() - fw_label.update(f"[#506878]FW:[/] [#c8d0d8]{fw['version']}[/]") - except Exception: - fw_label.update("[#506878]FW:[/] [#e04040]error[/]") - - try: - config = self._bridge.get_config() - bits = format_config_bits(config) - active = [name for name, is_set in bits if is_set] - config_str = " | ".join(active) if active else "idle" - config_label.update(f"[#506878]Config:[/] [#7090a8]{config_str}[/]") - except Exception: - config_label.update("[#506878]Config:[/] [#e04040]error[/]") +"""Device status bar — connection state, firmware version, config bits.""" + +from textual.app import ComposeResult +from textual.widget import Widget +from textual.widgets import Label + +from skywalker_lib import format_config_bits + + +class DeviceStatusBar(Widget): + """Bottom status bar showing device connection and configuration.""" + + DEFAULT_CSS = """ + DeviceStatusBar { + height: 3; + background: #0e1420; + border-top: solid #1a2a3a; + padding: 0 1; + dock: bottom; + layout: horizontal; + } + DeviceStatusBar Label { + width: auto; + margin: 1 2 0 0; + } + """ + + def __init__(self, bridge=None): + super().__init__(id="device-status") + self._bridge = bridge + + def compose(self) -> ComposeResult: + yield Label("", id="status-conn") + yield Label("", id="status-fw") + yield Label("", id="status-config") + + def update_status(self, bridge=None): + if bridge is not None: + self._bridge = bridge + if self._bridge is None: + return + + conn_label = self.query_one("#status-conn", Label) + fw_label = self.query_one("#status-fw", Label) + config_label = self.query_one("#status-config", Label) + + if self._bridge.is_demo: + conn_label.update("[bold #e8a020]DEMO[/]") + else: + conn_label.update("[bold #00d4aa]CONNECTED[/]") + + try: + fw = self._bridge.get_fw_version() + fw_label.update(f"[#506878]FW:[/] [#c8d0d8]{fw['version']}[/]") + except Exception: + fw_label.update("[#506878]FW:[/] [#e04040]error[/]") + + try: + config = self._bridge.get_config() + bits = format_config_bits(config) + active = [name for name, is_set in bits if is_set] + config_str = " | ".join(active) if active else "idle" + config_label.update(f"[#506878]Config:[/] [#7090a8]{config_str}[/]") + except Exception: + config_label.update("[#506878]Config:[/] [#e04040]error[/]") diff --git a/tui/src/skywalker_tui/widgets/waterfall.py b/tui/src/skywalker_tui/widgets/waterfall.py index 49f8787..688a525 100644 --- a/tui/src/skywalker_tui/widgets/waterfall.py +++ b/tui/src/skywalker_tui/widgets/waterfall.py @@ -1,98 +1,98 @@ -"""Rolling waterfall display — each row is one sweep, color = power level. - -The waterfall auto-scrolls: new sweeps appear at the top, older rows shift down. -Uses Rich markup with the same 16-color power gradient as the spectrum plot. -""" - -from collections import deque -from datetime import datetime - -from textual.widget import Widget -from textual.widgets import Static -from textual.app import ComposeResult -from textual.containers import VerticalScroll - - -# Same gradient as spectrum_plot -_WATERFALL_COLORS = [ - "#1a237e", "#1565c0", "#0277bd", "#00838f", - "#00897b", "#2e7d32", "#558b2f", "#9e9d24", - "#f9a825", "#ff8f00", "#ef6c00", "#e65100", - "#d84315", "#c62828", "#b71c1c", "#880e0e", -] - - -class WaterfallDisplay(Widget): - """Scrolling waterfall of spectrum sweeps.""" - - DEFAULT_CSS = """ - WaterfallDisplay { - height: 1fr; - min-height: 8; - background: #0a0a12; - padding: 0 1; - } - WaterfallDisplay #waterfall-title { - height: 1; - color: #00d4aa; - text-style: bold; - } - WaterfallDisplay #waterfall-body { - height: 1fr; - } - """ - - def __init__(self, title: str = "Waterfall", max_rows: int = 50, **kwargs): - super().__init__(**kwargs) - self._title = title - self._max_rows = max_rows - self._rows: deque[tuple[str, list[float]]] = deque(maxlen=max_rows) - self._global_min: float = -40.0 - self._global_max: float = 0.0 - - def compose(self) -> ComposeResult: - yield Static(f"[#00d4aa bold]{self._title}[/]", id="waterfall-title") - yield VerticalScroll(Static("", id="waterfall-lines"), id="waterfall-body") - - def add_sweep(self, powers: list[float]) -> None: - """Add a new sweep row and refresh.""" - ts = datetime.now().strftime("%H:%M:%S") - self._rows.appendleft((ts, list(powers))) - - # Update global range for consistent coloring - if powers: - self._global_min = min(self._global_min, min(powers)) - self._global_max = max(self._global_max, max(powers)) - - self._refresh() - - def clear(self) -> None: - self._rows.clear() - self._global_min = -40.0 - self._global_max = 0.0 - if self.is_mounted: - self.query_one("#waterfall-lines", Static).update("") - - def _refresh(self) -> None: - if not self.is_mounted or not self._rows: - return - - rng = self._global_max - self._global_min - if rng == 0: - rng = 1.0 - - lines = [] - for ts, powers in self._rows: - chars = [] - for p in powers: - ratio = (p - self._global_min) / rng - ratio = max(0.0, min(1.0, ratio)) - idx = int(ratio * (len(_WATERFALL_COLORS) - 1)) - color = _WATERFALL_COLORS[idx] - chars.append(f"[{color}]█[/]") - - line = "".join(chars) - lines.append(f"[#506878]{ts}[/] {line}") - - body = self.query_one("#waterfall-lines", Static) - body.update("\n".join(lines)) +"""Rolling waterfall display — each row is one sweep, color = power level. + +The waterfall auto-scrolls: new sweeps appear at the top, older rows shift down. +Uses Rich markup with the same 16-color power gradient as the spectrum plot. +""" + +from collections import deque +from datetime import datetime + +from textual.widget import Widget +from textual.widgets import Static +from textual.app import ComposeResult +from textual.containers import VerticalScroll + + +# Same gradient as spectrum_plot +_WATERFALL_COLORS = [ + "#1a237e", "#1565c0", "#0277bd", "#00838f", + "#00897b", "#2e7d32", "#558b2f", "#9e9d24", + "#f9a825", "#ff8f00", "#ef6c00", "#e65100", + "#d84315", "#c62828", "#b71c1c", "#880e0e", +] + + +class WaterfallDisplay(Widget): + """Scrolling waterfall of spectrum sweeps.""" + + DEFAULT_CSS = """ + WaterfallDisplay { + height: 1fr; + min-height: 8; + background: #0a0a12; + padding: 0 1; + } + WaterfallDisplay #waterfall-title { + height: 1; + color: #00d4aa; + text-style: bold; + } + WaterfallDisplay #waterfall-body { + height: 1fr; + } + """ + + def __init__(self, title: str = "Waterfall", max_rows: int = 50, **kwargs): + super().__init__(**kwargs) + self._title = title + self._max_rows = max_rows + self._rows: deque[tuple[str, list[float]]] = deque(maxlen=max_rows) + self._global_min: float = -40.0 + self._global_max: float = 0.0 + + def compose(self) -> ComposeResult: + yield Static(f"[#00d4aa bold]{self._title}[/]", id="waterfall-title") + yield VerticalScroll(Static("", id="waterfall-lines"), id="waterfall-body") + + def add_sweep(self, powers: list[float]) -> None: + """Add a new sweep row and refresh.""" + ts = datetime.now().strftime("%H:%M:%S") + self._rows.appendleft((ts, list(powers))) + + # Update global range for consistent coloring + if powers: + self._global_min = min(self._global_min, min(powers)) + self._global_max = max(self._global_max, max(powers)) + + self._refresh() + + def clear(self) -> None: + self._rows.clear() + self._global_min = -40.0 + self._global_max = 0.0 + if self.is_mounted: + self.query_one("#waterfall-lines", Static).update("") + + def _refresh(self) -> None: + if not self.is_mounted or not self._rows: + return + + rng = self._global_max - self._global_min + if rng == 0: + rng = 1.0 + + lines = [] + for ts, powers in self._rows: + chars = [] + for p in powers: + ratio = (p - self._global_min) / rng + ratio = max(0.0, min(1.0, ratio)) + idx = int(ratio * (len(_WATERFALL_COLORS) - 1)) + color = _WATERFALL_COLORS[idx] + chars.append(f"[{color}]█[/]") + + line = "".join(chars) + lines.append(f"[#506878]{ts}[/] {line}") + + body = self.query_one("#waterfall-lines", Static) + body.update("\n".join(lines)) diff --git a/tui/tests/test_radar_scope.py b/tui/tests/test_radar_scope.py index 5a5be1b..495f03b 100644 --- a/tui/tests/test_radar_scope.py +++ b/tui/tests/test_radar_scope.py @@ -1,192 +1,192 @@ -"""Tests for the radar scope widget — LUT geometry and pixel intensity.""" - -from skywalker_tui.widgets.radar_scope import RadarScope - - -class TestRadarGeometry: - """LUT pre-computation and geometry tests.""" - - def _make_scope(self, width=40, height=20): - scope = RadarScope() - scope._recompute_geometry(width, height) - return scope - - def test_lut_dimensions(self): - scope = self._make_scope(40, 20) - # pixel rows = terminal rows * 2 - assert len(scope._dist_lut) == 40 - assert len(scope._angle_lut) == 40 - assert len(scope._dx_lut) == 40 - assert len(scope._dy_lut) == 40 - # each row has `width` columns - assert len(scope._dist_lut[0]) == 40 - assert len(scope._angle_lut[0]) == 40 - - def test_center_pixel_distance_near_zero(self): - scope = self._make_scope(40, 20) - # center = (20, 20) in pixel coords - cx_int = int(scope._cx) - cy_int = int(scope._cy) - dist = scope._dist_lut[cy_int][cx_int] - assert dist < 1.0, f"Center pixel distance should be ~0, got {dist}" - - def test_corner_pixel_outside_circle(self): - scope = self._make_scope(40, 20) - dist = scope._dist_lut[0][0] - assert dist > scope._radius, "Corner should be outside the radar circle" - - def test_radius_fits_within_bounds(self): - scope = self._make_scope(60, 30) - # radius should be ≤ half the smaller dimension - assert scope._radius <= 30 # half of width - assert scope._radius <= 29 # half of pixel height (60) - 1 - - def test_recompute_updates_on_resize(self): - scope = self._make_scope(40, 20) - r1 = scope._radius - scope._recompute_geometry(80, 40) - r2 = scope._radius - assert r2 > r1, "Larger terminal should give larger radius" - - -class TestPixelIntensity: - """Tests for _pixel_intensity with pre-computed LUT values.""" - - def _make_scope(self, width=40, height=20): - scope = RadarScope() - scope._recompute_geometry(width, height) - return scope - - def test_outside_circle_returns_zero(self): - scope = self._make_scope() - samples = [0.0] * 360 - result = scope._pixel_intensity( - dist=scope._radius + 5, - sample_idx=0, dx=20.0, dy=0.0, - radius=scope._radius, samples=samples, - angle_idx=0, locked=False, - ) - assert result == 0 - - def test_center_on_crosshair(self): - """At exact center, crosshair check (abs(dx) < 0.7) fires before center dot.""" - scope = self._make_scope() - samples = [0.0] * 360 - result = scope._pixel_intensity( - dist=0.5, sample_idx=0, dx=0.0, dy=0.0, - radius=scope._radius, samples=samples, - angle_idx=0, locked=False, - ) - assert result == 2 # crosshair overlaps center - - def test_center_off_crosshair(self): - """Near center but off crosshair axes → center dot (1).""" - scope = self._make_scope() - samples = [0.0] * 360 - result = scope._pixel_intensity( - dist=0.8, sample_idx=45, dx=0.8, dy=0.8, - radius=scope._radius, samples=samples, - angle_idx=0, locked=False, - ) - assert result == 1 # center dot, off crosshair axes - - def test_lock_ring_at_edge(self): - scope = self._make_scope() - samples = [0.0] * 360 - result = scope._pixel_intensity( - dist=scope._radius, sample_idx=0, dx=scope._radius, dy=0.0, - radius=scope._radius, samples=samples, - angle_idx=0, locked=True, - ) - assert result == 7 # peak phosphor for lock ring - - def test_no_lock_ring_when_unlocked(self): - scope = self._make_scope() - samples = [0.0] * 360 - result = scope._pixel_intensity( - dist=scope._radius, sample_idx=0, dx=scope._radius, dy=0.0, - radius=scope._radius, samples=samples, - angle_idx=0, locked=False, - ) - # Should be boundary ring (2), not lock ring (7) - assert result != 7 - - def test_crosshair_on_vertical(self): - scope = self._make_scope() - samples = [0.0] * 360 - # Point on the vertical crosshair (dx near 0, inside circle) - result = scope._pixel_intensity( - dist=scope._radius * 0.5, - sample_idx=90, dx=0.0, dy=scope._radius * 0.5, - radius=scope._radius, samples=samples, - angle_idx=0, locked=False, - ) - assert result == 2 # crosshair intensity - - def test_strong_signal_blip_near_sweep(self): - scope = self._make_scope() - samples = [0.0] * 360 - # strength 0.47 → blip at 0.47 * r * 0.85 = 0.40*r - # Clear of range rings at 0.25r, 0.50r, 0.75r (nearest gap: 0.15r ≈ 2.85px) - samples[180] = 0.47 - signal_dist = 0.47 * scope._radius * 0.85 - # Verify we're not on a range ring - r = scope._radius - for ring_r in (0.25, 0.5, 0.75): - assert abs(signal_dist - r * ring_r) > 0.7, ( - f"Signal blip at {signal_dist:.2f} overlaps range ring at {r * ring_r:.2f}" - ) - result = scope._pixel_intensity( - dist=signal_dist, - sample_idx=180, dx=5.0, dy=5.0, - radius=scope._radius, samples=samples, - angle_idx=180, locked=False, - ) - # Newest sample at sweep position should be visible - assert result >= 3 - - def test_old_signal_decays(self): - scope = self._make_scope() - samples = [0.0] * 360 - samples[0] = 0.8 - signal_dist = 0.8 * scope._radius * 0.85 - # Sample at index 0, sweep beam at index 300 → age = 300 - result = scope._pixel_intensity( - dist=signal_dist, - sample_idx=0, dx=5.0, dy=5.0, - radius=scope._radius, samples=samples, - angle_idx=300, locked=False, - ) - # Old sample should be dim or invisible - assert result <= 2 - - -class TestRadarPush: - """Tests for the push() method and normalization.""" - - def test_push_normalizes_to_range(self): - scope = RadarScope(max_samples=10) - scope.push(8.0, max_snr=16.0) - assert scope._samples[-1] == 0.5 - - def test_push_clamps_above_max(self): - scope = RadarScope(max_samples=10) - scope.push(20.0, max_snr=16.0) - assert scope._samples[-1] == 1.0 - - def test_push_clamps_below_zero(self): - scope = RadarScope(max_samples=10) - scope.push(-5.0, max_snr=16.0) - assert scope._samples[-1] == 0.0 - - def test_angle_index_wraps(self): - scope = RadarScope(max_samples=4) - for _ in range(5): - scope.push(1.0) - assert scope._angle_idx == 1 # 5 % 4 = 1 - - def test_set_locked(self): - scope = RadarScope() - assert scope._locked is False - scope.set_locked(True) - assert scope._locked is True +"""Tests for the radar scope widget — LUT geometry and pixel intensity.""" + +from skywalker_tui.widgets.radar_scope import RadarScope + + +class TestRadarGeometry: + """LUT pre-computation and geometry tests.""" + + def _make_scope(self, width=40, height=20): + scope = RadarScope() + scope._recompute_geometry(width, height) + return scope + + def test_lut_dimensions(self): + scope = self._make_scope(40, 20) + # pixel rows = terminal rows * 2 + assert len(scope._dist_lut) == 40 + assert len(scope._angle_lut) == 40 + assert len(scope._dx_lut) == 40 + assert len(scope._dy_lut) == 40 + # each row has `width` columns + assert len(scope._dist_lut[0]) == 40 + assert len(scope._angle_lut[0]) == 40 + + def test_center_pixel_distance_near_zero(self): + scope = self._make_scope(40, 20) + # center = (20, 20) in pixel coords + cx_int = int(scope._cx) + cy_int = int(scope._cy) + dist = scope._dist_lut[cy_int][cx_int] + assert dist < 1.0, f"Center pixel distance should be ~0, got {dist}" + + def test_corner_pixel_outside_circle(self): + scope = self._make_scope(40, 20) + dist = scope._dist_lut[0][0] + assert dist > scope._radius, "Corner should be outside the radar circle" + + def test_radius_fits_within_bounds(self): + scope = self._make_scope(60, 30) + # radius should be ≤ half the smaller dimension + assert scope._radius <= 30 # half of width + assert scope._radius <= 29 # half of pixel height (60) - 1 + + def test_recompute_updates_on_resize(self): + scope = self._make_scope(40, 20) + r1 = scope._radius + scope._recompute_geometry(80, 40) + r2 = scope._radius + assert r2 > r1, "Larger terminal should give larger radius" + + +class TestPixelIntensity: + """Tests for _pixel_intensity with pre-computed LUT values.""" + + def _make_scope(self, width=40, height=20): + scope = RadarScope() + scope._recompute_geometry(width, height) + return scope + + def test_outside_circle_returns_zero(self): + scope = self._make_scope() + samples = [0.0] * 360 + result = scope._pixel_intensity( + dist=scope._radius + 5, + sample_idx=0, dx=20.0, dy=0.0, + radius=scope._radius, samples=samples, + angle_idx=0, locked=False, + ) + assert result == 0 + + def test_center_on_crosshair(self): + """At exact center, crosshair check (abs(dx) < 0.7) fires before center dot.""" + scope = self._make_scope() + samples = [0.0] * 360 + result = scope._pixel_intensity( + dist=0.5, sample_idx=0, dx=0.0, dy=0.0, + radius=scope._radius, samples=samples, + angle_idx=0, locked=False, + ) + assert result == 2 # crosshair overlaps center + + def test_center_off_crosshair(self): + """Near center but off crosshair axes → center dot (1).""" + scope = self._make_scope() + samples = [0.0] * 360 + result = scope._pixel_intensity( + dist=0.8, sample_idx=45, dx=0.8, dy=0.8, + radius=scope._radius, samples=samples, + angle_idx=0, locked=False, + ) + assert result == 1 # center dot, off crosshair axes + + def test_lock_ring_at_edge(self): + scope = self._make_scope() + samples = [0.0] * 360 + result = scope._pixel_intensity( + dist=scope._radius, sample_idx=0, dx=scope._radius, dy=0.0, + radius=scope._radius, samples=samples, + angle_idx=0, locked=True, + ) + assert result == 7 # peak phosphor for lock ring + + def test_no_lock_ring_when_unlocked(self): + scope = self._make_scope() + samples = [0.0] * 360 + result = scope._pixel_intensity( + dist=scope._radius, sample_idx=0, dx=scope._radius, dy=0.0, + radius=scope._radius, samples=samples, + angle_idx=0, locked=False, + ) + # Should be boundary ring (2), not lock ring (7) + assert result != 7 + + def test_crosshair_on_vertical(self): + scope = self._make_scope() + samples = [0.0] * 360 + # Point on the vertical crosshair (dx near 0, inside circle) + result = scope._pixel_intensity( + dist=scope._radius * 0.5, + sample_idx=90, dx=0.0, dy=scope._radius * 0.5, + radius=scope._radius, samples=samples, + angle_idx=0, locked=False, + ) + assert result == 2 # crosshair intensity + + def test_strong_signal_blip_near_sweep(self): + scope = self._make_scope() + samples = [0.0] * 360 + # strength 0.47 → blip at 0.47 * r * 0.85 = 0.40*r + # Clear of range rings at 0.25r, 0.50r, 0.75r (nearest gap: 0.15r ≈ 2.85px) + samples[180] = 0.47 + signal_dist = 0.47 * scope._radius * 0.85 + # Verify we're not on a range ring + r = scope._radius + for ring_r in (0.25, 0.5, 0.75): + assert abs(signal_dist - r * ring_r) > 0.7, ( + f"Signal blip at {signal_dist:.2f} overlaps range ring at {r * ring_r:.2f}" + ) + result = scope._pixel_intensity( + dist=signal_dist, + sample_idx=180, dx=5.0, dy=5.0, + radius=scope._radius, samples=samples, + angle_idx=180, locked=False, + ) + # Newest sample at sweep position should be visible + assert result >= 3 + + def test_old_signal_decays(self): + scope = self._make_scope() + samples = [0.0] * 360 + samples[0] = 0.8 + signal_dist = 0.8 * scope._radius * 0.85 + # Sample at index 0, sweep beam at index 300 → age = 300 + result = scope._pixel_intensity( + dist=signal_dist, + sample_idx=0, dx=5.0, dy=5.0, + radius=scope._radius, samples=samples, + angle_idx=300, locked=False, + ) + # Old sample should be dim or invisible + assert result <= 2 + + +class TestRadarPush: + """Tests for the push() method and normalization.""" + + def test_push_normalizes_to_range(self): + scope = RadarScope(max_samples=10) + scope.push(8.0, max_snr=16.0) + assert scope._samples[-1] == 0.5 + + def test_push_clamps_above_max(self): + scope = RadarScope(max_samples=10) + scope.push(20.0, max_snr=16.0) + assert scope._samples[-1] == 1.0 + + def test_push_clamps_below_zero(self): + scope = RadarScope(max_samples=10) + scope.push(-5.0, max_snr=16.0) + assert scope._samples[-1] == 0.0 + + def test_angle_index_wraps(self): + scope = RadarScope(max_samples=4) + for _ in range(5): + scope.push(1.0) + assert scope._angle_idx == 1 # 5 % 4 = 1 + + def test_set_locked(self): + scope = RadarScope() + assert scope._locked is False + scope.set_locked(True) + assert scope._locked is True diff --git a/tui/tests/test_splash.py b/tui/tests/test_splash.py index 9cfe4c0..88c8b7f 100644 --- a/tui/tests/test_splash.py +++ b/tui/tests/test_splash.py @@ -1,48 +1,48 @@ -"""Tests for the splash screen art catalog and selection.""" - -from skywalker_tui.screens.splash import SplashScreen, ASSETS_DIR, ART_CATALOG - - -class TestSplashArtCatalog: - """Verify bundled pre-baked .ans art assets exist and catalog is consistent.""" - - def test_assets_dir_exists(self): - assert ASSETS_DIR.is_dir(), f"Assets dir missing: {ASSETS_DIR}" - - def test_all_catalog_entries_have_ans_files(self): - missing = [] - for stem, artist, title in ART_CATALOG: - path = ASSETS_DIR / f"{stem}.ans" - if not path.exists(): - missing.append(f"{stem}.ans") - assert not missing, f"Missing pre-baked .ans files: {missing}" - - def test_catalog_has_entries(self): - assert len(ART_CATALOG) >= 1 - - def test_ans_files_not_empty(self): - for stem, _, _ in ART_CATALOG: - path = ASSETS_DIR / f"{stem}.ans" - if path.exists(): - assert path.stat().st_size > 0, f"{stem}.ans is empty" - - def test_ans_files_contain_ansi_escapes(self): - """Pre-baked files should contain ANSI color escape sequences.""" - for stem, _, _ in ART_CATALOG: - path = ASSETS_DIR / f"{stem}.ans" - if path.exists(): - content = path.read_text() - assert "\033[" in content, f"{stem}.ans has no ANSI escapes" - assert "\u2580" in content, f"{stem}.ans has no half-block chars" - - -class TestSplashScreenInit: - """Test SplashScreen construction (no app needed).""" - - def test_selects_art_on_init(self): - screen = SplashScreen() - assert screen._ans_path is not None or len(ART_CATALOG) == 0 - if screen._ans_path: - assert screen._ans_path.exists() - assert screen._artist != "" - assert screen._title != "" +"""Tests for the splash screen art catalog and selection.""" + +from skywalker_tui.screens.splash import SplashScreen, ASSETS_DIR, ART_CATALOG + + +class TestSplashArtCatalog: + """Verify bundled pre-baked .ans art assets exist and catalog is consistent.""" + + def test_assets_dir_exists(self): + assert ASSETS_DIR.is_dir(), f"Assets dir missing: {ASSETS_DIR}" + + def test_all_catalog_entries_have_ans_files(self): + missing = [] + for stem, artist, title in ART_CATALOG: + path = ASSETS_DIR / f"{stem}.ans" + if not path.exists(): + missing.append(f"{stem}.ans") + assert not missing, f"Missing pre-baked .ans files: {missing}" + + def test_catalog_has_entries(self): + assert len(ART_CATALOG) >= 1 + + def test_ans_files_not_empty(self): + for stem, _, _ in ART_CATALOG: + path = ASSETS_DIR / f"{stem}.ans" + if path.exists(): + assert path.stat().st_size > 0, f"{stem}.ans is empty" + + def test_ans_files_contain_ansi_escapes(self): + """Pre-baked files should contain ANSI color escape sequences.""" + for stem, _, _ in ART_CATALOG: + path = ASSETS_DIR / f"{stem}.ans" + if path.exists(): + content = path.read_text() + assert "\033[" in content, f"{stem}.ans has no ANSI escapes" + assert "\u2580" in content, f"{stem}.ans has no half-block chars" + + +class TestSplashScreenInit: + """Test SplashScreen construction (no app needed).""" + + def test_selects_art_on_init(self): + screen = SplashScreen() + assert screen._ans_path is not None or len(ART_CATALOG) == 0 + if screen._ans_path: + assert screen._ans_path.exists() + assert screen._artist != "" + assert screen._title != "" diff --git a/tui/tests/test_telnet_stripper.py b/tui/tests/test_telnet_stripper.py index 94582f2..6f78d0e 100644 --- a/tui/tests/test_telnet_stripper.py +++ b/tui/tests/test_telnet_stripper.py @@ -1,108 +1,108 @@ -"""Tests for the stateful telnet IAC sequence stripper.""" - -from skywalker_tui.screens.starwars import _TelnetStripper - - -class TestTelnetStripper: - """Edge cases for IAC parsing across chunk boundaries.""" - - def test_plain_text_passthrough(self): - s = _TelnetStripper() - assert s.feed(b"hello world") == b"hello world" - - def test_strips_will_command(self): - # IAC WILL ECHO = FF FB 01 - s = _TelnetStripper() - assert s.feed(b"\xff\xfb\x01hello") == b"hello" - - def test_strips_wont_command(self): - s = _TelnetStripper() - assert s.feed(b"\xff\xfc\x03data") == b"data" - - def test_strips_do_command(self): - s = _TelnetStripper() - assert s.feed(b"\xff\xfd\x01data") == b"data" - - def test_strips_dont_command(self): - s = _TelnetStripper() - assert s.feed(b"\xff\xfe\x01data") == b"data" - - def test_escaped_0xff(self): - """Doubled 0xFF = literal 0xFF byte in content.""" - s = _TelnetStripper() - assert s.feed(b"\xff\xff") == b"\xff" - - def test_sub_negotiation(self): - # IAC SB 0x01 ... IAC SE = FF FA 01 xx xx FF F0 - s = _TelnetStripper() - data = b"before\xff\xfa\x01\x00\x00\xff\xf0after" - assert s.feed(data) == b"beforeafter" - - def test_split_iac_across_chunks(self): - """IAC command split: FF in chunk 1, FB 01 in chunk 2.""" - s = _TelnetStripper() - out1 = s.feed(b"hello\xff") - out2 = s.feed(b"\xfb\x01world") - assert out1 == b"hello" - assert out2 == b"world" - - def test_split_will_at_boundary(self): - """IAC WILL split: FF FB in chunk 1, option byte in chunk 2.""" - s = _TelnetStripper() - out1 = s.feed(b"aaa\xff\xfb") - out2 = s.feed(b"\x03bbb") - assert out1 == b"aaa" - assert out2 == b"bbb" - - def test_split_sub_negotiation(self): - """Sub-negotiation without closing IAC SE — buffers until next chunk.""" - s = _TelnetStripper() - out1 = s.feed(b"x\xff\xfa\x01\x00") - out2 = s.feed(b"\xff\xf0y") - assert out1 == b"x" - assert out2 == b"y" - - def test_multiple_commands_in_one_chunk(self): - s = _TelnetStripper() - data = b"\xff\xfb\x01\xff\xfc\x03text\xff\xfd\x01" - assert s.feed(data) == b"text" - - def test_empty_input(self): - s = _TelnetStripper() - assert s.feed(b"") == b"" - - def test_just_iac_byte(self): - """Single 0xFF byte — should buffer, waiting for next byte.""" - s = _TelnetStripper() - out1 = s.feed(b"\xff") - assert out1 == b"" - out2 = s.feed(b"\xfb\x01done") - assert out2 == b"done" - - def test_unknown_iac_command(self): - """Unknown command byte after IAC — stripped as 2-byte sequence.""" - s = _TelnetStripper() - assert s.feed(b"\xff\xf1text") == b"text" - - -class TestFrameParsing: - """Tests for the ESC[H frame boundary detection logic.""" - - def test_frame_split_on_cursor_home(self): - """ESC[H splits data into frames.""" - data = b"frame1\x1b[Hframe2\x1b[Hframe3" - frames = data.split(b"\x1b[H") - assert frames == [b"frame1", b"frame2", b"frame3"] - - def test_esc_j_in_frame(self): - """ESC[J (clear to end) can be stripped from frame data.""" - data = b"\x1b[Jsome text here" - clean = data.replace(b"\x1b[J", b"") - assert clean == b"some text here" - - def test_empty_frames_between_homes(self): - """Consecutive ESC[H produces empty frames (filtered in code).""" - data = b"\x1b[H\x1b[Htext" - frames = data.split(b"\x1b[H") - non_empty = [f for f in frames if f.strip()] - assert non_empty == [b"text"] +"""Tests for the stateful telnet IAC sequence stripper.""" + +from skywalker_tui.screens.starwars import _TelnetStripper + + +class TestTelnetStripper: + """Edge cases for IAC parsing across chunk boundaries.""" + + def test_plain_text_passthrough(self): + s = _TelnetStripper() + assert s.feed(b"hello world") == b"hello world" + + def test_strips_will_command(self): + # IAC WILL ECHO = FF FB 01 + s = _TelnetStripper() + assert s.feed(b"\xff\xfb\x01hello") == b"hello" + + def test_strips_wont_command(self): + s = _TelnetStripper() + assert s.feed(b"\xff\xfc\x03data") == b"data" + + def test_strips_do_command(self): + s = _TelnetStripper() + assert s.feed(b"\xff\xfd\x01data") == b"data" + + def test_strips_dont_command(self): + s = _TelnetStripper() + assert s.feed(b"\xff\xfe\x01data") == b"data" + + def test_escaped_0xff(self): + """Doubled 0xFF = literal 0xFF byte in content.""" + s = _TelnetStripper() + assert s.feed(b"\xff\xff") == b"\xff" + + def test_sub_negotiation(self): + # IAC SB 0x01 ... IAC SE = FF FA 01 xx xx FF F0 + s = _TelnetStripper() + data = b"before\xff\xfa\x01\x00\x00\xff\xf0after" + assert s.feed(data) == b"beforeafter" + + def test_split_iac_across_chunks(self): + """IAC command split: FF in chunk 1, FB 01 in chunk 2.""" + s = _TelnetStripper() + out1 = s.feed(b"hello\xff") + out2 = s.feed(b"\xfb\x01world") + assert out1 == b"hello" + assert out2 == b"world" + + def test_split_will_at_boundary(self): + """IAC WILL split: FF FB in chunk 1, option byte in chunk 2.""" + s = _TelnetStripper() + out1 = s.feed(b"aaa\xff\xfb") + out2 = s.feed(b"\x03bbb") + assert out1 == b"aaa" + assert out2 == b"bbb" + + def test_split_sub_negotiation(self): + """Sub-negotiation without closing IAC SE — buffers until next chunk.""" + s = _TelnetStripper() + out1 = s.feed(b"x\xff\xfa\x01\x00") + out2 = s.feed(b"\xff\xf0y") + assert out1 == b"x" + assert out2 == b"y" + + def test_multiple_commands_in_one_chunk(self): + s = _TelnetStripper() + data = b"\xff\xfb\x01\xff\xfc\x03text\xff\xfd\x01" + assert s.feed(data) == b"text" + + def test_empty_input(self): + s = _TelnetStripper() + assert s.feed(b"") == b"" + + def test_just_iac_byte(self): + """Single 0xFF byte — should buffer, waiting for next byte.""" + s = _TelnetStripper() + out1 = s.feed(b"\xff") + assert out1 == b"" + out2 = s.feed(b"\xfb\x01done") + assert out2 == b"done" + + def test_unknown_iac_command(self): + """Unknown command byte after IAC — stripped as 2-byte sequence.""" + s = _TelnetStripper() + assert s.feed(b"\xff\xf1text") == b"text" + + +class TestFrameParsing: + """Tests for the ESC[H frame boundary detection logic.""" + + def test_frame_split_on_cursor_home(self): + """ESC[H splits data into frames.""" + data = b"frame1\x1b[Hframe2\x1b[Hframe3" + frames = data.split(b"\x1b[H") + assert frames == [b"frame1", b"frame2", b"frame3"] + + def test_esc_j_in_frame(self): + """ESC[J (clear to end) can be stripped from frame data.""" + data = b"\x1b[Jsome text here" + clean = data.replace(b"\x1b[J", b"") + assert clean == b"some text here" + + def test_empty_frames_between_homes(self): + """Consecutive ESC[H produces empty frames (filtered in code).""" + data = b"\x1b[H\x1b[Htext" + frames = data.split(b"\x1b[H") + non_empty = [f for f in frames if f.strip()] + assert non_empty == [b"text"] diff --git a/tuning-protocol-analysis.md b/tuning-protocol-analysis.md index 82d27fa..a93c4bb 100644 --- a/tuning-protocol-analysis.md +++ b/tuning-protocol-analysis.md @@ -1,895 +1,895 @@ -# Genpix SkyWalker-1 TUNE_8PSK (0x86) Protocol Deep-Dive - -## Executive Summary - -This document presents a complete reverse engineering analysis of the TUNE_8PSK vendor command (0x86) as implemented in three Genpix SkyWalker-1 FX2 firmware versions. The analysis traces the full signal path from USB control transfer through EP0BUF parameter parsing, modulation-specific BCM4500 configuration, I2C register programming, and signal acquisition polling. Supporting commands (LNB voltage, 22 kHz tone, signal lock, signal strength) are fully documented. - -**Firmware versions analyzed:** -- v2.06 (Ghidra port 8193) -- 61 functions, earliest revision -- v2.13 FW1 (Ghidra port 8194) -- 88 functions, latest revision -- Rev.2 v2.10.4 (Ghidra port 8197) -- 107 functions, intermediate revision - -**Primary analysis target:** Rev.2 v2.10.4 (clearest decompilation, most granular function decomposition) - ---- - -## 1. TUNE_8PSK Command Handler (0x86) - -### 1.1 Vendor Dispatch Entry Points - -All three firmware versions use identical vendor command dispatch logic at CODE:0056. The dispatcher checks `bmRequestType` bit 6 (vendor request), validates `bRequest` is in range, and performs an indexed jump via `JMP @A+DPTR` at the jump table starting at CODE:0076. - -| Firmware | TUNE_8PSK Jump Target | Config Status Byte | Tune Function | -|----------|----------------------|-------------------|---------------| -| v2.06 | 0x012E | IRAM 0x6D | 0x0800 (via FUN_CODE_09a7) | -| v2.13 | 0x012E | IRAM 0x4F | 0x09DF | -| Rev.2 v2.10.4 | 0x0118 | IRAM 0x4E | 0x0800 | - -### 1.2 Handler Inline Code - -The TUNE_8PSK handler at the jump table target performs two operations: - -1. **Wait for EP0 data phase** -- calls the EP0 flush function to ensure the 10-byte command payload has arrived in EP0BUF -2. **Check device status** -- reads the config status byte and verifies bit 0 (bm8pskStarted) is set before proceeding -3. **Call tune function** -- if the device is started, calls the main tune function - -**Rev.2 v2.10.4 (0x0118):** -``` -CODE:0118 LCALL 0x2167 ; EP0 flush -- wait for EP0BUF data ready -CODE:011B MOV A, 0x4E ; Read config status byte (IRAM 0x4E) -CODE:011D JNB ACC.0, +3 ; Skip tune if device not started -CODE:0120 LCALL 0x0800 ; Call main tune function (FUN_CODE_0800) -``` - -**v2.06 (0x012E):** -``` -CODE:012E LCALL 0x23E0 ; EP0 flush function -CODE:0131 MOV A, 0x6D ; Config status byte (IRAM 0x6D) -CODE:0133 JNB ACC.0, +3 ; Skip if not started -CODE:0136 LCALL 0x0800 ; Main tune function -``` - -**v2.13 (0x012E):** -``` -CODE:012E LCALL 0x231E ; EP0 flush function -CODE:0131 MOV A, 0x4F ; Config status byte (IRAM 0x4F) -CODE:0133 JNB ACC.0, +3 ; Skip if not started -CODE:0136 LCALL 0x09DF ; Main tune function (different address) -``` - ---- - -## 2. EP0BUF Parameter Parsing - -### 2.1 10-Byte Command Format - -The host (Windows BDA driver or Linux dvb-usb-gp8psk) sends a 10-byte payload via USB control transfer OUT: - -``` -USB Setup Packet: - bmRequestType = 0x40 (Vendor, Host-to-Device) - bRequest = 0x86 (TUNE_8PSK) - wValue = 0x0000 - wIndex = 0x0000 - wLength = 10 - -EP0BUF Layout (0xE740-0xE749): - Offset XRAM Addr Content Encoding - ------ --------- ------------------- ------------------ - [0] 0xE740 Symbol Rate byte 0 Little-endian LSB - [1] 0xE741 Symbol Rate byte 1 - [2] 0xE742 Symbol Rate byte 2 - [3] 0xE743 Symbol Rate byte 3 Little-endian MSB - [4] 0xE744 Frequency byte 0 Little-endian LSB - [5] 0xE745 Frequency byte 1 - [6] 0xE746 Frequency byte 2 - [7] 0xE747 Frequency byte 3 Little-endian MSB - [8] 0xE748 Modulation Type 0-9 (see section 3) - [9] 0xE749 Inner FEC Rate See FEC tables -``` - -**Symbol Rate** is in samples per second (sps). The Windows driver converts from ksps: `ulTempSymbolRate = pDeviceParameter->ulSymbolRate * 1000` - -**Frequency** is in kHz, computed as `(CarrierFrequency - LO_Frequency) * FrequencyMultiplier`. Valid range: 800,000 - 2,250,000 kHz (after LNB downconversion: 950-2150 MHz IF). - -### 2.2 Firmware EP0BUF Read Sequence (Rev.2 v2.10.4 Disassembly) - -The tune function at 0x0800 begins with direct EP0BUF reads: - -``` -; Read modulation type and FEC rate -CODE:0802 MOV DPTR, #0xE748 ; EP0BUF[8] = modulation type -CODE:0805 MOVX A, @DPTR -CODE:0806 MOV 0x4D, A ; Store to IRAM 0x4D (DAT_INTMEM_4d) -CODE:0808 INC DPTR ; DPTR = 0xE749 -CODE:0809 MOVX A, @DPTR ; EP0BUF[9] = FEC rate -CODE:080A MOV 0x4F, A ; Store to IRAM 0x4F (DAT_INTMEM_4f) -``` - -**Frequency bytes** (EP0BUF[4-7]) are byte-reversed from little-endian to big-endian for BCM4500: - -``` -; Loop: i = 0..3 -; EP0BUF[4+i] -> XRAM[0xE0DE - i] (frequency, LE -> BE) -CODE:0816 MOV A, #0x44 ; 0x44 = EP0BUF offset for freq[0] (0xE740+4=0xE744) -CODE:0818 ADD A, 0x3B ; + loop index i -CODE:081A MOV DPL, A -CODE:081C CLR A -CODE:081D ADDC A, #0xE7 ; DPH = 0xE7 -CODE:0821 MOVX A, @DPTR ; Read EP0BUF[4+i] -CODE:0822 MOV R7, A ; Save byte - -; Compute destination: 0xE0D8 + (6 - i) = 0xE0DE - i -CODE:0827 MOV A, #0x06 -CODE:0829 SUBB A, R5 ; 6 - i -CODE:082F MOV A, #0xD8 ; Base = 0xE0D8 -CODE:0831 ADD A, R5 -CODE:0832 MOV DPL, A -CODE:0834 MOV A, #0xE0 -CODE:0837 MOV DPH, A -CODE:0839 MOV A, R7 -CODE:083A MOVX @DPTR, A ; Store byte-reversed -``` - -**Symbol rate bytes** (EP0BUF[0-3]) follow the same pattern: - -``` -; EP0BUF[i] -> XRAM[0xE0C7 + (7 - i)] = XRAM[0xE0CE - i] (symbol rate, LE -> BE) -CODE:083B MOV A, #0x40 ; 0x40 = EP0BUF offset for SR[0] (0xE740+0=0xE740) -CODE:083D ADD A, 0x3B ; + loop index i -CODE:0846 MOVX A, @DPTR ; Read EP0BUF[i] - -; Destination: 0xE0C7 + (7 - i) = 0xE0CE - i -CODE:084C MOV A, #0x07 -CODE:084E SUBB A, R5 ; 7 - i -CODE:0854 MOV A, #0xC7 ; Base = 0xE0C7 -CODE:0856 ADD A, R5 -CODE:085F MOVX @DPTR, A ; Store byte-reversed -``` - -### 2.3 XRAM Storage Map After Parsing - -``` -IRAM Variables: - 0x4D Modulation type (0-9) [from EP0BUF[8]] - 0x4F Inner FEC rate index [from EP0BUF[9]] - -XRAM Frequency (big-endian, 4 bytes): - 0xE0DB Frequency byte 3 (MSB) [from EP0BUF[7]] - 0xE0DC Frequency byte 2 [from EP0BUF[6]] - 0xE0DD Frequency byte 1 [from EP0BUF[5]] - 0xE0DE Frequency byte 0 (LSB) [from EP0BUF[4]] - -XRAM Symbol Rate (big-endian, 4 bytes): - 0xE0CB Symbol Rate byte 3 (MSB) [from EP0BUF[3]] - 0xE0CC Symbol Rate byte 2 [from EP0BUF[2]] - 0xE0CD Symbol Rate byte 1 [from EP0BUF[1]] - 0xE0CE Symbol Rate byte 0 (LSB) [from EP0BUF[0]] -``` - -The byte reversal converts the host's little-endian format to the BCM4500's big-endian register format, so the values can be written directly to the demodulator via I2C without further conversion. - ---- - -## 3. Modulation Mode Dispatch - -### 3.1 Dispatch Table (Rev.2 at CODE:0873) - -After parsing EP0BUF, the firmware validates the modulation type (IRAM 0x4D) is < 10, then dispatches via a jump table: - -``` -CODE:0864 MOV A, 0x4D ; Load modulation type -CODE:0866 CJNE A, #0x0A, +0 ; Compare with 10 -CODE:0869 JC 0x086D ; If < 10, proceed to dispatch -CODE:086B AJMP 0x098E ; If >= 10, skip to post-tune (invalid mod) -CODE:086D MOV DPTR, #0x0873 ; Jump table base -CODE:0870 ADD A, A ; Double index (2 bytes per entry) -CODE:0872 JMP @A+DPTR ; Dispatch -``` - -**Jump Table Memory (CODE:0873, 20 bytes):** - -``` -Offset Bytes AJMP Target Modulation Type ------- ------ ----------- --------------- -+0x00 01 B7 0x08B7 0 = ADV_MOD_DVB_QPSK -+0x02 01 DF 0x08DF 1 = ADV_MOD_TURBO_QPSK -+0x04 01 FA 0x08FA 2 = ADV_MOD_TURBO_8PSK -+0x06 21 15 0x0915 3 = ADV_MOD_TURBO_16QAM -+0x08 21 47 0x0947 4 = ADV_MOD_DCII_C_QPSK (Combo) -+0x0A 21 4F 0x094F 5 = ADV_MOD_DCII_I_QPSK (I-stream) -+0x0C 21 57 0x0957 6 = ADV_MOD_DCII_Q_QPSK (Q-stream) -+0x0E 21 5F 0x095F 7 = ADV_MOD_DCII_C_OQPSK (Offset) -+0x10 01 87 0x0887 8 = ADV_MOD_DSS_QPSK -+0x12 01 87 0x0887 9 = ADV_MOD_DVB_BPSK -``` - -Note: Modulations 8 (DSS) and 9 (DVB BPSK) share the same handler at 0x0887. - -### 3.2 Modulation Handler Details - -Each handler reads the FEC rate from IRAM 0x4F, validates it against a maximum count, looks up a preconfigured value from an XRAM table, and writes it to XRAM 0xE0EB (the BCM4500 FEC configuration register). The handlers then set additional XRAM configuration registers. - -#### Modulation 0: DVB-S QPSK (0x08B7) - -``` -CODE:08B7 MOV A, 0x4F ; FEC index -CODE:08BA SUBB A, #0x07 ; Max 7 FEC rates -CODE:08BC JNC 0x08D0 ; Out of range -> return error -CODE:08BE MOV A, #0xF9 ; XRAM table base = 0xE0F9 -CODE:08C0 ADD A, 0x4F ; + FEC index -CODE:08C2 MOV DPL, A ; -> DPTR = 0xE0F9 + i -CODE:08C9 MOVX A, @DPTR ; Read FEC lookup value -CODE:08CA MOV DPTR, #0xE0EB ; BCM4500 FEC register -CODE:08CD MOVX @DPTR, A ; Write FEC value - -; Set remaining config: -CODE:08D2 XRAM[0xE0EC] = 0x09 ; Modulation type register -CODE:08D9 XRAM[0xE0F6] = 0x00 ; Turbo mode flag = OFF -; Falls through to 0x093C: -CODE:093C XRAM[0xE0F5] = 0x10 ; Demod mode = standard -CODE:0942 IRAM[0x4E] &= 0xBF ; Clear bmDCtuned flag -``` - -#### Modulations 8/9: DSS QPSK / DVB BPSK (0x0887) - -``` -CODE:0887 CLR 0x04 ; Clear _0_4 flag (non-turbo indicator) -CODE:0889 MOV A, 0x4F ; FEC index -CODE:088C SUBB A, #0x07 ; Max 7 FEC rates -CODE:088E JNC 0x08A4 ; Out of range -> use default -; In range: -CODE:0890 XRAM[0xE0EB] = XRAM[0xE0F9 + FEC_index] | 0x80 ; FEC with bit 7 set -; Out of range: -CODE:08A4 XRAM[0xE0EB] = 0x8C ; Default FEC value -; Common: -CODE:08AA XRAM[0xE0EC] = 0x09 ; Mod type register -CODE:08B1 XRAM[0xE0F6] = 0x00 ; Turbo flag = OFF -; Falls through to 0x093C (same as DVB-S QPSK) -``` - -#### Modulation 1: Turbo QPSK (0x08DF) - -``` -CODE:08DF MOV A, 0x4F ; FEC index -CODE:08E2 SUBB A, #0x05 ; Max 5 Turbo QPSK FEC rates -CODE:08E4 JNC 0x08F8 ; Out of range -> error -CODE:08E6 XRAM[0xE0EB] = XRAM[0xE0B7 + FEC_index] ; Turbo QPSK FEC table -; Falls through to 0x0930: -CODE:0930 XRAM[0xE0EC] = 0x09 ; Mod type -CODE:0936 XRAM[0xE0F6] = 0x01 ; Turbo flag = ON -; Falls through to 0x093C: -CODE:093C XRAM[0xE0F5] = 0x10 ; Demod mode = Turbo -CODE:0942 IRAM[0x4E] &= 0xBF ; Clear bmDCtuned -``` - -#### Modulation 2: Turbo 8PSK (0x08FA) - -``` -CODE:08FA MOV A, 0x4F ; FEC index -CODE:08FD SUBB A, #0x05 ; Max 5 Turbo 8PSK FEC rates -CODE:08FF JNC 0x0913 ; Out of range -> error -CODE:0901 XRAM[0xE0EB] = XRAM[0xE0B1 + FEC_index] ; Turbo 8PSK FEC table -; Falls through to 0x0930 (same as Turbo QPSK) -``` - -#### Modulation 3: Turbo 16QAM (0x0915) - -``` -CODE:0915 MOV A, 0x4F ; FEC index -CODE:0918 SUBB A, #0x01 ; Max 1 Turbo 16QAM FEC rate -CODE:091A JNC 0x092E ; Out of range -> error -CODE:091C XRAM[0xE0EB] = XRAM[0xE0BC + FEC_index] ; Turbo 16QAM FEC table -; Falls through to 0x0930 (same as Turbo QPSK/8PSK) -``` - -#### Modulations 4-7: Digicipher II Variants (0x0947-0x095F) - -All four DCII handlers share common post-processing but set different demod modes: - -``` -; Mod 4: DCII Combo (0x0947) -CODE:0947 XRAM[0xE0F5] = 0x10 ; Demod mode = DCII Combo -CODE:094D SJMP 0x0965 ; -> common DCII handler - -; Mod 5: DCII I-stream (0x094F) -CODE:094F XRAM[0xE0F5] = 0x12 ; Demod mode = DCII I-stream (split) -CODE:0955 SJMP 0x0965 - -; Mod 6: DCII Q-stream (0x0957) -CODE:0957 XRAM[0xE0F5] = 0x16 ; Demod mode = DCII Q-stream (split) -CODE:095D SJMP 0x0965 - -; Mod 7: DCII Offset QPSK (0x095F) -CODE:095F XRAM[0xE0F5] = 0x11 ; Demod mode = DCII Offset QPSK -CODE:0964 ; Falls through to 0x0965 - -; Common DCII handler (0x0965): -CODE:0965 MOV A, 0x4F ; FEC index -CODE:0968 SUBB A, #0x09 ; Max 9 DCII FEC rates -CODE:096A JNC 0x097E ; Out of range -> error -CODE:096C XRAM[0xE0EC] = XRAM[0xE0BD + FEC_index] ; DCII FEC/mod table -CODE:0980 XRAM[0xE0EB] = 0xFC ; Fixed DCII FEC code -CODE:0987 XRAM[0xE0F6] = 0x00 ; Turbo flag = OFF -CODE:098B IRAM[0x4E] |= 0x40 ; SET bmDCtuned flag -``` - -### 3.3 BCM4500 XRAM Configuration Summary - -After modulation dispatch completes, four XRAM registers hold the BCM4500 configuration: - -| XRAM Address | Register Name | DVB-S | Turbo QPSK/8PSK/16QAM | DCII | DSS/BPSK | -|-------------|---------------|-------|----------------------|------|----------| -| 0xE0EB | FEC Code Rate | Lookup from 0xE0F9+i | Lookup from tables | 0xFC (fixed) | Lookup | 0x80 from 0xE0F9+i | -| 0xE0EC | Modulation Type | 0x09 | 0x09 | From 0xE0BD+i table | 0x09 | -| 0xE0F5 | Demod Mode | 0x10 | 0x10 | 0x10/0x11/0x12/0x16 | 0x10 | -| 0xE0F6 | Turbo Flag | 0x00 | 0x01 | 0x00 | 0x00 | - -### 3.4 FEC Rate Lookup Tables - -The FEC lookup tables are populated at boot from the init data table in CODE space, copied to XRAM during startup: - -| Table Base | Modulation | Max Index | FEC Rates | -|-----------|------------|-----------|-----------| -| XRAM 0xE0F9 | DVB-S QPSK / DSS / BPSK | 7 | Standard Viterbi rates (1/2, 2/3, 3/4, 5/6, 7/8, auto, none) | -| XRAM 0xE0B7 | Turbo QPSK | 5 | Turbo QPSK code rates | -| XRAM 0xE0B1 | Turbo 8PSK | 5 | Turbo 8PSK code rates | -| XRAM 0xE0BC | Turbo 16QAM | 1 | Single 16QAM code rate | -| XRAM 0xE0BD | DCII (all variants) | 9 | DCII code rate + modulation combined | - ---- - -## 4. BCM4500 I2C Write Sequence - -### 4.1 Post-Dispatch: DVB Mode and I2C Programming - -After the modulation dispatch sets XRAM configuration registers, the tune function enters the common post-processing path at CODE:098E: - -``` -; Copy _0_4 flag to _0_5 (DVB/non-DVB indicator) -CODE:098E MOV CY, 0x04 ; Read _0_4 flag -CODE:0990 MOV 0x05, CY ; Copy to _0_5 - -; Configure DVB mode pin -CODE:0992 LCALL 0x21D3 ; FUN_CODE_21d3: SET_DVB_MODE - ; Sets P3.6 based on _0_4 - -; Attempt I2C programming (up to 3 tries) -CODE:0995 MOV 0x3B, #0x03 ; Retry counter = 3 -CODE:0998 MOV A, 0x3B -CODE:099A JZ 0x09A7 ; If 0 retries left, return failure -CODE:099C LCALL 0x1DD0 ; FUN_CODE_1dd0: Demod I2C write sequence -CODE:099F JC 0x09A3 ; If carry set (success), return -CODE:09A1 SETB CY ; Set carry = success -CODE:09A2 RET -CODE:09A3 DEC 0x3B ; Decrement retry counter -CODE:09A5 SJMP 0x0998 ; Try again -CODE:09A7 CLR CY ; Clear carry = failure -CODE:09A8 RET -``` - -### 4.2 FUN_CODE_1DD0: Demod Scan (3 I2C Address Attempts) - -The demod scan function tries to program the BCM4500 at up to three different I2C device addresses. This supports hardware variants where the BCM4500 may appear at different I2C addresses: - -```c -// Pseudocode for FUN_CODE_1dd0 (Rev.2 v2.10.4) -char demod_scan(void) { - for (DAT_INTMEM_3c = 0; DAT_INTMEM_3c < 3; DAT_INTMEM_3c++) { - // Compute I2C parameters from iteration index - // The multiplication by 0x11 and offset calculations produce: - // Iteration 0: device addr derived, register set A - // Iteration 1: device addr derived, register set B - // Iteration 2: device addr derived, register set C - result = FUN_CODE_1670(buf_addr, device_param, data_len); - if (result == success) { - return success; // Carry set - } - } - return failure; // Carry clear -} -``` - -### 4.3 FUN_CODE_1670: BCM4500 Indirect Register Write - -This is the core I2C programming function. The BCM4500 uses an indirect register access protocol through three I2C-accessible registers: - -``` -BCM4500 I2C Registers (accessed at device address 0x10): - 0xA6 = Page/Address register (indirect address high byte) - 0xA7 = Data register (indirect data) - 0xA8 = Command register (indirect command: 0x03 = write) -``` - -**FUN_CODE_1670 decompiled (Rev.2 v2.10.4):** - -```c -void bcm4500_indirect_write(byte buf_hi, byte device_param, byte data_count) { - // Step 1: Wait for BCM4500 ready (poll regs 0xA2, 0xA8, 0xA4) - FUN_CODE_1e73(); // 3-register bus wait - if (!carry) return; // BCM4500 not ready - - // Step 2: Write page address (0x00) to register 0xA6 - XRAM[0xE111] = 0x00; // Page = 0 - IRAM[0x45] = 0xE1; // Buffer pointer high - IRAM[0x46] = 0x11; // Buffer pointer low -> XRAM 0xE111 - FUN_CODE_136c(1, 0, 0xA6, 0x10); // I2C write 1 byte to device 0x10, reg 0xA6 - - // Step 3: Write data to register 0xA7 - IRAM[0x45] = buf_hi; // Source buffer high byte - IRAM[0x46] = device_param; // Source buffer low byte - FUN_CODE_136c(data_count, 0, 0xA7, 0x10); // I2C write N bytes to reg 0xA7 - - // Step 4: Write command (0x03 = indirect write) to register 0xA8 - XRAM[0xE111] = 0x03; // Command = write - IRAM[0x45] = 0xE1; - IRAM[0x46] = 0x11; - FUN_CODE_136c(1, 0, 0xA8, 0x10); // I2C write 1 byte to reg 0xA8 - - // Step 5: Wait for write completion (poll regs 0xA8, 0xA2) - FUN_CODE_1ea9(); - - // Step 6: Verify -- read back from 0xA7 and compare - XRAM[0xE111] = 0x00; - FUN_CODE_136c(1, 0, 0xA6, 0x10); // Re-select page 0 - FUN_CODE_20cb(); // Read reg 0xA7 - // Compare read-back with expected value -} -``` - -### 4.4 FUN_CODE_136C: I2C Multi-Byte Write Primitive - -This is the lowest-level I2C write function that talks to the FX2's I2C controller: - -```c -// Pseudocode for FUN_CODE_136c (Rev.2 v2.10.4) -void i2c_write(byte byte_count, byte flags, byte register_addr, byte device_addr) { - // Setup I2C controller at XRAM 0xE678 (FX2 I2C register) - FUN_CODE_2000(); // Wait for I2C bus ready - FUN_CODE_2206(device_addr << 1); // Send I2C start + device address (write) - FUN_CODE_2224(register_addr); // Send register address - - // Transfer data bytes from buffer at IRAM[0x45:0x46] - for (i = 0; i < byte_count; i++) { - byte data = XRAM[IRAM[0x45]:IRAM[0x46] + i]; - FUN_CODE_2224(data); // Send each data byte - } - - // Send I2C stop condition - FUN_CODE_1aa3(); // I2C stop + cleanup -} -``` - -### 4.5 Complete I2C Register Write Sequence for a Tune Operation - -Combining all the above, a complete tune operation produces the following I2C bus transactions: - -``` -=== Phase 1: Pre-Tune LNB/Tone Configuration === -(These happen before TUNE_8PSK, via separate vendor commands) - -1. SET_LNB_VOLTAGE (0x8B): GPIO P0.4 high/low (no I2C) -2. SET_22KHZ_TONE (0x8C): GPIO P0.3 high/low (no I2C) - -=== Phase 2: Tune Parameter Setup === -(EP0BUF parsing -- no I2C, just XRAM writes) - -3. EP0BUF[8] -> IRAM[0x4D] (modulation) -4. EP0BUF[9] -> IRAM[0x4F] (FEC rate) -5. EP0BUF[4-7] -> XRAM[0xE0DB-0xE0DE] (frequency, byte-reversed) -6. EP0BUF[0-3] -> XRAM[0xE0CB-0xE0CE] (symbol rate, byte-reversed) - -=== Phase 3: Modulation Dispatch === -(XRAM configuration writes -- no I2C) - -7. XRAM[0xE0EB] = FEC lookup value (from modulation-specific table) -8. XRAM[0xE0EC] = modulation type (usually 0x09) -9. XRAM[0xE0F5] = demod mode (0x10/0x11/0x12/0x16) -10. XRAM[0xE0F6] = turbo flag (0x00 or 0x01) - -=== Phase 4: DVB Mode GPIO === - -11. P3.6 set/clear based on _0_4 flag (DVB vs non-DVB mode) - -=== Phase 5: BCM4500 I2C Programming (x3 retry, x3 addresses) === - -For each attempt (up to 3 retries, each trying up to 3 I2C addresses): - - --- Wait for BCM4500 ready --- - 12. I2C READ device 0x10, reg 0xA2 (poll status register) - 13. I2C READ device 0x10, reg 0xA8 (poll command register) - 14. I2C READ device 0x10, reg 0xA4 (poll lock/ready register) - - --- Write page address --- - 15. I2C WRITE device 0x10, reg 0xA6 <- 0x00 (select page 0) - - --- Write configuration data --- - 16. I2C WRITE device 0x10, reg 0xA7 <- [N config bytes from XRAM buffer] - Data includes: frequency, symbol rate, FEC, modulation, demod mode, - turbo flag -- assembled from XRAM 0xE0EB/EC/F5/F6 and 0xE0CB-CE/DB-DE - - --- Issue indirect write command --- - 17. I2C WRITE device 0x10, reg 0xA8 <- 0x03 (execute indirect write) - - --- Wait for completion --- - 18. I2C READ device 0x10, reg 0xA8 (poll for command done) - 19. I2C READ device 0x10, reg 0xA2 (verify status) - - --- Verify write --- - 20. I2C WRITE device 0x10, reg 0xA6 <- 0x00 (re-select page) - 21. I2C READ device 0x10, reg 0xA7 (read back data) - 22. Compare read-back with expected value -``` - -### 4.6 I2C Device Address Note - -The BCM4500 demodulator is accessed at I2C device address **0x10** (7-bit address, shifted to 0x20 for write, 0x21 for read on the wire). In some firmware versions, the device also responds at alternate addresses: -- 0x3F (used by v2.13 for status polling in INT0) -- 0x7F (used by v2.13 for status polling in INT0) - -The use of 0x3F and 0x7F in v2.13's INT0 handler (demod availability probing) suggests these may be alternate I2C addresses on different hardware revisions, or they may access different internal register banks of the BCM4500. - ---- - -## 5. Signal Acquisition - -### 5.1 GET_SIGNAL_LOCK (Vendor Command 0x90) - -**Jump table targets:** -- v2.06: 0x020B -- v2.13: 0x022D -- Rev.2: 0x0217 - -The signal lock handler reads BCM4500 register 0xA4 and returns the result to the host via EP0BUF. - -**Rev.2 implementation chain:** - -``` -; Vendor command handler at 0x0217: -CODE:0217 MOV A, 0x4E ; Config status byte -CODE:0219 JNB ACC.0, ... ; Check device started -CODE:021C LCALL 0x2236 ; FUN_CODE_2236: Read lock register - -; FUN_CODE_2236: -CODE:2236 MOV R7, #0xA4 ; Register = 0xA4 -CODE:2238 LCALL 0x20CB ; FUN_CODE_20cb: I2C read - -; FUN_CODE_20cb: I2C single register read -; Sets up buffer at XRAM 0xE114 -; Calls FUN_CODE_0f00(device=0x0F, offset=0, reg=param, addr=0x10) -; Returns byte from XRAM[0xE114] -``` - -**I2C transaction:** -``` -I2C READ device 0x10, register 0xA4, 1 byte -> XRAM[0xE114] -``` - -**Register 0xA4 bit fields:** -``` -Bit 5 (0x20): Signal locked (demod has achieved lock) -``` - -The firmware returns the raw register byte to EP0BUF. The host driver (both Linux and Windows) interprets any non-zero value as "locked": - -```c -// Linux kernel driver (gp8psk-fe.c): -gp8psk_usb_in_op(d, GET_SIGNAL_LOCK, 0, 0, &lock, 1); -if (lock) - *status = FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI | - FE_HAS_SIGNAL | FE_HAS_CARRIER; - -// Windows BDA driver (SkyWalker1Control.cpp): -ControlUsbDevice(pKSDeviceObject, GET_SIGNAL_LOCK, 0, 0, &ucSignalStatus, 1, true); -if (ucSignalStatus) - *pbSignalLockStatus = TRUE; -``` - -### 5.2 GET_SIGNAL_STRENGTH (Vendor Command 0x87) - -**Jump table targets:** -- v2.06: 0x0140 -- v2.13: 0x0162 -- Rev.2: 0x014C - -This command returns 6 bytes of signal quality data. The first two bytes contain the SNR value. - -**Rev.2 implementation (FUN_CODE_15eb at 0x15EB):** - -The signal strength reader performs a more complex sequence than simple lock detection: - -1. Writes configuration to XRAM buffer via FUN_CODE_067e (multi-mode data access) -2. Performs BCM4500 indirect register write via FUN_CODE_1670 (same protocol as tuning) -3. Reads back BCM4500 register 0xA7 via FUN_CODE_0f00 -4. Compares write value with read-back for validity check -5. If valid, copies 6 bytes of signal data to EP0BUF - -**I2C transactions during signal strength read:** -``` -1. I2C WRITE device 0x10, reg 0xA6 <- 0x00 (page select) -2. I2C WRITE device 0x10, reg 0xA7 <- [config] (request signal data) -3. I2C WRITE device 0x10, reg 0xA8 <- 0x03 (execute) -4. I2C READ device 0x10, reg 0xA7, 1 byte (verify/read) -5. Copy 6-byte result buffer to EP0BUF -``` - -**EP0BUF response format (6 bytes):** -``` -Byte 0: SNR low byte (LSB) -Byte 1: SNR high byte (MSB) -Bytes 2-5: Reserved/diagnostic (BCM4500 internal registers) -``` - -**SNR scaling (from Windows BDA driver):** -```c -ulSignalStrength = (int)(ucBuffer[1]) << 8 | ucBuffer[0]; -// SNR is in dBu * 256 units -// SNR * 17 maps to 0-65535 range (100% at SNR >= 0x0F00) -if (ulSignalStrength <= 0x0F00) - *pulSigStrength = (ulSignalStrength << 4) + ulSignalStrength; // * 17 -else - *pulSigStrength = 0xFFFF; -``` - -### 5.3 Differences Between Firmware Versions - -**v2.06 GET_SIGNAL_STRENGTH (0x0140):** -- Checks IRAM[0x6D] bit 0 (demod active) -- Reads three I2C status registers (0xA2, 0xA8, 0xA4) to compute quality -- Loops up to 6 iterations polling for demod readiness -- Uses FUN_CODE_0c97 for I2C reads - -**v2.13 GET_SIGNAL_STRENGTH (0x0162):** -- Checks IRAM[0x4F] bit 0 (demod active) -- Uses different function call chain (FUN_CODE_1278 vs v2.06's FUN_CODE_0c97) -- Same overall logic with relocated internal variables -- Simplified polling (uses consolidated BCM4500 status register) - -**Rev.2 GET_SIGNAL_STRENGTH (0x014C -> FUN_CODE_15eb):** -- Checks IRAM[0x4E] bit 0 -- Uses FUN_CODE_067e for multi-mode data access -- Validates read-back against written configuration -- Most granular implementation with explicit verify step - ---- - -## 6. LNB and Tone Control - -### 6.1 SET_LNB_VOLTAGE (Vendor Command 0x8B) - -**Jump table targets:** -- v2.06: 0x01CB -- v2.13: 0x01ED -- Rev.2: 0x01D7 - -LNB voltage selection is pure GPIO -- no I2C transactions. The FX2 pin P0.4 directly controls a voltage regulator on the PCB. - -**Rev.2 FUN_CODE_21b1 decompiled:** -```c -void set_lnb_voltage(void) { - if (_0_4 != 0) { - // wValue = 1: Set 18V (horizontal / circular-left polarization) - P0 |= 0x10; // P0.4 = HIGH -> 18V - DAT_INTMEM_4e |= 0x20; // Set bmSEL18V in config status - } else { - // wValue = 0: Set 13V (vertical / circular-right polarization) - P0 &= 0xEF; // P0.4 = LOW -> 13V - DAT_INTMEM_4e &= 0xDF; // Clear bmSEL18V in config status - } -} -``` - -**Pin mapping across versions:** - -| Version | 18V Pin | Config Status Bit | -|---------|---------|------------------| -| v2.06 | P0.4 | IRAM[0x6D] bit 5 | -| v2.13 | P0.4 | IRAM[0x4F] bit 5 | -| Rev.2 | P0.4 | IRAM[0x4E] bit 5 | - -The `wValue` parameter from the USB SETUP packet is passed via the `_0_4` bit flag (IRAM bit-addressable area). The Windows driver sends: -```c -ControlUsbDevice(device, SET_LNB_VOLTAGE, (ucVoltage == SEC_VOLTAGE_18), 0, NULL, 0); -``` - -**Polarization mapping:** -- Horizontal / Circular-Left: 18V (wValue=1) -- Vertical / Circular-Right: 13V (wValue=0) - -### 6.2 SET_22KHZ_TONE (Vendor Command 0x8C) - -**Jump table targets:** -- v2.06: 0x01DD -- v2.13: 0x01FF -- Rev.2: 0x01E9 - -Like LNB voltage, the 22 kHz tone is pure GPIO. P0.3 enables/disables an external 22 kHz oscillator on the PCB. - -**Rev.2 FUN_CODE_21c2 decompiled:** -```c -void set_22khz_tone(void) { - if (_0_4 != 0) { - // wValue = 1: Tone ON (high band) - P0 |= 0x08; // P0.3 = HIGH -> 22 kHz oscillator enabled - DAT_INTMEM_4e |= 0x10; // Set bm22kHz in config status - } else { - // wValue = 0: Tone OFF (low band) - P0 &= 0xF7; // P0.3 = LOW -> 22 kHz oscillator disabled - DAT_INTMEM_4e &= 0xEF; // Clear bm22kHz in config status - } -} -``` - -**Pin mapping across versions:** - -| Version | Tone Pin | Config Status Bit | -|---------|----------|------------------| -| v2.06 | P0.3 | IRAM[0x6D] bit 4 | -| v2.13 | P0.3 | IRAM[0x4F] bit 4 | -| Rev.2 | P0.3 | IRAM[0x4E] bit 4 | - -The 22 kHz tone is used for satellite band selection: -- **Tone ON**: Select high-band LNB oscillator (universal LNB: 10.6 GHz LO) -- **Tone OFF**: Select low-band LNB oscillator (universal LNB: 9.75 GHz LO) - -**Windows driver tone logic:** -```c -// SEC_TONE_ON = 0, SEC_TONE_OFF = 1 -ControlUsbDevice(device, SET_22KHZ_TONE, (ucTone == 0), 0, NULL, 0); -``` - -Note the inverted logic: `SEC_TONE_ON = 0` results in `wValue = 1` (tone active). - -### 6.3 ConfigureTuner: Full Tuning Sequence (Windows BDA Driver) - -The Windows driver's `ConfigureTuner()` function shows the complete host-side tuning sequence: - -```c -NTSTATUS ConfigureTuner(PKSDEVICE device, PBDATUNER_DEVICE_PARAMETER config) { - // Step 1: Set LNB voltage based on polarization - if (config->Polarity == BDA_POLARISATION_LINEAR_H || - config->Polarity == BDA_POLARISATION_CIRCULAR_L) { - SetLnbVoltage(device, SEC_VOLTAGE_18); // 0x8B, wValue=1 - } else { - SetLnbVoltage(device, SEC_VOLTAGE_13); // 0x8B, wValue=0 - } - - // Step 2: Disable tone during tune - SetTunerTone(device, SEC_TONE_OFF); // 0x8C, wValue=0 - - // Step 3: Send tune command - TuneDevice(device, config); // 0x86, 10 bytes -} -``` - ---- - -## 7. GPIO Pin Summary - -All LNB, tone, and DVB mode control is performed through direct GPIO manipulation with no I2C involvement: - -``` -FX2 Port 0 Pin Assignments (Rev.2 v2.10.4): - P0.0 -- (unused in Rev.2; DiSEqC data in v2.13) - P0.1 -- (unused) - P0.2 -- (set during init, purpose TBD) - P0.3 -- 22 kHz tone oscillator enable (all versions) - P0.4 -- LNB 13V/18V voltage select (all versions) - Also DiSEqC data pin in Rev.2 - P0.5 -- (unused) - P0.6 -- GPIO control (FUN_CODE_1fcf) - P0.7 -- DiSEqC data pin (v2.06 only) - -FX2 Port 3 Pin Assignments (Rev.2 v2.10.4): - P3.4 -- GPIO control (FUN_CODE_1fcf) - P3.6 -- DVB mode select (SET_DVB_MODE, FUN_CODE_21d3) -``` - ---- - -## 8. Cross-Version Comparison - -### 8.1 Tune Function Correspondence - -| Component | v2.06 | v2.13 | Rev.2 v2.10.4 | -|-----------|-------|-------|---------------| -| Vendor dispatch | 0x0056 | 0x0056 | 0x0056 | -| TUNE_8PSK handler | 0x012E | 0x012E | 0x0118 | -| EP0 flush | 0x23E0 | 0x231E | 0x2167 | -| Main tune function | 0x0800 | 0x09DF | 0x0800 | -| Config status byte | IRAM 0x6D | IRAM 0x4F | IRAM 0x4E | -| Modulation storage | IRAM 0x4D* | IRAM 0x4D | IRAM 0x4D | -| FEC storage | IRAM 0x4F* | IRAM 0x4F | IRAM 0x4F | -| Mod dispatch table | 0x0873* | embedded in 0x09DF | 0x0873 | -| BCM4500 indirect write | similar | FUN_CODE_15b8 chain | FUN_CODE_1670 | -| I2C write primitive | FUN_CODE_1556 | FUN_CODE_15b8 | FUN_CODE_136c | -| Signal lock read | 0x020B | 0x022D | 0x0217 -> FUN_CODE_2236 | -| Signal strength | 0x0140 | 0x0162 | 0x014C -> FUN_CODE_15eb | -| LNB voltage | 0x01CB | 0x01ED | 0x01D7 -> FUN_CODE_21b1 | -| 22 kHz tone | 0x01DD | 0x01FF | 0x01E9 -> FUN_CODE_21c2 | - -*v2.06 shares the same IRAM layout as Rev.2 for modulation/FEC but uses different XRAM offsets. - -### 8.2 Key Architectural Differences - -**v2.06:** -- Simplest implementation, single code path -- No demod verification or retry logic during tune -- BCM4500 status polling reads 3 registers (0xA2, 0xA8, 0xA4) -- Signal strength handler loops up to 6 times - -**Rev.2 v2.10.4:** -- 3 I2C address attempts per tune (FUN_CODE_1dd0) -- 3 outer retries for the whole demod scan -- BCM4500 indirect write with read-back verification (FUN_CODE_1670) -- Most granular function decomposition -- Smallest binary despite highest function count - -**v2.13:** -- Adds host-controlled DELAY_COMMAND (0x9C) for tuning timing -- Adds INIT_DEMOD (0x9A) for host-triggered re-initialization -- Adds GET_DEMOD_STATUS (0x99) for diagnostic register reads -- INT0 repurposed for demod availability polling -- Most robust error recovery (20-attempt retry loops for init) - ---- - -## 9. Data Flow Diagram - -``` -Host Application (DVB app / szap / w_scan) - | - v -Linux dvb-usb-gp8psk / Windows BDA Driver - | - | USB Control Transfer (EP0) - | bmRequestType = 0x40 (Vendor OUT) - | bRequest = 0x86 (TUNE_8PSK) - | wValue = 0, wIndex = 0, wLength = 10 - | Data: [SR0 SR1 SR2 SR3 F0 F1 F2 F3 MOD FEC] - | - v -FX2 Microcontroller (Cypress CY7C68013A) - | - | 1. EP0BUF (XRAM 0xE740-0xE749) receives 10 bytes - | 2. Parse: mod->IRAM[0x4D], fec->IRAM[0x4F] - | 3. Byte-reverse freq->XRAM[0xE0DB-DE], sr->XRAM[0xE0CB-CE] - | 4. Dispatch on mod type (jump table at CODE:0873) - | 5. Set XRAM[0xE0EB/EC/F5/F6] per modulation - | 6. GPIO: P3.6 for DVB mode - | - | I2C Bus (FX2 I2C controller at XRAM 0xE678) - | Device address: 0x10 (BCM4500) - | - v -BCM4500 Demodulator (Broadcom DVB-S/8PSK) - | - | Indirect register protocol: - | reg 0xA6 <- page (0x00) - | reg 0xA7 <- config data (freq, SR, FEC, mod params) - | reg 0xA8 <- command (0x03 = write) - | - | After programming: - | reg 0xA4 bit 5 = signal lock status - | reg 0xA7 = SNR / signal quality readback - | - v -RF Front End - | - | LNB Control (GPIO, no I2C): - | P0.4 = voltage (13V/18V) - | P0.3 = 22 kHz tone (band select) - | - v -Satellite LNB -> Dish -> Satellite -``` - ---- - -## Sources - -- Ghidra firmware disassembly: v2.06 (port 8193), v2.13 FW1 (port 8194), Rev.2 v2.10.4 (port 8197) -- Windows BDA driver source: `SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp` -- Windows BDA driver headers: `SkyWalker1_Final_Release/Include/SkyWalker1Control.h`, `SkyWalker1CommonDef.h` -- Linux kernel driver: `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h`, `gp8psk-fe.c` -- Companion documents: `gp8psk-driver-analysis.md`, `firmware-analysis-v206-vs-v213.md`, `rev2-deep-analysis.md` +# Genpix SkyWalker-1 TUNE_8PSK (0x86) Protocol Deep-Dive + +## Executive Summary + +This document presents a complete reverse engineering analysis of the TUNE_8PSK vendor command (0x86) as implemented in three Genpix SkyWalker-1 FX2 firmware versions. The analysis traces the full signal path from USB control transfer through EP0BUF parameter parsing, modulation-specific BCM4500 configuration, I2C register programming, and signal acquisition polling. Supporting commands (LNB voltage, 22 kHz tone, signal lock, signal strength) are fully documented. + +**Firmware versions analyzed:** +- v2.06 (Ghidra port 8193) -- 61 functions, earliest revision +- v2.13 FW1 (Ghidra port 8194) -- 88 functions, latest revision +- Rev.2 v2.10.4 (Ghidra port 8197) -- 107 functions, intermediate revision + +**Primary analysis target:** Rev.2 v2.10.4 (clearest decompilation, most granular function decomposition) + +--- + +## 1. TUNE_8PSK Command Handler (0x86) + +### 1.1 Vendor Dispatch Entry Points + +All three firmware versions use identical vendor command dispatch logic at CODE:0056. The dispatcher checks `bmRequestType` bit 6 (vendor request), validates `bRequest` is in range, and performs an indexed jump via `JMP @A+DPTR` at the jump table starting at CODE:0076. + +| Firmware | TUNE_8PSK Jump Target | Config Status Byte | Tune Function | +|----------|----------------------|-------------------|---------------| +| v2.06 | 0x012E | IRAM 0x6D | 0x0800 (via FUN_CODE_09a7) | +| v2.13 | 0x012E | IRAM 0x4F | 0x09DF | +| Rev.2 v2.10.4 | 0x0118 | IRAM 0x4E | 0x0800 | + +### 1.2 Handler Inline Code + +The TUNE_8PSK handler at the jump table target performs two operations: + +1. **Wait for EP0 data phase** -- calls the EP0 flush function to ensure the 10-byte command payload has arrived in EP0BUF +2. **Check device status** -- reads the config status byte and verifies bit 0 (bm8pskStarted) is set before proceeding +3. **Call tune function** -- if the device is started, calls the main tune function + +**Rev.2 v2.10.4 (0x0118):** +``` +CODE:0118 LCALL 0x2167 ; EP0 flush -- wait for EP0BUF data ready +CODE:011B MOV A, 0x4E ; Read config status byte (IRAM 0x4E) +CODE:011D JNB ACC.0, +3 ; Skip tune if device not started +CODE:0120 LCALL 0x0800 ; Call main tune function (FUN_CODE_0800) +``` + +**v2.06 (0x012E):** +``` +CODE:012E LCALL 0x23E0 ; EP0 flush function +CODE:0131 MOV A, 0x6D ; Config status byte (IRAM 0x6D) +CODE:0133 JNB ACC.0, +3 ; Skip if not started +CODE:0136 LCALL 0x0800 ; Main tune function +``` + +**v2.13 (0x012E):** +``` +CODE:012E LCALL 0x231E ; EP0 flush function +CODE:0131 MOV A, 0x4F ; Config status byte (IRAM 0x4F) +CODE:0133 JNB ACC.0, +3 ; Skip if not started +CODE:0136 LCALL 0x09DF ; Main tune function (different address) +``` + +--- + +## 2. EP0BUF Parameter Parsing + +### 2.1 10-Byte Command Format + +The host (Windows BDA driver or Linux dvb-usb-gp8psk) sends a 10-byte payload via USB control transfer OUT: + +``` +USB Setup Packet: + bmRequestType = 0x40 (Vendor, Host-to-Device) + bRequest = 0x86 (TUNE_8PSK) + wValue = 0x0000 + wIndex = 0x0000 + wLength = 10 + +EP0BUF Layout (0xE740-0xE749): + Offset XRAM Addr Content Encoding + ------ --------- ------------------- ------------------ + [0] 0xE740 Symbol Rate byte 0 Little-endian LSB + [1] 0xE741 Symbol Rate byte 1 + [2] 0xE742 Symbol Rate byte 2 + [3] 0xE743 Symbol Rate byte 3 Little-endian MSB + [4] 0xE744 Frequency byte 0 Little-endian LSB + [5] 0xE745 Frequency byte 1 + [6] 0xE746 Frequency byte 2 + [7] 0xE747 Frequency byte 3 Little-endian MSB + [8] 0xE748 Modulation Type 0-9 (see section 3) + [9] 0xE749 Inner FEC Rate See FEC tables +``` + +**Symbol Rate** is in samples per second (sps). The Windows driver converts from ksps: `ulTempSymbolRate = pDeviceParameter->ulSymbolRate * 1000` + +**Frequency** is in kHz, computed as `(CarrierFrequency - LO_Frequency) * FrequencyMultiplier`. Valid range: 800,000 - 2,250,000 kHz (after LNB downconversion: 950-2150 MHz IF). + +### 2.2 Firmware EP0BUF Read Sequence (Rev.2 v2.10.4 Disassembly) + +The tune function at 0x0800 begins with direct EP0BUF reads: + +``` +; Read modulation type and FEC rate +CODE:0802 MOV DPTR, #0xE748 ; EP0BUF[8] = modulation type +CODE:0805 MOVX A, @DPTR +CODE:0806 MOV 0x4D, A ; Store to IRAM 0x4D (DAT_INTMEM_4d) +CODE:0808 INC DPTR ; DPTR = 0xE749 +CODE:0809 MOVX A, @DPTR ; EP0BUF[9] = FEC rate +CODE:080A MOV 0x4F, A ; Store to IRAM 0x4F (DAT_INTMEM_4f) +``` + +**Frequency bytes** (EP0BUF[4-7]) are byte-reversed from little-endian to big-endian for BCM4500: + +``` +; Loop: i = 0..3 +; EP0BUF[4+i] -> XRAM[0xE0DE - i] (frequency, LE -> BE) +CODE:0816 MOV A, #0x44 ; 0x44 = EP0BUF offset for freq[0] (0xE740+4=0xE744) +CODE:0818 ADD A, 0x3B ; + loop index i +CODE:081A MOV DPL, A +CODE:081C CLR A +CODE:081D ADDC A, #0xE7 ; DPH = 0xE7 +CODE:0821 MOVX A, @DPTR ; Read EP0BUF[4+i] +CODE:0822 MOV R7, A ; Save byte + +; Compute destination: 0xE0D8 + (6 - i) = 0xE0DE - i +CODE:0827 MOV A, #0x06 +CODE:0829 SUBB A, R5 ; 6 - i +CODE:082F MOV A, #0xD8 ; Base = 0xE0D8 +CODE:0831 ADD A, R5 +CODE:0832 MOV DPL, A +CODE:0834 MOV A, #0xE0 +CODE:0837 MOV DPH, A +CODE:0839 MOV A, R7 +CODE:083A MOVX @DPTR, A ; Store byte-reversed +``` + +**Symbol rate bytes** (EP0BUF[0-3]) follow the same pattern: + +``` +; EP0BUF[i] -> XRAM[0xE0C7 + (7 - i)] = XRAM[0xE0CE - i] (symbol rate, LE -> BE) +CODE:083B MOV A, #0x40 ; 0x40 = EP0BUF offset for SR[0] (0xE740+0=0xE740) +CODE:083D ADD A, 0x3B ; + loop index i +CODE:0846 MOVX A, @DPTR ; Read EP0BUF[i] + +; Destination: 0xE0C7 + (7 - i) = 0xE0CE - i +CODE:084C MOV A, #0x07 +CODE:084E SUBB A, R5 ; 7 - i +CODE:0854 MOV A, #0xC7 ; Base = 0xE0C7 +CODE:0856 ADD A, R5 +CODE:085F MOVX @DPTR, A ; Store byte-reversed +``` + +### 2.3 XRAM Storage Map After Parsing + +``` +IRAM Variables: + 0x4D Modulation type (0-9) [from EP0BUF[8]] + 0x4F Inner FEC rate index [from EP0BUF[9]] + +XRAM Frequency (big-endian, 4 bytes): + 0xE0DB Frequency byte 3 (MSB) [from EP0BUF[7]] + 0xE0DC Frequency byte 2 [from EP0BUF[6]] + 0xE0DD Frequency byte 1 [from EP0BUF[5]] + 0xE0DE Frequency byte 0 (LSB) [from EP0BUF[4]] + +XRAM Symbol Rate (big-endian, 4 bytes): + 0xE0CB Symbol Rate byte 3 (MSB) [from EP0BUF[3]] + 0xE0CC Symbol Rate byte 2 [from EP0BUF[2]] + 0xE0CD Symbol Rate byte 1 [from EP0BUF[1]] + 0xE0CE Symbol Rate byte 0 (LSB) [from EP0BUF[0]] +``` + +The byte reversal converts the host's little-endian format to the BCM4500's big-endian register format, so the values can be written directly to the demodulator via I2C without further conversion. + +--- + +## 3. Modulation Mode Dispatch + +### 3.1 Dispatch Table (Rev.2 at CODE:0873) + +After parsing EP0BUF, the firmware validates the modulation type (IRAM 0x4D) is < 10, then dispatches via a jump table: + +``` +CODE:0864 MOV A, 0x4D ; Load modulation type +CODE:0866 CJNE A, #0x0A, +0 ; Compare with 10 +CODE:0869 JC 0x086D ; If < 10, proceed to dispatch +CODE:086B AJMP 0x098E ; If >= 10, skip to post-tune (invalid mod) +CODE:086D MOV DPTR, #0x0873 ; Jump table base +CODE:0870 ADD A, A ; Double index (2 bytes per entry) +CODE:0872 JMP @A+DPTR ; Dispatch +``` + +**Jump Table Memory (CODE:0873, 20 bytes):** + +``` +Offset Bytes AJMP Target Modulation Type +------ ------ ----------- --------------- ++0x00 01 B7 0x08B7 0 = ADV_MOD_DVB_QPSK ++0x02 01 DF 0x08DF 1 = ADV_MOD_TURBO_QPSK ++0x04 01 FA 0x08FA 2 = ADV_MOD_TURBO_8PSK ++0x06 21 15 0x0915 3 = ADV_MOD_TURBO_16QAM ++0x08 21 47 0x0947 4 = ADV_MOD_DCII_C_QPSK (Combo) ++0x0A 21 4F 0x094F 5 = ADV_MOD_DCII_I_QPSK (I-stream) ++0x0C 21 57 0x0957 6 = ADV_MOD_DCII_Q_QPSK (Q-stream) ++0x0E 21 5F 0x095F 7 = ADV_MOD_DCII_C_OQPSK (Offset) ++0x10 01 87 0x0887 8 = ADV_MOD_DSS_QPSK ++0x12 01 87 0x0887 9 = ADV_MOD_DVB_BPSK +``` + +Note: Modulations 8 (DSS) and 9 (DVB BPSK) share the same handler at 0x0887. + +### 3.2 Modulation Handler Details + +Each handler reads the FEC rate from IRAM 0x4F, validates it against a maximum count, looks up a preconfigured value from an XRAM table, and writes it to XRAM 0xE0EB (the BCM4500 FEC configuration register). The handlers then set additional XRAM configuration registers. + +#### Modulation 0: DVB-S QPSK (0x08B7) + +``` +CODE:08B7 MOV A, 0x4F ; FEC index +CODE:08BA SUBB A, #0x07 ; Max 7 FEC rates +CODE:08BC JNC 0x08D0 ; Out of range -> return error +CODE:08BE MOV A, #0xF9 ; XRAM table base = 0xE0F9 +CODE:08C0 ADD A, 0x4F ; + FEC index +CODE:08C2 MOV DPL, A ; -> DPTR = 0xE0F9 + i +CODE:08C9 MOVX A, @DPTR ; Read FEC lookup value +CODE:08CA MOV DPTR, #0xE0EB ; BCM4500 FEC register +CODE:08CD MOVX @DPTR, A ; Write FEC value + +; Set remaining config: +CODE:08D2 XRAM[0xE0EC] = 0x09 ; Modulation type register +CODE:08D9 XRAM[0xE0F6] = 0x00 ; Turbo mode flag = OFF +; Falls through to 0x093C: +CODE:093C XRAM[0xE0F5] = 0x10 ; Demod mode = standard +CODE:0942 IRAM[0x4E] &= 0xBF ; Clear bmDCtuned flag +``` + +#### Modulations 8/9: DSS QPSK / DVB BPSK (0x0887) + +``` +CODE:0887 CLR 0x04 ; Clear _0_4 flag (non-turbo indicator) +CODE:0889 MOV A, 0x4F ; FEC index +CODE:088C SUBB A, #0x07 ; Max 7 FEC rates +CODE:088E JNC 0x08A4 ; Out of range -> use default +; In range: +CODE:0890 XRAM[0xE0EB] = XRAM[0xE0F9 + FEC_index] | 0x80 ; FEC with bit 7 set +; Out of range: +CODE:08A4 XRAM[0xE0EB] = 0x8C ; Default FEC value +; Common: +CODE:08AA XRAM[0xE0EC] = 0x09 ; Mod type register +CODE:08B1 XRAM[0xE0F6] = 0x00 ; Turbo flag = OFF +; Falls through to 0x093C (same as DVB-S QPSK) +``` + +#### Modulation 1: Turbo QPSK (0x08DF) + +``` +CODE:08DF MOV A, 0x4F ; FEC index +CODE:08E2 SUBB A, #0x05 ; Max 5 Turbo QPSK FEC rates +CODE:08E4 JNC 0x08F8 ; Out of range -> error +CODE:08E6 XRAM[0xE0EB] = XRAM[0xE0B7 + FEC_index] ; Turbo QPSK FEC table +; Falls through to 0x0930: +CODE:0930 XRAM[0xE0EC] = 0x09 ; Mod type +CODE:0936 XRAM[0xE0F6] = 0x01 ; Turbo flag = ON +; Falls through to 0x093C: +CODE:093C XRAM[0xE0F5] = 0x10 ; Demod mode = Turbo +CODE:0942 IRAM[0x4E] &= 0xBF ; Clear bmDCtuned +``` + +#### Modulation 2: Turbo 8PSK (0x08FA) + +``` +CODE:08FA MOV A, 0x4F ; FEC index +CODE:08FD SUBB A, #0x05 ; Max 5 Turbo 8PSK FEC rates +CODE:08FF JNC 0x0913 ; Out of range -> error +CODE:0901 XRAM[0xE0EB] = XRAM[0xE0B1 + FEC_index] ; Turbo 8PSK FEC table +; Falls through to 0x0930 (same as Turbo QPSK) +``` + +#### Modulation 3: Turbo 16QAM (0x0915) + +``` +CODE:0915 MOV A, 0x4F ; FEC index +CODE:0918 SUBB A, #0x01 ; Max 1 Turbo 16QAM FEC rate +CODE:091A JNC 0x092E ; Out of range -> error +CODE:091C XRAM[0xE0EB] = XRAM[0xE0BC + FEC_index] ; Turbo 16QAM FEC table +; Falls through to 0x0930 (same as Turbo QPSK/8PSK) +``` + +#### Modulations 4-7: Digicipher II Variants (0x0947-0x095F) + +All four DCII handlers share common post-processing but set different demod modes: + +``` +; Mod 4: DCII Combo (0x0947) +CODE:0947 XRAM[0xE0F5] = 0x10 ; Demod mode = DCII Combo +CODE:094D SJMP 0x0965 ; -> common DCII handler + +; Mod 5: DCII I-stream (0x094F) +CODE:094F XRAM[0xE0F5] = 0x12 ; Demod mode = DCII I-stream (split) +CODE:0955 SJMP 0x0965 + +; Mod 6: DCII Q-stream (0x0957) +CODE:0957 XRAM[0xE0F5] = 0x16 ; Demod mode = DCII Q-stream (split) +CODE:095D SJMP 0x0965 + +; Mod 7: DCII Offset QPSK (0x095F) +CODE:095F XRAM[0xE0F5] = 0x11 ; Demod mode = DCII Offset QPSK +CODE:0964 ; Falls through to 0x0965 + +; Common DCII handler (0x0965): +CODE:0965 MOV A, 0x4F ; FEC index +CODE:0968 SUBB A, #0x09 ; Max 9 DCII FEC rates +CODE:096A JNC 0x097E ; Out of range -> error +CODE:096C XRAM[0xE0EC] = XRAM[0xE0BD + FEC_index] ; DCII FEC/mod table +CODE:0980 XRAM[0xE0EB] = 0xFC ; Fixed DCII FEC code +CODE:0987 XRAM[0xE0F6] = 0x00 ; Turbo flag = OFF +CODE:098B IRAM[0x4E] |= 0x40 ; SET bmDCtuned flag +``` + +### 3.3 BCM4500 XRAM Configuration Summary + +After modulation dispatch completes, four XRAM registers hold the BCM4500 configuration: + +| XRAM Address | Register Name | DVB-S | Turbo QPSK/8PSK/16QAM | DCII | DSS/BPSK | +|-------------|---------------|-------|----------------------|------|----------| +| 0xE0EB | FEC Code Rate | Lookup from 0xE0F9+i | Lookup from tables | 0xFC (fixed) | Lookup | 0x80 from 0xE0F9+i | +| 0xE0EC | Modulation Type | 0x09 | 0x09 | From 0xE0BD+i table | 0x09 | +| 0xE0F5 | Demod Mode | 0x10 | 0x10 | 0x10/0x11/0x12/0x16 | 0x10 | +| 0xE0F6 | Turbo Flag | 0x00 | 0x01 | 0x00 | 0x00 | + +### 3.4 FEC Rate Lookup Tables + +The FEC lookup tables are populated at boot from the init data table in CODE space, copied to XRAM during startup: + +| Table Base | Modulation | Max Index | FEC Rates | +|-----------|------------|-----------|-----------| +| XRAM 0xE0F9 | DVB-S QPSK / DSS / BPSK | 7 | Standard Viterbi rates (1/2, 2/3, 3/4, 5/6, 7/8, auto, none) | +| XRAM 0xE0B7 | Turbo QPSK | 5 | Turbo QPSK code rates | +| XRAM 0xE0B1 | Turbo 8PSK | 5 | Turbo 8PSK code rates | +| XRAM 0xE0BC | Turbo 16QAM | 1 | Single 16QAM code rate | +| XRAM 0xE0BD | DCII (all variants) | 9 | DCII code rate + modulation combined | + +--- + +## 4. BCM4500 I2C Write Sequence + +### 4.1 Post-Dispatch: DVB Mode and I2C Programming + +After the modulation dispatch sets XRAM configuration registers, the tune function enters the common post-processing path at CODE:098E: + +``` +; Copy _0_4 flag to _0_5 (DVB/non-DVB indicator) +CODE:098E MOV CY, 0x04 ; Read _0_4 flag +CODE:0990 MOV 0x05, CY ; Copy to _0_5 + +; Configure DVB mode pin +CODE:0992 LCALL 0x21D3 ; FUN_CODE_21d3: SET_DVB_MODE + ; Sets P3.6 based on _0_4 + +; Attempt I2C programming (up to 3 tries) +CODE:0995 MOV 0x3B, #0x03 ; Retry counter = 3 +CODE:0998 MOV A, 0x3B +CODE:099A JZ 0x09A7 ; If 0 retries left, return failure +CODE:099C LCALL 0x1DD0 ; FUN_CODE_1dd0: Demod I2C write sequence +CODE:099F JC 0x09A3 ; If carry set (success), return +CODE:09A1 SETB CY ; Set carry = success +CODE:09A2 RET +CODE:09A3 DEC 0x3B ; Decrement retry counter +CODE:09A5 SJMP 0x0998 ; Try again +CODE:09A7 CLR CY ; Clear carry = failure +CODE:09A8 RET +``` + +### 4.2 FUN_CODE_1DD0: Demod Scan (3 I2C Address Attempts) + +The demod scan function tries to program the BCM4500 at up to three different I2C device addresses. This supports hardware variants where the BCM4500 may appear at different I2C addresses: + +```c +// Pseudocode for FUN_CODE_1dd0 (Rev.2 v2.10.4) +char demod_scan(void) { + for (DAT_INTMEM_3c = 0; DAT_INTMEM_3c < 3; DAT_INTMEM_3c++) { + // Compute I2C parameters from iteration index + // The multiplication by 0x11 and offset calculations produce: + // Iteration 0: device addr derived, register set A + // Iteration 1: device addr derived, register set B + // Iteration 2: device addr derived, register set C + result = FUN_CODE_1670(buf_addr, device_param, data_len); + if (result == success) { + return success; // Carry set + } + } + return failure; // Carry clear +} +``` + +### 4.3 FUN_CODE_1670: BCM4500 Indirect Register Write + +This is the core I2C programming function. The BCM4500 uses an indirect register access protocol through three I2C-accessible registers: + +``` +BCM4500 I2C Registers (accessed at device address 0x10): + 0xA6 = Page/Address register (indirect address high byte) + 0xA7 = Data register (indirect data) + 0xA8 = Command register (indirect command: 0x03 = write) +``` + +**FUN_CODE_1670 decompiled (Rev.2 v2.10.4):** + +```c +void bcm4500_indirect_write(byte buf_hi, byte device_param, byte data_count) { + // Step 1: Wait for BCM4500 ready (poll regs 0xA2, 0xA8, 0xA4) + FUN_CODE_1e73(); // 3-register bus wait + if (!carry) return; // BCM4500 not ready + + // Step 2: Write page address (0x00) to register 0xA6 + XRAM[0xE111] = 0x00; // Page = 0 + IRAM[0x45] = 0xE1; // Buffer pointer high + IRAM[0x46] = 0x11; // Buffer pointer low -> XRAM 0xE111 + FUN_CODE_136c(1, 0, 0xA6, 0x10); // I2C write 1 byte to device 0x10, reg 0xA6 + + // Step 3: Write data to register 0xA7 + IRAM[0x45] = buf_hi; // Source buffer high byte + IRAM[0x46] = device_param; // Source buffer low byte + FUN_CODE_136c(data_count, 0, 0xA7, 0x10); // I2C write N bytes to reg 0xA7 + + // Step 4: Write command (0x03 = indirect write) to register 0xA8 + XRAM[0xE111] = 0x03; // Command = write + IRAM[0x45] = 0xE1; + IRAM[0x46] = 0x11; + FUN_CODE_136c(1, 0, 0xA8, 0x10); // I2C write 1 byte to reg 0xA8 + + // Step 5: Wait for write completion (poll regs 0xA8, 0xA2) + FUN_CODE_1ea9(); + + // Step 6: Verify -- read back from 0xA7 and compare + XRAM[0xE111] = 0x00; + FUN_CODE_136c(1, 0, 0xA6, 0x10); // Re-select page 0 + FUN_CODE_20cb(); // Read reg 0xA7 + // Compare read-back with expected value +} +``` + +### 4.4 FUN_CODE_136C: I2C Multi-Byte Write Primitive + +This is the lowest-level I2C write function that talks to the FX2's I2C controller: + +```c +// Pseudocode for FUN_CODE_136c (Rev.2 v2.10.4) +void i2c_write(byte byte_count, byte flags, byte register_addr, byte device_addr) { + // Setup I2C controller at XRAM 0xE678 (FX2 I2C register) + FUN_CODE_2000(); // Wait for I2C bus ready + FUN_CODE_2206(device_addr << 1); // Send I2C start + device address (write) + FUN_CODE_2224(register_addr); // Send register address + + // Transfer data bytes from buffer at IRAM[0x45:0x46] + for (i = 0; i < byte_count; i++) { + byte data = XRAM[IRAM[0x45]:IRAM[0x46] + i]; + FUN_CODE_2224(data); // Send each data byte + } + + // Send I2C stop condition + FUN_CODE_1aa3(); // I2C stop + cleanup +} +``` + +### 4.5 Complete I2C Register Write Sequence for a Tune Operation + +Combining all the above, a complete tune operation produces the following I2C bus transactions: + +``` +=== Phase 1: Pre-Tune LNB/Tone Configuration === +(These happen before TUNE_8PSK, via separate vendor commands) + +1. SET_LNB_VOLTAGE (0x8B): GPIO P0.4 high/low (no I2C) +2. SET_22KHZ_TONE (0x8C): GPIO P0.3 high/low (no I2C) + +=== Phase 2: Tune Parameter Setup === +(EP0BUF parsing -- no I2C, just XRAM writes) + +3. EP0BUF[8] -> IRAM[0x4D] (modulation) +4. EP0BUF[9] -> IRAM[0x4F] (FEC rate) +5. EP0BUF[4-7] -> XRAM[0xE0DB-0xE0DE] (frequency, byte-reversed) +6. EP0BUF[0-3] -> XRAM[0xE0CB-0xE0CE] (symbol rate, byte-reversed) + +=== Phase 3: Modulation Dispatch === +(XRAM configuration writes -- no I2C) + +7. XRAM[0xE0EB] = FEC lookup value (from modulation-specific table) +8. XRAM[0xE0EC] = modulation type (usually 0x09) +9. XRAM[0xE0F5] = demod mode (0x10/0x11/0x12/0x16) +10. XRAM[0xE0F6] = turbo flag (0x00 or 0x01) + +=== Phase 4: DVB Mode GPIO === + +11. P3.6 set/clear based on _0_4 flag (DVB vs non-DVB mode) + +=== Phase 5: BCM4500 I2C Programming (x3 retry, x3 addresses) === + +For each attempt (up to 3 retries, each trying up to 3 I2C addresses): + + --- Wait for BCM4500 ready --- + 12. I2C READ device 0x10, reg 0xA2 (poll status register) + 13. I2C READ device 0x10, reg 0xA8 (poll command register) + 14. I2C READ device 0x10, reg 0xA4 (poll lock/ready register) + + --- Write page address --- + 15. I2C WRITE device 0x10, reg 0xA6 <- 0x00 (select page 0) + + --- Write configuration data --- + 16. I2C WRITE device 0x10, reg 0xA7 <- [N config bytes from XRAM buffer] + Data includes: frequency, symbol rate, FEC, modulation, demod mode, + turbo flag -- assembled from XRAM 0xE0EB/EC/F5/F6 and 0xE0CB-CE/DB-DE + + --- Issue indirect write command --- + 17. I2C WRITE device 0x10, reg 0xA8 <- 0x03 (execute indirect write) + + --- Wait for completion --- + 18. I2C READ device 0x10, reg 0xA8 (poll for command done) + 19. I2C READ device 0x10, reg 0xA2 (verify status) + + --- Verify write --- + 20. I2C WRITE device 0x10, reg 0xA6 <- 0x00 (re-select page) + 21. I2C READ device 0x10, reg 0xA7 (read back data) + 22. Compare read-back with expected value +``` + +### 4.6 I2C Device Address Note + +The BCM4500 demodulator is accessed at I2C device address **0x10** (7-bit address, shifted to 0x20 for write, 0x21 for read on the wire). In some firmware versions, the device also responds at alternate addresses: +- 0x3F (used by v2.13 for status polling in INT0) +- 0x7F (used by v2.13 for status polling in INT0) + +The use of 0x3F and 0x7F in v2.13's INT0 handler (demod availability probing) suggests these may be alternate I2C addresses on different hardware revisions, or they may access different internal register banks of the BCM4500. + +--- + +## 5. Signal Acquisition + +### 5.1 GET_SIGNAL_LOCK (Vendor Command 0x90) + +**Jump table targets:** +- v2.06: 0x020B +- v2.13: 0x022D +- Rev.2: 0x0217 + +The signal lock handler reads BCM4500 register 0xA4 and returns the result to the host via EP0BUF. + +**Rev.2 implementation chain:** + +``` +; Vendor command handler at 0x0217: +CODE:0217 MOV A, 0x4E ; Config status byte +CODE:0219 JNB ACC.0, ... ; Check device started +CODE:021C LCALL 0x2236 ; FUN_CODE_2236: Read lock register + +; FUN_CODE_2236: +CODE:2236 MOV R7, #0xA4 ; Register = 0xA4 +CODE:2238 LCALL 0x20CB ; FUN_CODE_20cb: I2C read + +; FUN_CODE_20cb: I2C single register read +; Sets up buffer at XRAM 0xE114 +; Calls FUN_CODE_0f00(device=0x0F, offset=0, reg=param, addr=0x10) +; Returns byte from XRAM[0xE114] +``` + +**I2C transaction:** +``` +I2C READ device 0x10, register 0xA4, 1 byte -> XRAM[0xE114] +``` + +**Register 0xA4 bit fields:** +``` +Bit 5 (0x20): Signal locked (demod has achieved lock) +``` + +The firmware returns the raw register byte to EP0BUF. The host driver (both Linux and Windows) interprets any non-zero value as "locked": + +```c +// Linux kernel driver (gp8psk-fe.c): +gp8psk_usb_in_op(d, GET_SIGNAL_LOCK, 0, 0, &lock, 1); +if (lock) + *status = FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI | + FE_HAS_SIGNAL | FE_HAS_CARRIER; + +// Windows BDA driver (SkyWalker1Control.cpp): +ControlUsbDevice(pKSDeviceObject, GET_SIGNAL_LOCK, 0, 0, &ucSignalStatus, 1, true); +if (ucSignalStatus) + *pbSignalLockStatus = TRUE; +``` + +### 5.2 GET_SIGNAL_STRENGTH (Vendor Command 0x87) + +**Jump table targets:** +- v2.06: 0x0140 +- v2.13: 0x0162 +- Rev.2: 0x014C + +This command returns 6 bytes of signal quality data. The first two bytes contain the SNR value. + +**Rev.2 implementation (FUN_CODE_15eb at 0x15EB):** + +The signal strength reader performs a more complex sequence than simple lock detection: + +1. Writes configuration to XRAM buffer via FUN_CODE_067e (multi-mode data access) +2. Performs BCM4500 indirect register write via FUN_CODE_1670 (same protocol as tuning) +3. Reads back BCM4500 register 0xA7 via FUN_CODE_0f00 +4. Compares write value with read-back for validity check +5. If valid, copies 6 bytes of signal data to EP0BUF + +**I2C transactions during signal strength read:** +``` +1. I2C WRITE device 0x10, reg 0xA6 <- 0x00 (page select) +2. I2C WRITE device 0x10, reg 0xA7 <- [config] (request signal data) +3. I2C WRITE device 0x10, reg 0xA8 <- 0x03 (execute) +4. I2C READ device 0x10, reg 0xA7, 1 byte (verify/read) +5. Copy 6-byte result buffer to EP0BUF +``` + +**EP0BUF response format (6 bytes):** +``` +Byte 0: SNR low byte (LSB) +Byte 1: SNR high byte (MSB) +Bytes 2-5: Reserved/diagnostic (BCM4500 internal registers) +``` + +**SNR scaling (from Windows BDA driver):** +```c +ulSignalStrength = (int)(ucBuffer[1]) << 8 | ucBuffer[0]; +// SNR is in dBu * 256 units +// SNR * 17 maps to 0-65535 range (100% at SNR >= 0x0F00) +if (ulSignalStrength <= 0x0F00) + *pulSigStrength = (ulSignalStrength << 4) + ulSignalStrength; // * 17 +else + *pulSigStrength = 0xFFFF; +``` + +### 5.3 Differences Between Firmware Versions + +**v2.06 GET_SIGNAL_STRENGTH (0x0140):** +- Checks IRAM[0x6D] bit 0 (demod active) +- Reads three I2C status registers (0xA2, 0xA8, 0xA4) to compute quality +- Loops up to 6 iterations polling for demod readiness +- Uses FUN_CODE_0c97 for I2C reads + +**v2.13 GET_SIGNAL_STRENGTH (0x0162):** +- Checks IRAM[0x4F] bit 0 (demod active) +- Uses different function call chain (FUN_CODE_1278 vs v2.06's FUN_CODE_0c97) +- Same overall logic with relocated internal variables +- Simplified polling (uses consolidated BCM4500 status register) + +**Rev.2 GET_SIGNAL_STRENGTH (0x014C -> FUN_CODE_15eb):** +- Checks IRAM[0x4E] bit 0 +- Uses FUN_CODE_067e for multi-mode data access +- Validates read-back against written configuration +- Most granular implementation with explicit verify step + +--- + +## 6. LNB and Tone Control + +### 6.1 SET_LNB_VOLTAGE (Vendor Command 0x8B) + +**Jump table targets:** +- v2.06: 0x01CB +- v2.13: 0x01ED +- Rev.2: 0x01D7 + +LNB voltage selection is pure GPIO -- no I2C transactions. The FX2 pin P0.4 directly controls a voltage regulator on the PCB. + +**Rev.2 FUN_CODE_21b1 decompiled:** +```c +void set_lnb_voltage(void) { + if (_0_4 != 0) { + // wValue = 1: Set 18V (horizontal / circular-left polarization) + P0 |= 0x10; // P0.4 = HIGH -> 18V + DAT_INTMEM_4e |= 0x20; // Set bmSEL18V in config status + } else { + // wValue = 0: Set 13V (vertical / circular-right polarization) + P0 &= 0xEF; // P0.4 = LOW -> 13V + DAT_INTMEM_4e &= 0xDF; // Clear bmSEL18V in config status + } +} +``` + +**Pin mapping across versions:** + +| Version | 18V Pin | Config Status Bit | +|---------|---------|------------------| +| v2.06 | P0.4 | IRAM[0x6D] bit 5 | +| v2.13 | P0.4 | IRAM[0x4F] bit 5 | +| Rev.2 | P0.4 | IRAM[0x4E] bit 5 | + +The `wValue` parameter from the USB SETUP packet is passed via the `_0_4` bit flag (IRAM bit-addressable area). The Windows driver sends: +```c +ControlUsbDevice(device, SET_LNB_VOLTAGE, (ucVoltage == SEC_VOLTAGE_18), 0, NULL, 0); +``` + +**Polarization mapping:** +- Horizontal / Circular-Left: 18V (wValue=1) +- Vertical / Circular-Right: 13V (wValue=0) + +### 6.2 SET_22KHZ_TONE (Vendor Command 0x8C) + +**Jump table targets:** +- v2.06: 0x01DD +- v2.13: 0x01FF +- Rev.2: 0x01E9 + +Like LNB voltage, the 22 kHz tone is pure GPIO. P0.3 enables/disables an external 22 kHz oscillator on the PCB. + +**Rev.2 FUN_CODE_21c2 decompiled:** +```c +void set_22khz_tone(void) { + if (_0_4 != 0) { + // wValue = 1: Tone ON (high band) + P0 |= 0x08; // P0.3 = HIGH -> 22 kHz oscillator enabled + DAT_INTMEM_4e |= 0x10; // Set bm22kHz in config status + } else { + // wValue = 0: Tone OFF (low band) + P0 &= 0xF7; // P0.3 = LOW -> 22 kHz oscillator disabled + DAT_INTMEM_4e &= 0xEF; // Clear bm22kHz in config status + } +} +``` + +**Pin mapping across versions:** + +| Version | Tone Pin | Config Status Bit | +|---------|----------|------------------| +| v2.06 | P0.3 | IRAM[0x6D] bit 4 | +| v2.13 | P0.3 | IRAM[0x4F] bit 4 | +| Rev.2 | P0.3 | IRAM[0x4E] bit 4 | + +The 22 kHz tone is used for satellite band selection: +- **Tone ON**: Select high-band LNB oscillator (universal LNB: 10.6 GHz LO) +- **Tone OFF**: Select low-band LNB oscillator (universal LNB: 9.75 GHz LO) + +**Windows driver tone logic:** +```c +// SEC_TONE_ON = 0, SEC_TONE_OFF = 1 +ControlUsbDevice(device, SET_22KHZ_TONE, (ucTone == 0), 0, NULL, 0); +``` + +Note the inverted logic: `SEC_TONE_ON = 0` results in `wValue = 1` (tone active). + +### 6.3 ConfigureTuner: Full Tuning Sequence (Windows BDA Driver) + +The Windows driver's `ConfigureTuner()` function shows the complete host-side tuning sequence: + +```c +NTSTATUS ConfigureTuner(PKSDEVICE device, PBDATUNER_DEVICE_PARAMETER config) { + // Step 1: Set LNB voltage based on polarization + if (config->Polarity == BDA_POLARISATION_LINEAR_H || + config->Polarity == BDA_POLARISATION_CIRCULAR_L) { + SetLnbVoltage(device, SEC_VOLTAGE_18); // 0x8B, wValue=1 + } else { + SetLnbVoltage(device, SEC_VOLTAGE_13); // 0x8B, wValue=0 + } + + // Step 2: Disable tone during tune + SetTunerTone(device, SEC_TONE_OFF); // 0x8C, wValue=0 + + // Step 3: Send tune command + TuneDevice(device, config); // 0x86, 10 bytes +} +``` + +--- + +## 7. GPIO Pin Summary + +All LNB, tone, and DVB mode control is performed through direct GPIO manipulation with no I2C involvement: + +``` +FX2 Port 0 Pin Assignments (Rev.2 v2.10.4): + P0.0 -- (unused in Rev.2; DiSEqC data in v2.13) + P0.1 -- (unused) + P0.2 -- (set during init, purpose TBD) + P0.3 -- 22 kHz tone oscillator enable (all versions) + P0.4 -- LNB 13V/18V voltage select (all versions) + Also DiSEqC data pin in Rev.2 + P0.5 -- (unused) + P0.6 -- GPIO control (FUN_CODE_1fcf) + P0.7 -- DiSEqC data pin (v2.06 only) + +FX2 Port 3 Pin Assignments (Rev.2 v2.10.4): + P3.4 -- GPIO control (FUN_CODE_1fcf) + P3.6 -- DVB mode select (SET_DVB_MODE, FUN_CODE_21d3) +``` + +--- + +## 8. Cross-Version Comparison + +### 8.1 Tune Function Correspondence + +| Component | v2.06 | v2.13 | Rev.2 v2.10.4 | +|-----------|-------|-------|---------------| +| Vendor dispatch | 0x0056 | 0x0056 | 0x0056 | +| TUNE_8PSK handler | 0x012E | 0x012E | 0x0118 | +| EP0 flush | 0x23E0 | 0x231E | 0x2167 | +| Main tune function | 0x0800 | 0x09DF | 0x0800 | +| Config status byte | IRAM 0x6D | IRAM 0x4F | IRAM 0x4E | +| Modulation storage | IRAM 0x4D* | IRAM 0x4D | IRAM 0x4D | +| FEC storage | IRAM 0x4F* | IRAM 0x4F | IRAM 0x4F | +| Mod dispatch table | 0x0873* | embedded in 0x09DF | 0x0873 | +| BCM4500 indirect write | similar | FUN_CODE_15b8 chain | FUN_CODE_1670 | +| I2C write primitive | FUN_CODE_1556 | FUN_CODE_15b8 | FUN_CODE_136c | +| Signal lock read | 0x020B | 0x022D | 0x0217 -> FUN_CODE_2236 | +| Signal strength | 0x0140 | 0x0162 | 0x014C -> FUN_CODE_15eb | +| LNB voltage | 0x01CB | 0x01ED | 0x01D7 -> FUN_CODE_21b1 | +| 22 kHz tone | 0x01DD | 0x01FF | 0x01E9 -> FUN_CODE_21c2 | + +*v2.06 shares the same IRAM layout as Rev.2 for modulation/FEC but uses different XRAM offsets. + +### 8.2 Key Architectural Differences + +**v2.06:** +- Simplest implementation, single code path +- No demod verification or retry logic during tune +- BCM4500 status polling reads 3 registers (0xA2, 0xA8, 0xA4) +- Signal strength handler loops up to 6 times + +**Rev.2 v2.10.4:** +- 3 I2C address attempts per tune (FUN_CODE_1dd0) +- 3 outer retries for the whole demod scan +- BCM4500 indirect write with read-back verification (FUN_CODE_1670) +- Most granular function decomposition +- Smallest binary despite highest function count + +**v2.13:** +- Adds host-controlled DELAY_COMMAND (0x9C) for tuning timing +- Adds INIT_DEMOD (0x9A) for host-triggered re-initialization +- Adds GET_DEMOD_STATUS (0x99) for diagnostic register reads +- INT0 repurposed for demod availability polling +- Most robust error recovery (20-attempt retry loops for init) + +--- + +## 9. Data Flow Diagram + +``` +Host Application (DVB app / szap / w_scan) + | + v +Linux dvb-usb-gp8psk / Windows BDA Driver + | + | USB Control Transfer (EP0) + | bmRequestType = 0x40 (Vendor OUT) + | bRequest = 0x86 (TUNE_8PSK) + | wValue = 0, wIndex = 0, wLength = 10 + | Data: [SR0 SR1 SR2 SR3 F0 F1 F2 F3 MOD FEC] + | + v +FX2 Microcontroller (Cypress CY7C68013A) + | + | 1. EP0BUF (XRAM 0xE740-0xE749) receives 10 bytes + | 2. Parse: mod->IRAM[0x4D], fec->IRAM[0x4F] + | 3. Byte-reverse freq->XRAM[0xE0DB-DE], sr->XRAM[0xE0CB-CE] + | 4. Dispatch on mod type (jump table at CODE:0873) + | 5. Set XRAM[0xE0EB/EC/F5/F6] per modulation + | 6. GPIO: P3.6 for DVB mode + | + | I2C Bus (FX2 I2C controller at XRAM 0xE678) + | Device address: 0x10 (BCM4500) + | + v +BCM4500 Demodulator (Broadcom DVB-S/8PSK) + | + | Indirect register protocol: + | reg 0xA6 <- page (0x00) + | reg 0xA7 <- config data (freq, SR, FEC, mod params) + | reg 0xA8 <- command (0x03 = write) + | + | After programming: + | reg 0xA4 bit 5 = signal lock status + | reg 0xA7 = SNR / signal quality readback + | + v +RF Front End + | + | LNB Control (GPIO, no I2C): + | P0.4 = voltage (13V/18V) + | P0.3 = 22 kHz tone (band select) + | + v +Satellite LNB -> Dish -> Satellite +``` + +--- + +## Sources + +- Ghidra firmware disassembly: v2.06 (port 8193), v2.13 FW1 (port 8194), Rev.2 v2.10.4 (port 8197) +- Windows BDA driver source: `SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp` +- Windows BDA driver headers: `SkyWalker1_Final_Release/Include/SkyWalker1Control.h`, `SkyWalker1CommonDef.h` +- Linux kernel driver: `drivers/media/usb/dvb-usb/gp8psk.c`, `gp8psk.h`, `gp8psk-fe.c` +- Companion documents: `gp8psk-driver-analysis.md`, `firmware-analysis-v206-vs-v213.md`, `rev2-deep-analysis.md` diff --git a/vendor-commands-unknown.md b/vendor-commands-unknown.md index b17a06b..a220876 100644 --- a/vendor-commands-unknown.md +++ b/vendor-commands-unknown.md @@ -1,571 +1,571 @@ -# Genpix SkyWalker-1 Vendor Commands: Full Decode of "Unknown" Commands - -## Sources - -- Firmware disassembly via Ghidra MCP: v2.06 (port 8193), v2.13 FW1 (port 8194), Rev.2 v2.10.4 (port 8197) -- Linux kernel `gp8psk-fe.h` (kernel 4.9.227 and 6.18.6) -- provides definitive names for 0x8F, 0x92, 0x93, 0x94, 0x95 -- Linux kernel `gp8psk.c` and `gp8psk-fe.c` -- shows driver usage -- Windows driver `SkyWalker1Control.h` -- confirms 0x8F, 0x93, 0x94 - -## Jump Table Corrections - -The original addresses for Rev.2 were shifted by one command index. The corrected jump table targets (decoded from AJMP opcodes at CODE:0x0076) are: - -| Cmd | v2.06 (8193) | v2.13 (8194) | Rev.2 (8197) | -|------|-------------|-------------|-------------| -| 0x8F | 0x01FC | 0x021E | 0x0208 | -| 0x90 | 0x020B | 0x022D | 0x0217 | -| 0x91 | 0x022C | 0x024E | 0x0238 | -| 0x92 | 0x024A | 0x026C | 0x0256 | -| 0x93 | 0x026F | 0x0293 | 0x027D | -| 0x94 | 0x01B9 | 0x01DB | 0x01C5 | -| 0x95 | 0x02DF | 0x0303 | 0x02ED | -| 0x96 | 0x02B4 | 0x02D8 | 0x02C2 | -| 0x97 | 0x02C1 | 0x02E5 | 0x02CF | -| 0x98 | 0x02CB | 0x02EF | 0x02D9 | - -Note: v2.06 and v2.13 have 30 jump table entries (0x80-0x9D, range check `< 0x1E`). Rev.2 has only 27 entries (0x80-0x9A, range check `< 0x1B`). Commands 0x9B-0x9D do not exist on Rev.2 hardware. - ---- - -## Command 0x8F: SET_DN_SWITCH - -**Named by**: Linux kernel `gp8psk-fe.h`, Windows driver `SkyWalker1Control.h` - -| Field | Value | -|-------|-------| -| Direction | OUT (host-to-device) | -| wValue | Switch command byte (7 bits, LSB-first) | -| wIndex | 0x0000 | -| wLength | 0 (no data phase) | -| Purpose | Send legacy Dish Network switch command via GPIO bit-bang | - -### Firmware Behavior - -All three versions implement the same algorithm: - -1. Read `wValueL` from SETUPDAT[2] (0xE6BA) -2. Store value, then call a GPIO bit-bang subroutine: - - Assert P0.4 high (start pulse) - - Delay ~32 cycles - - Deassert P0.4 - - Delay ~8 cycles - - Loop 7 times, shifting out each bit LSB-first via P0.4 - - Between bits: delay ~8 cycles -3. ACK with EP0BCL = 0 (no data returned) - -### Subroutine Addresses - -| Version | Handler | Bit-bang Routine | Delay Routine | -|---------|---------|-----------------|---------------| -| v2.06 | 0x01FC | 0x1F04 | 0x1DFB | -| v2.13 | 0x021E | 0x1ECD | 0x14B9 | -| Rev.2 | 0x0208 | 0x1CE6 | 0x1BDA | - -### Linux Driver Usage - -```c -// gp8psk-fe.c: gp8psk_fe_send_legacy_dish_cmd() -u8 cmd = sw_cmd & 0x7f; -st->ops->out(st->priv, SET_DN_SWITCH, cmd, 0, NULL, 0); -// Then sets LNB voltage based on bit 7 of sw_cmd -st->ops->out(st->priv, SET_LNB_VOLTAGE, !!(sw_cmd & 0x80), 0, NULL, 0); -``` - -This is the `dishnetwork_send_legacy_command` callback in the DVB frontend ops. The 7-bit command value is a legacy Dish Network satellite switch protocol word, bit-banged on GPIO P0.4 with specific timing. The 8th bit (0x80) of the original command selects LNB voltage (13V or 18V) and is sent separately. - -### Cross-Version Differences - -None. All three versions use identical logic (same GPIO pin P0.4, same timing, same 7-bit protocol). Only the subroutine addresses differ due to code relocation. - ---- - -## Command 0x91: I2C_ADDR_ADJUST (internal debug) - -**Not named in any driver header.** This command is not used by either the Linux or Windows driver. - -| Field | Value | -|-------|-------| -| Direction | IN (device-to-host) | -| wValue | 0 = decrement, non-zero = increment | -| wIndex | 0x0000 | -| wLength | 1 byte | -| Returns | Current value of internal counter byte | -| Purpose | Increment/decrement an internal IRAM counter; return its value | - -### Firmware Behavior - -``` -if wValueL != 0: - IRAM[counter]++ -else: - IRAM[counter]-- -EP0BUF[0] = IRAM[counter] -EP0BCL = 1 -``` - -### Counter IRAM Addresses - -| Version | IRAM Address | -|---------|-------------| -| v2.06 | 0x66 | -| v2.13 | 0x18 | -| Rev.2 | 0x18 | - -### Purpose - -This appears to be a diagnostic/debug command for adjusting an internal pointer or counter, possibly related to I2C bus addressing or tuner register indexing. The counter is used by other internal routines but is not exposed through any standard driver interface. The name "I2C_ADDR_ADJUST" is inferred from its proximity to I2C commands and the inc/dec readback pattern. - -### Cross-Version Differences - -- v2.06 uses IRAM address 0x66; v2.13 and Rev.2 use 0x18 -- Logic is otherwise identical - ---- - -## Command 0x92: GET_FW_VERS - -**Named by**: Linux kernel `gp8psk-fe.h` (`#define GET_FW_VERS 0x92`) - -| Field | Value | -|-------|-------| -| Direction | IN (device-to-host) | -| wValue | 0x0000 | -| wIndex | 0x0000 | -| wLength | 6 bytes | -| Returns | Firmware version and build date (6 bytes, fixed per firmware build) | -| Purpose | Read firmware version identifier | - -### Data Format - -``` -Byte 0: Version minor-minor (fw_vers[0]) -Byte 1: Version minor (fw_vers[1]) -Byte 2: Version major (fw_vers[2]) -Byte 3: Build day -Byte 4: Build month -Byte 5: Build year (offset from 2000) -``` - -Full version number: `fw_vers[2] << 16 | fw_vers[1] << 8 | fw_vers[0]` -Build date: `(2000 + fw_vers[5]) / fw_vers[4] / fw_vers[3]` - -### Values Per Firmware - -| Version | Raw Bytes | Version | Build Date | FW_VERS macro | -|---------|-----------|---------|-----------|---------------| -| v2.06 | `04 06 02 0D 07 07` | 2.06.04 | 2007-07-13 | 0x020604 (= GP8PSK_FW_REV1) | -| v2.13 | `01 0D 02 0C 03 0A` | 2.13.01 | 2010-03-12 | 0x020D01 | -| Rev.2 | `04 0A 02 0C 03 0A` | 2.10.04 | 2010-03-12 | 0x020A04 (>= GP8PSK_FW_REV2) | - -### Linux Driver Usage - -```c -// gp8psk.c: gp8psk_get_fw_version() -gp8psk_usb_in_op(d, GET_FW_VERS, 0, 0, fw_vers, 6); - -// gp8psk.c: gp8psk_info() -- prints on boot: -// "FW Version = 2.06.4 (0x20604) Build 2007/07/13" - -// gp8psk-fe.h: Used for hardware revision detection: -#define GP8PSK_FW_REV1 0x020604 -#define GP8PSK_FW_REV2 0x020704 -// if GP8PSK_FW_VERS(fw) >= GP8PSK_FW_REV2 -> Rev.2 hardware -``` - -### Firmware Implementation - -The handler writes 6 hardcoded immediate values to EP0BUF[0..5] and sets EP0BCL = 6. There is no I2C or EEPROM access -- the version bytes are compiled directly into the firmware binary. - -### Cross-Version Differences - -Only the embedded constant values differ (they reflect each firmware's own version). The handler structure is identical across all three versions. - ---- - -## Command 0x93: GET_SERIAL_NUMBER - -**Named by**: Linux kernel `gp8psk-fe.h`, Windows driver `SkyWalker1Control.h` - -| Field | Value | -|-------|-------| -| Direction | IN (device-to-host) | -| wValue | 0x0000 | -| wIndex | 0x0000 | -| wLength | 4 bytes | -| Returns | 4-byte device serial number read from I2C EEPROM | -| Purpose | Read unique device serial number from onboard EEPROM | - -### Firmware Behavior - -1. Call EEPROM initialization routine (sets up I2C for device address 0x51) -2. Read byte at EEPROM bit-offset 0x08 -> EP0BUF[0] -3. Read byte at EEPROM bit-offset 0x10 -> EP0BUF[1] -4. Read byte at EEPROM bit-offset 0x18 -> EP0BUF[2] -5. Read byte at EEPROM bit-offset (from init) -> EP0BUF[3] -6. Set EP0BCL = 4 - -The I2C EEPROM at address 0x51 (7-bit) is a standard 24Cxx-family serial EEPROM. The serial number bytes are extracted at 8-bit intervals using a shift/rotate extraction routine. - -### Subroutine Addresses - -| Version | Handler | EEPROM Init | Byte Extract | -|---------|---------|------------|-------------| -| v2.06 | 0x026F | 0x1DA8 | 0x077C | -| v2.13 | 0x0293 | 0x1D4B | 0x078A | -| Rev.2 | 0x027D | 0x1A50 | 0x0798 | - -### Cross-Version Differences - -None in behavior. The EEPROM device address (0x51) and bit-offset scheme are identical. Only subroutine addresses differ. - ---- - -## Command 0x94: USE_EXTRA_VOLT - -**Named by**: Linux kernel `gp8psk-fe.h`, Windows driver `SkyWalker1Control.h` - -| Field | Value | -|-------|-------| -| Direction | OUT (host-to-device) in v2.06/v2.13; OUT with 1-byte ACK in Rev.2 | -| wValue | 0 = normal voltage (13V/18V), non-zero = high voltage (14V/19V) | -| wIndex | 0x0000 | -| wLength | 0 (v2.06/v2.13); 1 byte returned in Rev.2 | -| Purpose | Enable/disable +1V LNB voltage boost for long cable runs | - -### Firmware Behavior - -1. Read `wValueL` from SETUPDAT[2] -2. Compute carry flag: `CY = (wValueL >= 1)` via `ADD A,#0xFF` -3. Store carry to a bit-addressable IRAM flag -4. Call LNB voltage apply routine: - - If flag set: write 0x6A to XRAM 0xE0B6 (LNB control register) - - If flag clear: write 0x62 to XRAM 0xE0B6 -5. ACK (v2.06/v2.13: EP0BCL=0; Rev.2: returns 1 byte with result) - -The values 0x6A and 0x62 differ in bit 3 (0x08), which controls the extra voltage boost on the LNB power regulator IC. The register at XRAM 0xE0B6 is a hardware control register in the FX2's XRAM-mapped I/O space. - -### Flag Bit Addresses - -| Version | Bit Address | IRAM Byte.Bit | -|---------|------------|---------------| -| v2.06 | bit 0x07 | byte 0x20, bit 7 | -| v2.13 | bit 0x00 | byte 0x20, bit 0 | -| Rev.2 | bit 0x04 | byte 0x20, bit 4 | - -### Subroutine Addresses - -| Version | Handler | Voltage Apply | -|---------|---------|--------------| -| v2.06 | 0x01B9 | 0x2481 | -| v2.13 | 0x01DB | 0x23BF | -| Rev.2 | 0x01C5 | 0x21F5 | - -### Linux Driver Usage - -```c -// gp8psk-fe.c: gp8psk_fe_enable_high_lnb_voltage() -// Mapped to .enable_high_lnb_voltage frontend ops callback -st->ops->out(st->priv, USE_EXTRA_VOLT, onoff, 0, NULL, 0); -``` - -### Cross-Version Differences - -- v2.06/v2.13: Pure OUT command (EP0BCL=0, no data returned) -- **Rev.2**: Returns 1 byte (carry result from the voltage apply subroutine), making it partially an IN command. The Linux driver sends it as OUT and ignores any returned data, so this is backward-compatible. -- The bit-addressable flag location changes between versions but the functional behavior is identical. - ---- - -## Command 0x95: GET_FPGA_VERS - -**Named by**: Linux kernel `gp8psk-fe.h` (`#define GET_FPGA_VERS 0x95`) - -| Field | Value | -|-------|-------| -| Direction | IN (device-to-host) | -| wValue | 0x0000 | -| wIndex | 0x0000 | -| wLength | 1 byte (v2.13/Rev.2); 2 bytes (v2.06) | -| Returns | EEPROM-stored hardware/FPGA revision identifier | -| Purpose | Read hardware platform identifier from onboard EEPROM | - -### Firmware Behavior - -All versions read from the I2C EEPROM at device address 0x51, but the EEPROM offset and return size differ: - -**v2.06:** -1. Call EEPROM read at offset 0x31, read 2 bytes -2. Combine into 16-bit value (R7:R6) -3. Return 2 bytes in EP0BUF[0..1] - -**v2.13 and Rev.2:** -1. Call EEPROM read at offset 0x00, read 2 bytes -2. Compute a combined value -3. Return 1 byte in EP0BUF[0] - -### Subroutine Addresses - -| Version | Handler | EEPROM Read | -|---------|---------|------------| -| v2.06 | 0x02DF | 0x11DD | -| v2.13 | 0x0303 | 0x21AE | -| Rev.2 | 0x02ED | 0x0FDD | - -### Linux Driver Usage - -```c -// gp8psk.c: gp8psk_get_fpga_version() -gp8psk_usb_in_op(d, GET_FPGA_VERS, 0, 0, &fpga_vers, 1); - -// gp8psk.c: gp8psk_info() -- prints on boot: -// "FPGA Version = %i" -``` - -Despite the name "FPGA," this reads a version/ID byte from the I2C EEPROM. On these devices there is no separate FPGA -- the name is a legacy artifact from the Genpix product line where some models had an FPGA for signal processing. Here it returns the EEPROM-stored hardware platform identifier. - -### Cross-Version Differences - -- **v2.06**: Reads EEPROM offset 0x31, returns 2 bytes. The Linux driver only requests 1 byte, so it gets EP0BUF[0] (low byte of the 16-bit value). -- **v2.13/Rev.2**: Reads EEPROM offset 0x00, returns 1 byte. This is a cleaner implementation that matches what the driver actually requests. -- The change from offset 0x31 to offset 0x00 suggests the EEPROM layout was reorganized between Rev.1 and Rev.2 hardware. - ---- - -## Command 0x96: SET_LNB_GPIO_MODE (internal/debug) - -**Not named in any driver header.** This command is not used by either the Linux or Windows driver. - -| Field | Value | -|-------|-------| -| Direction | OUT (host-to-device) | -| wValue | 0 = default mode, non-zero = extra-volt pin mapping | -| wIndex | 0x0000 | -| wLength | 0 (no data phase) | -| Purpose | Configure LNB power supply GPIO output enable pins | - -### Firmware Behavior - -1. Read `wValueL`, compute carry flag (same pattern as USE_EXTRA_VOLT) -2. Store flag to bit-addressable IRAM location -3. Call GPIO configuration routine: - -**If flag set (v2.06/v2.13):** -``` -IOB = (IOB & 0xF7) | 0x06 ; clear IOB.3, set IOB.2 and IOB.1 -OEB = 0xFE ; enable IOB[7:1] as outputs, IOB.0 as input -``` - -**If flag clear (v2.06/v2.13):** -``` -OEB = 0xF0 ; enable IOB[7:4] as outputs, IOB[3:0] as inputs -``` - -### Flag Bit Addresses - -| Version | Bit Address | -|---------|------------| -| v2.06 | bit 0x07 | -| v2.13 | bit 0x04 | -| Rev.2 | bit 0x06 | - -### Subroutine Addresses - -| Version | Handler | GPIO Config | -|---------|---------|------------| -| v2.06 | 0x02B4 | 0x2406 | -| v2.13 | 0x02D8 | 0x2344 | -| Rev.2 | 0x02C2 | 0x20F9 | - -### Cross-Version Differences - -- **v2.06/v2.13**: Controls Port B (IOB/OEB) pins only. Clears IOB.3, sets IOB.2 and IOB.1 for voltage mode. -- **Rev.2**: Uses different GPIO pins reflecting the Rev.2 PCB layout: - - Clears IOB.4 (instead of IOB.3) - - Sets OEB with IOB.4 enabled - - Additionally sets Port A pins: P0.6 and P0.0 (via `ORL P0, #0x41` and `ORL OEA, #0x41`) - - Default mode: `OEB = 0xE7`, `OEA = 0x9E` - -This command configures the GPIO output enable registers that control the LNB voltage regulator hardware. It works in conjunction with USE_EXTRA_VOLT (0x94) and SET_LNB_VOLTAGE (0x8B) -- cmd 0x96 sets which pins are active outputs, while 0x8B and 0x94 set the pin states. - ---- - -## Command 0x97: SET_GPIO_PINS (internal/debug) - -**Not named in any driver header.** This command is not used by either the Linux or Windows driver. - -| Field | Value | -|-------|-------| -| Direction | OUT (host-to-device) | -| wValue | GPIO pin state bitmap | -| wIndex | 0x0000 | -| wLength | 0 (no data phase) | -| Purpose | Direct GPIO pin write for LNB/switch hardware control | - -### Firmware Behavior - -**v2.06/v2.13:** -``` -IOB = (IOB & 0xF1) | (wValueL & 0x0E) -``` -Clears IOB bits [3:1] and sets them to the corresponding bits from `wValueL`. This gives direct control over the 3 LNB-related GPIO pins on Port B. - -**Rev.2:** -Maps individual bits of `wValueL` to different GPIO pins: -``` -if wValueL.bit1: P0.6 = 1 else P0.6 = 0 ; Port A pin 6 -if wValueL.bit2: P0.0 = 1 else P0.0 = 0 ; Port A pin 0 -if wValueL.bit3: IOB.4 = 1 else IOB.4 = 0 ; Port B pin 4 -``` - -### Subroutine Addresses - -| Version | Handler | GPIO Write | -|---------|---------|-----------| -| v2.06 | 0x02C1 | 0x24BE | -| v2.13 | 0x02E5 | 0x2429 | -| Rev.2 | 0x02CF | 0x1FCF | - -### wValue Bit Mapping - -**v2.06/v2.13 (Port B bulk write):** -| wValue Bit | GPIO Pin | Function | -|-----------|---------|----------| -| bit 1 | IOB.1 | LNB regulator control line 1 | -| bit 2 | IOB.2 | LNB regulator control line 2 | -| bit 3 | IOB.3 | LNB regulator control line 3 | - -**Rev.2 (individual pin mapping):** -| wValue Bit | GPIO Pin | Function | -|-----------|---------|----------| -| bit 1 | P0.6 (Port A) | LNB control line | -| bit 2 | P0.0 (Port A) | LNB control line | -| bit 3 | IOB.4 (Port B) | LNB control line | - -### Cross-Version Differences - -Major hardware difference: v2.06/v2.13 use a simple Port B mask write, while Rev.2 maps each bit to different pins on two different ports (A and B). This reflects the Rev.2 PCB redesign that moved LNB control circuitry to different FX2 GPIO pins. - ---- - -## Command 0x98: GET_GPIO_STATUS (internal/debug) - -**Not named in any driver header.** This command is not used by either the Linux or Windows driver. - -| Field | Value | -|-------|-------| -| Direction | IN (device-to-host) | -| wValue | 0x0000 | -| wIndex | 0x0000 | -| wLength | 1 byte | -| Returns | GPIO input pin state (0 or 1) | -| Purpose | Read LNB-related GPIO input pin state | - -### Firmware Behavior - -**v2.06/v2.13:** -``` -A = IOB & 0x01 ; read Port B bit 0 -EP0BUF[0] = A ; return 0 or 1 -EP0BCL = 1 -``` - -**Rev.2:** -``` -A = P0 ; read Port A -if P0.5 == 1: - R7 = 1 -else: - R7 = 0 -EP0BUF[0] = R7 -EP0BCL = 1 -``` - -### Subroutine Addresses - -| Version | Handler | GPIO Read | -|---------|---------|----------| -| v2.06 | 0x02CB | 0x24CC | -| v2.13 | 0x02EF | 0x2437 | -| Rev.2 | 0x02D9 | 0x0046 | - -### GPIO Pin Read - -| Version | Pin | SFR | -|---------|-----|-----| -| v2.06 | IOB.0 (Port B bit 0) | SFR 0xB0, bit 0 | -| v2.13 | IOB.0 (Port B bit 0) | SFR 0xB0, bit 0 | -| Rev.2 | P0.5 (Port A bit 5) | SFR 0x80, bit 5 | - -### Purpose - -This reads a single GPIO input pin, likely a feedback/status signal from the LNB power supply circuitry (overcurrent detect, power-good, or similar). The pin assignment changed with the Rev.2 PCB redesign. - -### Cross-Version Differences - -- v2.06/v2.13: Reads Port B pin 0 (IOB.0) via mask `& 0x01` -- Rev.2: Reads Port A pin 5 (P0.5) via bit test `JNB ACC.5` -- Different GPIO pin due to Rev.2 hardware redesign - ---- - -## Summary Table - -| Cmd | Name | Dir | wValue | wLength | Purpose | -|------|------|-----|--------|---------|---------| -| 0x8F | **SET_DN_SWITCH** | OUT | 7-bit switch cmd | 0 | Legacy Dish Network switch via GPIO bit-bang on P0.4 | -| 0x91 | **I2C_ADDR_ADJUST** | IN | 0=dec, else inc | 1 | Inc/dec internal IRAM counter (debug) | -| 0x92 | **GET_FW_VERS** | IN | 0 | 6 | Read firmware version + build date (hardcoded) | -| 0x93 | **GET_SERIAL_NUMBER** | IN | 0 | 4 | Read 4-byte serial from I2C EEPROM (0x51) | -| 0x94 | **USE_EXTRA_VOLT** | OUT | 0=off, 1=on | 0 | Enable +1V LNB boost (13->14V, 18->19V) via XRAM 0xE0B6 | -| 0x95 | **GET_FPGA_VERS** | IN | 0 | 1 | Read EEPROM-stored hardware/platform ID | -| 0x96 | **SET_LNB_GPIO_MODE** | OUT | 0=default, 1=active | 0 | Configure LNB GPIO output enables (IOB/OEB) | -| 0x97 | **SET_GPIO_PINS** | OUT | pin bitmap | 0 | Direct write to LNB GPIO pins | -| 0x98 | **GET_GPIO_STATUS** | IN | 0 | 1 | Read LNB feedback GPIO pin (0 or 1) | - -### Driver Usage - -| Cmd | Linux Kernel | Windows Driver | Firmware | -|-----|-------------|----------------|----------| -| 0x8F | `dishnetwork_send_legacy_command` callback | Defined in header, not called in source | All versions | -| 0x91 | Not used | Not defined | All versions | -| 0x92 | `gp8psk_get_fw_version()` on boot | Not defined | All versions | -| 0x93 | Not called directly (defined in header) | Defined in header, not called in source | All versions | -| 0x94 | `enable_high_lnb_voltage` callback | Defined in header, not called in source | All versions | -| 0x95 | `gp8psk_get_fpga_version()` on boot | Not defined | All versions | -| 0x96 | Not used | Not defined | All versions | -| 0x97 | Not used | Not defined | All versions | -| 0x98 | Not used | Not defined | All versions | - -### Cross-Version Change Summary - -| Cmd | Changed? | Details | -|-----|----------|---------| -| 0x8F | No | Identical bit-bang algorithm, same GPIO pin P0.4 | -| 0x91 | Minor | IRAM counter address: 0x66 (v2.06) vs 0x18 (v2.13/Rev.2) | -| 0x92 | Data only | Different hardcoded version bytes per build | -| 0x93 | No | Same EEPROM read logic, same device 0x51 | -| 0x94 | Minor | Rev.2 returns 1 byte (ack); v2.06/v2.13 return nothing | -| 0x95 | Yes | v2.06 reads EEPROM offset 0x31, returns 2 bytes; v2.13/Rev.2 read offset 0x00, return 1 byte | -| 0x96 | Yes | Rev.2 uses different GPIO pins (IOB.4 + Port A) vs v2.06/v2.13 (IOB.3/2/1) | -| 0x97 | Yes | Rev.2 maps bits to individual pins across two ports; v2.06/v2.13 bulk-writes Port B | -| 0x98 | Yes | Rev.2 reads P0.5 (Port A); v2.06/v2.13 read IOB.0 (Port B) | - ---- - -## FX2 Register Reference - -| Address | Name | Purpose | -|---------|------|---------| -| 0xE6B8 | SETUPDAT[0] | bmRequestType | -| 0xE6B9 | SETUPDAT[1] | bRequest | -| 0xE6BA | SETUPDAT[2] | wValueL | -| 0xE6BB | SETUPDAT[3] | wValueH | -| 0xE68A | EP0BCH | EP0 byte count high | -| 0xE68B | EP0BCL | EP0 byte count low (write triggers transfer) | -| 0xE740 | EP0BUF | EP0 data buffer start | -| 0xE0B6 | (custom) | LNB voltage control register | -| SFR 0x80 | P0 / IOA | Port A (Port 0) data register | -| SFR 0xB0 | P3 / IOB | Port B data register | -| SFR 0xB2 | OEA | Port A output enable | -| SFR 0xB5 | OEB | Port B output enable | +# Genpix SkyWalker-1 Vendor Commands: Full Decode of "Unknown" Commands + +## Sources + +- Firmware disassembly via Ghidra MCP: v2.06 (port 8193), v2.13 FW1 (port 8194), Rev.2 v2.10.4 (port 8197) +- Linux kernel `gp8psk-fe.h` (kernel 4.9.227 and 6.18.6) -- provides definitive names for 0x8F, 0x92, 0x93, 0x94, 0x95 +- Linux kernel `gp8psk.c` and `gp8psk-fe.c` -- shows driver usage +- Windows driver `SkyWalker1Control.h` -- confirms 0x8F, 0x93, 0x94 + +## Jump Table Corrections + +The original addresses for Rev.2 were shifted by one command index. The corrected jump table targets (decoded from AJMP opcodes at CODE:0x0076) are: + +| Cmd | v2.06 (8193) | v2.13 (8194) | Rev.2 (8197) | +|------|-------------|-------------|-------------| +| 0x8F | 0x01FC | 0x021E | 0x0208 | +| 0x90 | 0x020B | 0x022D | 0x0217 | +| 0x91 | 0x022C | 0x024E | 0x0238 | +| 0x92 | 0x024A | 0x026C | 0x0256 | +| 0x93 | 0x026F | 0x0293 | 0x027D | +| 0x94 | 0x01B9 | 0x01DB | 0x01C5 | +| 0x95 | 0x02DF | 0x0303 | 0x02ED | +| 0x96 | 0x02B4 | 0x02D8 | 0x02C2 | +| 0x97 | 0x02C1 | 0x02E5 | 0x02CF | +| 0x98 | 0x02CB | 0x02EF | 0x02D9 | + +Note: v2.06 and v2.13 have 30 jump table entries (0x80-0x9D, range check `< 0x1E`). Rev.2 has only 27 entries (0x80-0x9A, range check `< 0x1B`). Commands 0x9B-0x9D do not exist on Rev.2 hardware. + +--- + +## Command 0x8F: SET_DN_SWITCH + +**Named by**: Linux kernel `gp8psk-fe.h`, Windows driver `SkyWalker1Control.h` + +| Field | Value | +|-------|-------| +| Direction | OUT (host-to-device) | +| wValue | Switch command byte (7 bits, LSB-first) | +| wIndex | 0x0000 | +| wLength | 0 (no data phase) | +| Purpose | Send legacy Dish Network switch command via GPIO bit-bang | + +### Firmware Behavior + +All three versions implement the same algorithm: + +1. Read `wValueL` from SETUPDAT[2] (0xE6BA) +2. Store value, then call a GPIO bit-bang subroutine: + - Assert P0.4 high (start pulse) + - Delay ~32 cycles + - Deassert P0.4 + - Delay ~8 cycles + - Loop 7 times, shifting out each bit LSB-first via P0.4 + - Between bits: delay ~8 cycles +3. ACK with EP0BCL = 0 (no data returned) + +### Subroutine Addresses + +| Version | Handler | Bit-bang Routine | Delay Routine | +|---------|---------|-----------------|---------------| +| v2.06 | 0x01FC | 0x1F04 | 0x1DFB | +| v2.13 | 0x021E | 0x1ECD | 0x14B9 | +| Rev.2 | 0x0208 | 0x1CE6 | 0x1BDA | + +### Linux Driver Usage + +```c +// gp8psk-fe.c: gp8psk_fe_send_legacy_dish_cmd() +u8 cmd = sw_cmd & 0x7f; +st->ops->out(st->priv, SET_DN_SWITCH, cmd, 0, NULL, 0); +// Then sets LNB voltage based on bit 7 of sw_cmd +st->ops->out(st->priv, SET_LNB_VOLTAGE, !!(sw_cmd & 0x80), 0, NULL, 0); +``` + +This is the `dishnetwork_send_legacy_command` callback in the DVB frontend ops. The 7-bit command value is a legacy Dish Network satellite switch protocol word, bit-banged on GPIO P0.4 with specific timing. The 8th bit (0x80) of the original command selects LNB voltage (13V or 18V) and is sent separately. + +### Cross-Version Differences + +None. All three versions use identical logic (same GPIO pin P0.4, same timing, same 7-bit protocol). Only the subroutine addresses differ due to code relocation. + +--- + +## Command 0x91: I2C_ADDR_ADJUST (internal debug) + +**Not named in any driver header.** This command is not used by either the Linux or Windows driver. + +| Field | Value | +|-------|-------| +| Direction | IN (device-to-host) | +| wValue | 0 = decrement, non-zero = increment | +| wIndex | 0x0000 | +| wLength | 1 byte | +| Returns | Current value of internal counter byte | +| Purpose | Increment/decrement an internal IRAM counter; return its value | + +### Firmware Behavior + +``` +if wValueL != 0: + IRAM[counter]++ +else: + IRAM[counter]-- +EP0BUF[0] = IRAM[counter] +EP0BCL = 1 +``` + +### Counter IRAM Addresses + +| Version | IRAM Address | +|---------|-------------| +| v2.06 | 0x66 | +| v2.13 | 0x18 | +| Rev.2 | 0x18 | + +### Purpose + +This appears to be a diagnostic/debug command for adjusting an internal pointer or counter, possibly related to I2C bus addressing or tuner register indexing. The counter is used by other internal routines but is not exposed through any standard driver interface. The name "I2C_ADDR_ADJUST" is inferred from its proximity to I2C commands and the inc/dec readback pattern. + +### Cross-Version Differences + +- v2.06 uses IRAM address 0x66; v2.13 and Rev.2 use 0x18 +- Logic is otherwise identical + +--- + +## Command 0x92: GET_FW_VERS + +**Named by**: Linux kernel `gp8psk-fe.h` (`#define GET_FW_VERS 0x92`) + +| Field | Value | +|-------|-------| +| Direction | IN (device-to-host) | +| wValue | 0x0000 | +| wIndex | 0x0000 | +| wLength | 6 bytes | +| Returns | Firmware version and build date (6 bytes, fixed per firmware build) | +| Purpose | Read firmware version identifier | + +### Data Format + +``` +Byte 0: Version minor-minor (fw_vers[0]) +Byte 1: Version minor (fw_vers[1]) +Byte 2: Version major (fw_vers[2]) +Byte 3: Build day +Byte 4: Build month +Byte 5: Build year (offset from 2000) +``` + +Full version number: `fw_vers[2] << 16 | fw_vers[1] << 8 | fw_vers[0]` +Build date: `(2000 + fw_vers[5]) / fw_vers[4] / fw_vers[3]` + +### Values Per Firmware + +| Version | Raw Bytes | Version | Build Date | FW_VERS macro | +|---------|-----------|---------|-----------|---------------| +| v2.06 | `04 06 02 0D 07 07` | 2.06.04 | 2007-07-13 | 0x020604 (= GP8PSK_FW_REV1) | +| v2.13 | `01 0D 02 0C 03 0A` | 2.13.01 | 2010-03-12 | 0x020D01 | +| Rev.2 | `04 0A 02 0C 03 0A` | 2.10.04 | 2010-03-12 | 0x020A04 (>= GP8PSK_FW_REV2) | + +### Linux Driver Usage + +```c +// gp8psk.c: gp8psk_get_fw_version() +gp8psk_usb_in_op(d, GET_FW_VERS, 0, 0, fw_vers, 6); + +// gp8psk.c: gp8psk_info() -- prints on boot: +// "FW Version = 2.06.4 (0x20604) Build 2007/07/13" + +// gp8psk-fe.h: Used for hardware revision detection: +#define GP8PSK_FW_REV1 0x020604 +#define GP8PSK_FW_REV2 0x020704 +// if GP8PSK_FW_VERS(fw) >= GP8PSK_FW_REV2 -> Rev.2 hardware +``` + +### Firmware Implementation + +The handler writes 6 hardcoded immediate values to EP0BUF[0..5] and sets EP0BCL = 6. There is no I2C or EEPROM access -- the version bytes are compiled directly into the firmware binary. + +### Cross-Version Differences + +Only the embedded constant values differ (they reflect each firmware's own version). The handler structure is identical across all three versions. + +--- + +## Command 0x93: GET_SERIAL_NUMBER + +**Named by**: Linux kernel `gp8psk-fe.h`, Windows driver `SkyWalker1Control.h` + +| Field | Value | +|-------|-------| +| Direction | IN (device-to-host) | +| wValue | 0x0000 | +| wIndex | 0x0000 | +| wLength | 4 bytes | +| Returns | 4-byte device serial number read from I2C EEPROM | +| Purpose | Read unique device serial number from onboard EEPROM | + +### Firmware Behavior + +1. Call EEPROM initialization routine (sets up I2C for device address 0x51) +2. Read byte at EEPROM bit-offset 0x08 -> EP0BUF[0] +3. Read byte at EEPROM bit-offset 0x10 -> EP0BUF[1] +4. Read byte at EEPROM bit-offset 0x18 -> EP0BUF[2] +5. Read byte at EEPROM bit-offset (from init) -> EP0BUF[3] +6. Set EP0BCL = 4 + +The I2C EEPROM at address 0x51 (7-bit) is a standard 24Cxx-family serial EEPROM. The serial number bytes are extracted at 8-bit intervals using a shift/rotate extraction routine. + +### Subroutine Addresses + +| Version | Handler | EEPROM Init | Byte Extract | +|---------|---------|------------|-------------| +| v2.06 | 0x026F | 0x1DA8 | 0x077C | +| v2.13 | 0x0293 | 0x1D4B | 0x078A | +| Rev.2 | 0x027D | 0x1A50 | 0x0798 | + +### Cross-Version Differences + +None in behavior. The EEPROM device address (0x51) and bit-offset scheme are identical. Only subroutine addresses differ. + +--- + +## Command 0x94: USE_EXTRA_VOLT + +**Named by**: Linux kernel `gp8psk-fe.h`, Windows driver `SkyWalker1Control.h` + +| Field | Value | +|-------|-------| +| Direction | OUT (host-to-device) in v2.06/v2.13; OUT with 1-byte ACK in Rev.2 | +| wValue | 0 = normal voltage (13V/18V), non-zero = high voltage (14V/19V) | +| wIndex | 0x0000 | +| wLength | 0 (v2.06/v2.13); 1 byte returned in Rev.2 | +| Purpose | Enable/disable +1V LNB voltage boost for long cable runs | + +### Firmware Behavior + +1. Read `wValueL` from SETUPDAT[2] +2. Compute carry flag: `CY = (wValueL >= 1)` via `ADD A,#0xFF` +3. Store carry to a bit-addressable IRAM flag +4. Call LNB voltage apply routine: + - If flag set: write 0x6A to XRAM 0xE0B6 (LNB control register) + - If flag clear: write 0x62 to XRAM 0xE0B6 +5. ACK (v2.06/v2.13: EP0BCL=0; Rev.2: returns 1 byte with result) + +The values 0x6A and 0x62 differ in bit 3 (0x08), which controls the extra voltage boost on the LNB power regulator IC. The register at XRAM 0xE0B6 is a hardware control register in the FX2's XRAM-mapped I/O space. + +### Flag Bit Addresses + +| Version | Bit Address | IRAM Byte.Bit | +|---------|------------|---------------| +| v2.06 | bit 0x07 | byte 0x20, bit 7 | +| v2.13 | bit 0x00 | byte 0x20, bit 0 | +| Rev.2 | bit 0x04 | byte 0x20, bit 4 | + +### Subroutine Addresses + +| Version | Handler | Voltage Apply | +|---------|---------|--------------| +| v2.06 | 0x01B9 | 0x2481 | +| v2.13 | 0x01DB | 0x23BF | +| Rev.2 | 0x01C5 | 0x21F5 | + +### Linux Driver Usage + +```c +// gp8psk-fe.c: gp8psk_fe_enable_high_lnb_voltage() +// Mapped to .enable_high_lnb_voltage frontend ops callback +st->ops->out(st->priv, USE_EXTRA_VOLT, onoff, 0, NULL, 0); +``` + +### Cross-Version Differences + +- v2.06/v2.13: Pure OUT command (EP0BCL=0, no data returned) +- **Rev.2**: Returns 1 byte (carry result from the voltage apply subroutine), making it partially an IN command. The Linux driver sends it as OUT and ignores any returned data, so this is backward-compatible. +- The bit-addressable flag location changes between versions but the functional behavior is identical. + +--- + +## Command 0x95: GET_FPGA_VERS + +**Named by**: Linux kernel `gp8psk-fe.h` (`#define GET_FPGA_VERS 0x95`) + +| Field | Value | +|-------|-------| +| Direction | IN (device-to-host) | +| wValue | 0x0000 | +| wIndex | 0x0000 | +| wLength | 1 byte (v2.13/Rev.2); 2 bytes (v2.06) | +| Returns | EEPROM-stored hardware/FPGA revision identifier | +| Purpose | Read hardware platform identifier from onboard EEPROM | + +### Firmware Behavior + +All versions read from the I2C EEPROM at device address 0x51, but the EEPROM offset and return size differ: + +**v2.06:** +1. Call EEPROM read at offset 0x31, read 2 bytes +2. Combine into 16-bit value (R7:R6) +3. Return 2 bytes in EP0BUF[0..1] + +**v2.13 and Rev.2:** +1. Call EEPROM read at offset 0x00, read 2 bytes +2. Compute a combined value +3. Return 1 byte in EP0BUF[0] + +### Subroutine Addresses + +| Version | Handler | EEPROM Read | +|---------|---------|------------| +| v2.06 | 0x02DF | 0x11DD | +| v2.13 | 0x0303 | 0x21AE | +| Rev.2 | 0x02ED | 0x0FDD | + +### Linux Driver Usage + +```c +// gp8psk.c: gp8psk_get_fpga_version() +gp8psk_usb_in_op(d, GET_FPGA_VERS, 0, 0, &fpga_vers, 1); + +// gp8psk.c: gp8psk_info() -- prints on boot: +// "FPGA Version = %i" +``` + +Despite the name "FPGA," this reads a version/ID byte from the I2C EEPROM. On these devices there is no separate FPGA -- the name is a legacy artifact from the Genpix product line where some models had an FPGA for signal processing. Here it returns the EEPROM-stored hardware platform identifier. + +### Cross-Version Differences + +- **v2.06**: Reads EEPROM offset 0x31, returns 2 bytes. The Linux driver only requests 1 byte, so it gets EP0BUF[0] (low byte of the 16-bit value). +- **v2.13/Rev.2**: Reads EEPROM offset 0x00, returns 1 byte. This is a cleaner implementation that matches what the driver actually requests. +- The change from offset 0x31 to offset 0x00 suggests the EEPROM layout was reorganized between Rev.1 and Rev.2 hardware. + +--- + +## Command 0x96: SET_LNB_GPIO_MODE (internal/debug) + +**Not named in any driver header.** This command is not used by either the Linux or Windows driver. + +| Field | Value | +|-------|-------| +| Direction | OUT (host-to-device) | +| wValue | 0 = default mode, non-zero = extra-volt pin mapping | +| wIndex | 0x0000 | +| wLength | 0 (no data phase) | +| Purpose | Configure LNB power supply GPIO output enable pins | + +### Firmware Behavior + +1. Read `wValueL`, compute carry flag (same pattern as USE_EXTRA_VOLT) +2. Store flag to bit-addressable IRAM location +3. Call GPIO configuration routine: + +**If flag set (v2.06/v2.13):** +``` +IOB = (IOB & 0xF7) | 0x06 ; clear IOB.3, set IOB.2 and IOB.1 +OEB = 0xFE ; enable IOB[7:1] as outputs, IOB.0 as input +``` + +**If flag clear (v2.06/v2.13):** +``` +OEB = 0xF0 ; enable IOB[7:4] as outputs, IOB[3:0] as inputs +``` + +### Flag Bit Addresses + +| Version | Bit Address | +|---------|------------| +| v2.06 | bit 0x07 | +| v2.13 | bit 0x04 | +| Rev.2 | bit 0x06 | + +### Subroutine Addresses + +| Version | Handler | GPIO Config | +|---------|---------|------------| +| v2.06 | 0x02B4 | 0x2406 | +| v2.13 | 0x02D8 | 0x2344 | +| Rev.2 | 0x02C2 | 0x20F9 | + +### Cross-Version Differences + +- **v2.06/v2.13**: Controls Port B (IOB/OEB) pins only. Clears IOB.3, sets IOB.2 and IOB.1 for voltage mode. +- **Rev.2**: Uses different GPIO pins reflecting the Rev.2 PCB layout: + - Clears IOB.4 (instead of IOB.3) + - Sets OEB with IOB.4 enabled + - Additionally sets Port A pins: P0.6 and P0.0 (via `ORL P0, #0x41` and `ORL OEA, #0x41`) + - Default mode: `OEB = 0xE7`, `OEA = 0x9E` + +This command configures the GPIO output enable registers that control the LNB voltage regulator hardware. It works in conjunction with USE_EXTRA_VOLT (0x94) and SET_LNB_VOLTAGE (0x8B) -- cmd 0x96 sets which pins are active outputs, while 0x8B and 0x94 set the pin states. + +--- + +## Command 0x97: SET_GPIO_PINS (internal/debug) + +**Not named in any driver header.** This command is not used by either the Linux or Windows driver. + +| Field | Value | +|-------|-------| +| Direction | OUT (host-to-device) | +| wValue | GPIO pin state bitmap | +| wIndex | 0x0000 | +| wLength | 0 (no data phase) | +| Purpose | Direct GPIO pin write for LNB/switch hardware control | + +### Firmware Behavior + +**v2.06/v2.13:** +``` +IOB = (IOB & 0xF1) | (wValueL & 0x0E) +``` +Clears IOB bits [3:1] and sets them to the corresponding bits from `wValueL`. This gives direct control over the 3 LNB-related GPIO pins on Port B. + +**Rev.2:** +Maps individual bits of `wValueL` to different GPIO pins: +``` +if wValueL.bit1: P0.6 = 1 else P0.6 = 0 ; Port A pin 6 +if wValueL.bit2: P0.0 = 1 else P0.0 = 0 ; Port A pin 0 +if wValueL.bit3: IOB.4 = 1 else IOB.4 = 0 ; Port B pin 4 +``` + +### Subroutine Addresses + +| Version | Handler | GPIO Write | +|---------|---------|-----------| +| v2.06 | 0x02C1 | 0x24BE | +| v2.13 | 0x02E5 | 0x2429 | +| Rev.2 | 0x02CF | 0x1FCF | + +### wValue Bit Mapping + +**v2.06/v2.13 (Port B bulk write):** +| wValue Bit | GPIO Pin | Function | +|-----------|---------|----------| +| bit 1 | IOB.1 | LNB regulator control line 1 | +| bit 2 | IOB.2 | LNB regulator control line 2 | +| bit 3 | IOB.3 | LNB regulator control line 3 | + +**Rev.2 (individual pin mapping):** +| wValue Bit | GPIO Pin | Function | +|-----------|---------|----------| +| bit 1 | P0.6 (Port A) | LNB control line | +| bit 2 | P0.0 (Port A) | LNB control line | +| bit 3 | IOB.4 (Port B) | LNB control line | + +### Cross-Version Differences + +Major hardware difference: v2.06/v2.13 use a simple Port B mask write, while Rev.2 maps each bit to different pins on two different ports (A and B). This reflects the Rev.2 PCB redesign that moved LNB control circuitry to different FX2 GPIO pins. + +--- + +## Command 0x98: GET_GPIO_STATUS (internal/debug) + +**Not named in any driver header.** This command is not used by either the Linux or Windows driver. + +| Field | Value | +|-------|-------| +| Direction | IN (device-to-host) | +| wValue | 0x0000 | +| wIndex | 0x0000 | +| wLength | 1 byte | +| Returns | GPIO input pin state (0 or 1) | +| Purpose | Read LNB-related GPIO input pin state | + +### Firmware Behavior + +**v2.06/v2.13:** +``` +A = IOB & 0x01 ; read Port B bit 0 +EP0BUF[0] = A ; return 0 or 1 +EP0BCL = 1 +``` + +**Rev.2:** +``` +A = P0 ; read Port A +if P0.5 == 1: + R7 = 1 +else: + R7 = 0 +EP0BUF[0] = R7 +EP0BCL = 1 +``` + +### Subroutine Addresses + +| Version | Handler | GPIO Read | +|---------|---------|----------| +| v2.06 | 0x02CB | 0x24CC | +| v2.13 | 0x02EF | 0x2437 | +| Rev.2 | 0x02D9 | 0x0046 | + +### GPIO Pin Read + +| Version | Pin | SFR | +|---------|-----|-----| +| v2.06 | IOB.0 (Port B bit 0) | SFR 0xB0, bit 0 | +| v2.13 | IOB.0 (Port B bit 0) | SFR 0xB0, bit 0 | +| Rev.2 | P0.5 (Port A bit 5) | SFR 0x80, bit 5 | + +### Purpose + +This reads a single GPIO input pin, likely a feedback/status signal from the LNB power supply circuitry (overcurrent detect, power-good, or similar). The pin assignment changed with the Rev.2 PCB redesign. + +### Cross-Version Differences + +- v2.06/v2.13: Reads Port B pin 0 (IOB.0) via mask `& 0x01` +- Rev.2: Reads Port A pin 5 (P0.5) via bit test `JNB ACC.5` +- Different GPIO pin due to Rev.2 hardware redesign + +--- + +## Summary Table + +| Cmd | Name | Dir | wValue | wLength | Purpose | +|------|------|-----|--------|---------|---------| +| 0x8F | **SET_DN_SWITCH** | OUT | 7-bit switch cmd | 0 | Legacy Dish Network switch via GPIO bit-bang on P0.4 | +| 0x91 | **I2C_ADDR_ADJUST** | IN | 0=dec, else inc | 1 | Inc/dec internal IRAM counter (debug) | +| 0x92 | **GET_FW_VERS** | IN | 0 | 6 | Read firmware version + build date (hardcoded) | +| 0x93 | **GET_SERIAL_NUMBER** | IN | 0 | 4 | Read 4-byte serial from I2C EEPROM (0x51) | +| 0x94 | **USE_EXTRA_VOLT** | OUT | 0=off, 1=on | 0 | Enable +1V LNB boost (13->14V, 18->19V) via XRAM 0xE0B6 | +| 0x95 | **GET_FPGA_VERS** | IN | 0 | 1 | Read EEPROM-stored hardware/platform ID | +| 0x96 | **SET_LNB_GPIO_MODE** | OUT | 0=default, 1=active | 0 | Configure LNB GPIO output enables (IOB/OEB) | +| 0x97 | **SET_GPIO_PINS** | OUT | pin bitmap | 0 | Direct write to LNB GPIO pins | +| 0x98 | **GET_GPIO_STATUS** | IN | 0 | 1 | Read LNB feedback GPIO pin (0 or 1) | + +### Driver Usage + +| Cmd | Linux Kernel | Windows Driver | Firmware | +|-----|-------------|----------------|----------| +| 0x8F | `dishnetwork_send_legacy_command` callback | Defined in header, not called in source | All versions | +| 0x91 | Not used | Not defined | All versions | +| 0x92 | `gp8psk_get_fw_version()` on boot | Not defined | All versions | +| 0x93 | Not called directly (defined in header) | Defined in header, not called in source | All versions | +| 0x94 | `enable_high_lnb_voltage` callback | Defined in header, not called in source | All versions | +| 0x95 | `gp8psk_get_fpga_version()` on boot | Not defined | All versions | +| 0x96 | Not used | Not defined | All versions | +| 0x97 | Not used | Not defined | All versions | +| 0x98 | Not used | Not defined | All versions | + +### Cross-Version Change Summary + +| Cmd | Changed? | Details | +|-----|----------|---------| +| 0x8F | No | Identical bit-bang algorithm, same GPIO pin P0.4 | +| 0x91 | Minor | IRAM counter address: 0x66 (v2.06) vs 0x18 (v2.13/Rev.2) | +| 0x92 | Data only | Different hardcoded version bytes per build | +| 0x93 | No | Same EEPROM read logic, same device 0x51 | +| 0x94 | Minor | Rev.2 returns 1 byte (ack); v2.06/v2.13 return nothing | +| 0x95 | Yes | v2.06 reads EEPROM offset 0x31, returns 2 bytes; v2.13/Rev.2 read offset 0x00, return 1 byte | +| 0x96 | Yes | Rev.2 uses different GPIO pins (IOB.4 + Port A) vs v2.06/v2.13 (IOB.3/2/1) | +| 0x97 | Yes | Rev.2 maps bits to individual pins across two ports; v2.06/v2.13 bulk-writes Port B | +| 0x98 | Yes | Rev.2 reads P0.5 (Port A); v2.06/v2.13 read IOB.0 (Port B) | + +--- + +## FX2 Register Reference + +| Address | Name | Purpose | +|---------|------|---------| +| 0xE6B8 | SETUPDAT[0] | bmRequestType | +| 0xE6B9 | SETUPDAT[1] | bRequest | +| 0xE6BA | SETUPDAT[2] | wValueL | +| 0xE6BB | SETUPDAT[3] | wValueH | +| 0xE68A | EP0BCH | EP0 byte count high | +| 0xE68B | EP0BCL | EP0 byte count low (write triggers transfer) | +| 0xE740 | EP0BUF | EP0 data buffer start | +| 0xE0B6 | (custom) | LNB voltage control register | +| SFR 0x80 | P0 / IOA | Port A (Port 0) data register | +| SFR 0xB0 | P3 / IOB | Port B data register | +| SFR 0xB2 | OEA | Port A output enable | +| SFR 0xB5 | OEB | Port B output enable |