Fix remaining safety review findings: DiSEqC timeout, hotplug, watchdog status

Addresses all remaining Apollo review items:

- C-3: DiSEqC Timer2 spin loops now have timeout protection via
  diseqc_wait_ticks() with per-tick I2C_TIMEOUT countdown. Tone burst
  refactored to use the shared helper instead of bare while(!TF2) loops.
  New ERR_DISEQC_TIMER (0x0C) error code on Timer2 failure.

- I-3: Hotplug scan hp_prev copy moved from start to end of scan.
  Aborted scans no longer corrupt the previous-good baseline, preventing
  false "removed" events on consecutive scan failures.

- S-1: Watchdog-fired detection in main loop — when wdt_armed==2 (ISR
  fired), sets ERR_WDT_FIRED (0x0D) readable via 0xBC and clears
  BM_STARTED|BM_FW_LOADED|BM_ARMED so host sees system is down.

- I-2: Comment explaining (WORD)sd_poll_count cast as SDCC optimization.

- I-1: CKCON bit ownership comment in diseqc_tone_burst() Timer2 setup.

Code: 13,079 / 15,360 bytes (85%). XRAM: 218 / 512. Stack: 132 bytes.
This commit is contained in:
Ryan Malloy 2026-02-16 05:48:43 -07:00
parent 834c2bd9ee
commit aecad367a0

View File

@ -80,6 +80,8 @@
#define ERR_EP2_TIMEOUT 0x09
#define ERR_NOT_SUPPORTED 0x0A
#define ERR_DISEQC_LEN 0x0B
#define ERR_DISEQC_TIMER 0x0C
#define ERR_WDT_FIRED 0x0D
/* configuration status byte bits */
#define BM_STARTED 0x01
@ -586,13 +588,8 @@ static void bcm4500_shutdown(void) {
static void i2c_hotplug_scan(void) {
static __xdata BYTE hp_a, hp_byte, hp_bit, hp_diff;
/* Save current as previous before starting new scan.
* This keeps hp_prev valid between scans so the host can read
* both bitmaps and see the actual transition. */
for (hp_a = 0; hp_a < 16; hp_a++)
hp_prev[hp_a] = hp_curr[hp_a];
/* Clear current scan buffer */
/* Clear current scan buffer (hp_prev retains last SUCCESSFUL scan
* so aborted scans don't corrupt the comparison baseline) */
for (hp_a = 0; hp_a < 16; hp_a++)
hp_curr[hp_a] = 0;
@ -641,6 +638,12 @@ static void i2c_hotplug_scan(void) {
}
}
/* Snapshot successful scan as baseline for next comparison.
* Done after comparison so hp_prev always reflects the last
* fully-completed scan, not a partial abort. */
for (hp_a = 0; hp_a < 16; hp_a++)
hp_prev[hp_a] = hp_curr[hp_a];
hp_scan_ok = 1;
return;
@ -677,6 +680,8 @@ static void stream_diag_poll(void) {
/* Rate-limited BCM4500 I2C reads for sync tracking.
* Only attempt if BCM is booted and interval elapsed. */
/* (WORD) cast: SDCC optimization — avoids 32-bit AND. The 12-bit
* mask (0x0FFF) only needs the low 16 bits, so the cast is safe. */
if ((config_status & BM_FW_LOADED) &&
((WORD)sd_poll_count & (SD_I2C_INTERVAL - 1)) == 0) {
sd_rd[0] = 0;
@ -781,6 +786,10 @@ static void gpif_stop(void) {
IOD |= 0xE0;
}
/* Forward declaration: diseqc_wait_ticks() is defined in the Manchester
* encoder section but used by diseqc_tone_burst() above it. */
static void diseqc_wait_ticks(BYTE count);
/* ---------- DiSEqC tone burst ---------- */
/*
@ -791,45 +800,31 @@ static void gpif_stop(void) {
* Uses Timer2 for timing as the stock firmware does.
*/
static void diseqc_tone_burst(BYTE sat_b) {
BYTE i;
if (sat_b) { last_error = ERR_NOT_SUPPORTED; return; }
/* Configure Timer2 auto-reload */
/* CKCON.T2M = 0 -> Timer2 clk = 48MHz/12 = 4MHz */
/* Configure Timer2 auto-reload.
* CKCON bit 5 (T2M) only do not touch bit 3 (Timer0/watchdog). */
CKCON &= ~0x20;
T2CON = 0x04; /* auto-reload, running */
RCAP2H = 0xF8;
RCAP2L = 0x2F; /* reload = 63535 -> ~500us tick */
TL2 = 0xFF;
TH2 = 0xFF; /* force immediate overflow */
TF2 = 0;
/* Pre-burst settling: 15 ticks (~7.5ms) with carrier off */
IOA &= ~PIN_22KHZ;
TF2 = 0;
for (i = 0; i < 15; i++) {
while (!TF2)
;
TF2 = 0;
}
diseqc_wait_ticks(15);
/* Burst: 25 ticks (~12.5ms) with carrier on */
IOA |= PIN_22KHZ;
for (i = 0; i < 25; i++) {
while (!TF2)
;
TF2 = 0;
}
diseqc_wait_ticks(25);
/* Carrier off */
IOA &= ~PIN_22KHZ;
/* Post-burst settling: 5 ticks (~2.5ms) */
for (i = 0; i < 5; i++) {
while (!TF2)
;
TF2 = 0;
}
diseqc_wait_ticks(5);
/* Stop Timer2 */
TR2 = 0;
@ -853,9 +848,15 @@ static void diseqc_tone_burst(BYTE sat_b) {
static void diseqc_wait_ticks(BYTE count) {
static __xdata BYTE dt_i;
WORD dt_timeout;
for (dt_i = 0; dt_i < count; dt_i++) {
while (!TF2)
;
dt_timeout = I2C_TIMEOUT;
while (!TF2) {
if (--dt_timeout == 0) {
last_error = ERR_DISEQC_TIMER;
return;
}
}
TF2 = 0;
}
}
@ -2210,6 +2211,15 @@ void main(void) {
while (TRUE) {
wdt_kick(); /* main loop alive — reset watchdog */
/* If watchdog fired while we were blocked in a vendor command,
* acknowledge it: set error code so host can read via 0xBC,
* and clear config flags so stale state doesn't cause confusion. */
if (wdt_armed == 2) {
last_error = ERR_WDT_FIRED;
config_status &= ~(BM_STARTED | BM_FW_LOADED | BM_ARMED);
wdt_armed = 0; /* acknowledged — host must re-boot to recover */
}
if (got_sud) {
handle_setupdata();
got_sud = FALSE;