Add software watchdog and timeout protection for all I2C/USB paths (firmware v3.05.0)

Safety review identified infinite-hang paths in do_tune(), GPIF streaming,
EP0/EP2 FIFO waits, and the 0xB4 I2C bus scan. The firmware controls an LNB
power supply (750mA at 18V) on a Cypress FX2LP with no hardware watchdog.

Key changes:
- Software watchdog via Timer0 ISR (~2s timeout, cuts LNB power on stall)
- Replace fx2lib i2c_write() in do_tune() with timeout-protected helper
- ep0_wait_data() helper replaces 7 bare EP0 BUSY spin loops
- GPIF idle wait and EP2 FIFO full wait now have timeouts
- 0xB4 I2C bus scan uses i2c_wait_done()/i2c_wait_stop() instead of bare spins
- Return-value checks on all I2C writes in sweep/scan functions
- EP0 payload length validation on all vendor commands with data phase
- Zero-fill EP0BUF before indirect reads (0x87, 0xB7) for deterministic output
- i2c_wait_stop() now sets last_error on timeout
- New error codes: ERR_TUNE_FAIL through ERR_DISEQC_LEN
- BCM_LOCK_BIT constant replaces hardcoded 0x20 in lock checks
- DiSEqC Tone Burst B rejected with ERR_NOT_SUPPORTED
- DiSEqC message length error sets ERR_DISEQC_LEN
- hp_changes counter saturates instead of wrapping
- stream_diag_poll() only updates status on successful I2C reads
- do_tune() forward declaration eliminates implicit-function warning
- do_tune() sets ERR_BCM_NOT_READY when demod not booted
- wdt_kick() in all long-running sweep/scan loops

Code: 13,057 / 15,360 bytes (85%). XRAM: 218 / 512 bytes (43%).
Stack: 132 bytes free. Zero new SDCC warnings.
This commit is contained in:
Ryan Malloy 2026-02-16 03:41:08 -07:00
parent 6e353c351f
commit 834c2bd9ee

View File

@ -33,6 +33,7 @@
/* BCM4500 status registers */ /* BCM4500 status registers */
#define BCM_REG_STATUS 0xA2 #define BCM_REG_STATUS 0xA2
#define BCM_REG_LOCK 0xA4 #define BCM_REG_LOCK 0xA4
#define BCM_LOCK_BIT 0x20 /* BCM4500 lock detect bit in register 0xA4 */
/* BCM commands */ /* BCM commands */
#define BCM_CMD_READ 0x01 #define BCM_CMD_READ 0x01
@ -73,6 +74,12 @@
#define ERR_I2C_ARB_LOST 0x03 #define ERR_I2C_ARB_LOST 0x03
#define ERR_BCM_NOT_READY 0x04 #define ERR_BCM_NOT_READY 0x04
#define ERR_BCM_TIMEOUT 0x05 #define ERR_BCM_TIMEOUT 0x05
#define ERR_TUNE_FAIL 0x06
#define ERR_EP0_TIMEOUT 0x07
#define ERR_GPIF_TIMEOUT 0x08
#define ERR_EP2_TIMEOUT 0x09
#define ERR_NOT_SUPPORTED 0x0A
#define ERR_DISEQC_LEN 0x0B
/* configuration status byte bits */ /* configuration status byte bits */
#define BM_STARTED 0x01 #define BM_STARTED 0x01
@ -136,6 +143,11 @@ static __xdata BYTE sd_had_sync; /* had sync in previous poll */
/* Main loop timing: USB frame counter for periodic tasks */ /* Main loop timing: USB frame counter for periodic tasks */
static __xdata WORD hp_last_frame; /* frame counter at last I2C scan */ static __xdata WORD hp_last_frame; /* frame counter at last I2C scan */
/* Software watchdog: Timer0 ISR decrements; on zero, LNB power is cut.
* volatile: shared between ISR (interrupt 1) and main loop. */
static volatile __xdata BYTE wdt_counter;
static volatile __xdata BYTE wdt_armed;
/* /*
* BCM4500 register initialization data extracted from stock v2.06 firmware. * BCM4500 register initialization data extracted from stock v2.06 firmware.
* FUN_CODE_0ddd writes these 3 blocks to BCM4500 indirect registers (page 0) * FUN_CODE_0ddd writes these 3 blocks to BCM4500 indirect registers (page 0)
@ -163,6 +175,8 @@ static const __code BYTE bcm_init_block2[] = {
* SCL low (clock stretching), the master waits forever without this. * SCL low (clock stretching), the master waits forever without this.
*/ */
#define I2C_TIMEOUT 6000 #define I2C_TIMEOUT 6000
#define GPIF_TIMEOUT 60000 /* GPIF idle wait (~15ms at 4MHz tick) */
#define EP2_TIMEOUT 60000 /* EP2 drain wait */
static BOOL i2c_wait_done(void) { static BOOL i2c_wait_done(void) {
WORD timeout = I2C_TIMEOUT; WORD timeout = I2C_TIMEOUT;
@ -178,8 +192,25 @@ static BOOL i2c_wait_done(void) {
static BOOL i2c_wait_stop(void) { static BOOL i2c_wait_stop(void) {
WORD timeout = I2C_TIMEOUT; WORD timeout = I2C_TIMEOUT;
while (I2CS & bmSTOP) { while (I2CS & bmSTOP) {
if (--timeout == 0) if (--timeout == 0) {
last_error = ERR_I2C_TIMEOUT;
return FALSE; return FALSE;
}
}
return TRUE;
}
/*
* Wait for EP0 data phase to complete (host -> device transfer).
* Replaces bare `while (EP0CS & bmEPBUSY)` spin loops with timeout.
*/
static BOOL ep0_wait_data(void) {
WORD timeout = I2C_TIMEOUT;
while (EP0CS & bmEPBUSY) {
if (--timeout == 0) {
last_error = ERR_EP0_TIMEOUT;
return FALSE;
}
} }
return TRUE; return TRUE;
} }
@ -314,6 +345,39 @@ fail:
return FALSE; return FALSE;
} }
/* ---------- Software watchdog ---------- */
/*
* Kick the watchdog timer resets the countdown to ~2 seconds.
* Must be called from the main loop at least once per period.
*/
static void wdt_kick(void) {
if (wdt_armed == 1) wdt_counter = 122;
}
/*
* Initialize Timer0 as a ~16ms periodic interrupt for the software
* watchdog. At 48MHz/12 = 4MHz timer clock with 16-bit overflow,
* period = 65536 / 4MHz = 16.384ms. 122 decrements × 16.384ms 2s.
*
* Mode 1 (16-bit) does NOT auto-reload. After overflow the counter
* wraps to 0x0000 and keeps counting which is exactly the reload
* value we want, so no manual reload is needed in the ISR. If the
* start value is ever changed to non-zero, add a reload in the ISR.
*
* CKCON bit 3 (T0M) controls Timer0 clock; bit 5 (T2M) is Timer2.
* DiSEqC uses Timer2 do not touch bit 3 from DiSEqC code.
*/
static void wdt_init(void) {
TMOD = (TMOD & 0xF0) | 0x01; /* Timer0 Mode 1 (16-bit) */
CKCON &= ~0x08; /* Timer0 clk = 48MHz/12 = 4MHz (bit 3 only) */
TH0 = 0x00; TL0 = 0x00; /* full-count: 0x0000 to 0xFFFF = 16.384ms */
wdt_armed = 1;
wdt_kick();
ET0 = 1; /* Enable Timer0 interrupt */
TR0 = 1; /* Start Timer0 */
}
/* /*
* Write one byte to a BCM4500 direct I2C register (subaddr). * Write one byte to a BCM4500 direct I2C register (subaddr).
*/ */
@ -570,7 +634,7 @@ static void i2c_hotplug_scan(void) {
/* Count per-device changes (not per-byte) */ /* Count per-device changes (not per-byte) */
hp_byte = hp_diff; hp_byte = hp_diff;
while (hp_byte) { while (hp_byte) {
hp_changes++; if (hp_changes < 0xFFFF) hp_changes++;
hp_byte &= (hp_byte - 1); hp_byte &= (hp_byte - 1);
} }
} }
@ -617,18 +681,17 @@ static void stream_diag_poll(void) {
((WORD)sd_poll_count & (SD_I2C_INTERVAL - 1)) == 0) { ((WORD)sd_poll_count & (SD_I2C_INTERVAL - 1)) == 0) {
sd_rd[0] = 0; sd_rd[0] = 0;
sd_rd[1] = 0; sd_rd[1] = 0;
i2c_combined_read(BCM4500_ADDR, BCM_REG_STATUS, 1, &sd_rd[0]); if (i2c_combined_read(BCM4500_ADDR, BCM_REG_STATUS, 1, &sd_rd[0]))
i2c_combined_read(BCM4500_ADDR, BCM_REG_LOCK, 1, &sd_rd[1]); sd_last_status = sd_rd[0];
if (i2c_combined_read(BCM4500_ADDR, BCM_REG_LOCK, 1, &sd_rd[1]))
sd_last_status = sd_rd[0]; sd_last_lock = sd_rd[1];
sd_last_lock = sd_rd[1];
/* Detect sync loss: had lock (bit 5) previously, lost it now */ /* Detect sync loss: had lock (bit 5) previously, lost it now */
if (sd_had_sync && !(sd_last_lock & 0x20)) { if (sd_had_sync && !(sd_last_lock & BCM_LOCK_BIT)) {
if (sd_sync_loss < 0xFFFF) if (sd_sync_loss < 0xFFFF)
sd_sync_loss++; sd_sync_loss++;
} }
sd_had_sync = (sd_last_lock & 0x20) ? 1 : 0; sd_had_sync = (sd_last_lock & BCM_LOCK_BIT) ? 1 : 0;
} }
} }
@ -664,9 +727,18 @@ static void gpif_start(void) {
/* Assert P3.5 low (BCM4500 TS enable) briefly */ /* Assert P3.5 low (BCM4500 TS enable) briefly */
IOD &= ~0x20; IOD &= ~0x20;
/* Wait for GPIF idle */ /* Wait for GPIF idle with timeout */
while (!(GPIFTRIG & 0x80)) {
; WORD gp_timeout = GPIF_TIMEOUT;
while (!(GPIFTRIG & 0x80)) {
if (--gp_timeout == 0) {
last_error = ERR_GPIF_TIMEOUT;
IOD |= 0x20;
config_status &= ~BM_ARMED;
return;
}
}
}
IOD |= 0x20; IOD |= 0x20;
@ -688,9 +760,16 @@ static void gpif_stop(void) {
EP2FIFOBCH = 0xFF; EP2FIFOBCH = 0xFF;
SYNCDELAY; SYNCDELAY;
/* Wait for GPIF idle */ /* Wait for GPIF idle with timeout */
while (!(GPIFTRIG & 0x80)) {
; WORD gp_timeout = GPIF_TIMEOUT;
while (!(GPIFTRIG & 0x80)) {
if (--gp_timeout == 0) {
last_error = ERR_GPIF_TIMEOUT;
break; /* proceed with cleanup regardless */
}
}
}
/* Skip/discard partial EP2 packet */ /* Skip/discard partial EP2 packet */
OUTPKTEND = 0x82; OUTPKTEND = 0x82;
@ -714,7 +793,7 @@ static void gpif_stop(void) {
static void diseqc_tone_burst(BYTE sat_b) { static void diseqc_tone_burst(BYTE sat_b) {
BYTE i; BYTE i;
(void)sat_b; /* both A and B send 22kHz burst for now */ if (sat_b) { last_error = ERR_NOT_SUPPORTED; return; }
/* Configure Timer2 auto-reload */ /* Configure Timer2 auto-reload */
/* CKCON.T2M = 0 -> Timer2 clk = 48MHz/12 = 4MHz */ /* CKCON.T2M = 0 -> Timer2 clk = 48MHz/12 = 4MHz */
@ -822,8 +901,10 @@ static void diseqc_send_byte(BYTE val) {
static void diseqc_send_message(BYTE len) { static void diseqc_send_message(BYTE len) {
static __xdata BYTE dm_i, dm_saved_tone; static __xdata BYTE dm_i, dm_saved_tone;
if (len < 3 || len > 6) if (len < 3 || len > 6) {
last_error = ERR_DISEQC_LEN;
return; return;
}
/* Save current 22 kHz tone state */ /* Save current 22 kHz tone state */
dm_saved_tone = IOA & PIN_22KHZ; dm_saved_tone = IOA & PIN_22KHZ;
@ -874,6 +955,10 @@ static void diseqc_send_message(BYTE len) {
IOA &= ~PIN_22KHZ; IOA &= ~PIN_22KHZ;
} }
/* Forward declaration: do_tune() is defined after the sweep functions
* but called from do_param_sweep(). */
static void do_tune(void);
/* ---------- Parameterized sweep (0xBA) ---------- */ /* ---------- Parameterized sweep (0xBA) ---------- */
/* /*
@ -918,6 +1003,7 @@ static void do_param_sweep(void) {
ps_cur = ps_start; ps_cur = ps_start;
while (ps_cur <= ps_stop) { while (ps_cur <= ps_stop) {
wdt_kick(); /* sweep is progressing, not hung */
/* /*
* Set up a tune payload in EP0BUF for do_tune(): * Set up a tune payload in EP0BUF for do_tune():
* [0..3] = symbol_rate (LE), [4..7] = freq (LE), [8] = mod, [9] = fec * [0..3] = symbol_rate (LE), [4..7] = freq (LE), [8] = mod, [9] = fec
@ -957,8 +1043,15 @@ static void do_param_sweep(void) {
SYNCDELAY; SYNCDELAY;
ps_buf_idx = 0; ps_buf_idx = 0;
while (EP2CS & bmEPFULL) {
; WORD ep2_to = EP2_TIMEOUT;
while (EP2CS & bmEPFULL) {
if (--ep2_to == 0) {
last_error = ERR_EP2_TIMEOUT;
return;
}
}
}
} }
ps_cur += ps_step; ps_cur += ps_step;
@ -1016,20 +1109,30 @@ static BOOL do_adaptive_blind_scan(void) {
abs_sr_cur = abs_sr_min; abs_sr_cur = abs_sr_min;
while (abs_sr_cur <= abs_sr_max) { while (abs_sr_cur <= abs_sr_max) {
wdt_kick(); /* scan is progressing, not hung */
/* Program SR and frequency into BCM4500 */ /* Program SR and frequency into BCM4500 */
i2c_buf[0] = (BYTE)(abs_sr_cur >> 24); i2c_buf[0] = (BYTE)(abs_sr_cur >> 24);
i2c_buf[1] = (BYTE)(abs_sr_cur >> 16); i2c_buf[1] = (BYTE)(abs_sr_cur >> 16);
i2c_buf[2] = (BYTE)(abs_sr_cur >> 8); i2c_buf[2] = (BYTE)(abs_sr_cur >> 8);
i2c_buf[3] = (BYTE)(abs_sr_cur); i2c_buf[3] = (BYTE)(abs_sr_cur);
bcm_indirect_write_block(0x00, i2c_buf, 4); if (!bcm_indirect_write_block(0x00, i2c_buf, 4)) {
abs_sr_cur += abs_sr_step;
continue;
}
i2c_buf[0] = (BYTE)(abs_freq >> 24); i2c_buf[0] = (BYTE)(abs_freq >> 24);
i2c_buf[1] = (BYTE)(abs_freq >> 16); i2c_buf[1] = (BYTE)(abs_freq >> 16);
i2c_buf[2] = (BYTE)(abs_freq >> 8); i2c_buf[2] = (BYTE)(abs_freq >> 8);
i2c_buf[3] = (BYTE)(abs_freq); i2c_buf[3] = (BYTE)(abs_freq);
bcm_indirect_write_block(0x00, i2c_buf, 4); if (!bcm_indirect_write_block(0x00, i2c_buf, 4)) {
abs_sr_cur += abs_sr_step;
continue;
}
bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE); if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE)) {
abs_sr_cur += abs_sr_step;
continue;
}
/* Quick AGC pre-check if enabled */ /* Quick AGC pre-check if enabled */
if (abs_quick_dwell > 0) { if (abs_quick_dwell > 0) {
@ -1056,7 +1159,7 @@ static BOOL do_adaptive_blind_scan(void) {
/* Check lock */ /* Check lock */
abs_lock_val = 0; abs_lock_val = 0;
bcm_direct_read(BCM_REG_LOCK, &abs_lock_val); bcm_direct_read(BCM_REG_LOCK, &abs_lock_val);
if (abs_lock_val & 0x20) { if (abs_lock_val & BCM_LOCK_BIT) {
EP0BUF[0] = (BYTE)(abs_freq); EP0BUF[0] = (BYTE)(abs_freq);
EP0BUF[1] = (BYTE)(abs_freq >> 8); EP0BUF[1] = (BYTE)(abs_freq >> 8);
EP0BUF[2] = (BYTE)(abs_freq >> 16); EP0BUF[2] = (BYTE)(abs_freq >> 16);
@ -1120,6 +1223,7 @@ static void do_spectrum_sweep(void) {
cur_freq = start_freq; cur_freq = start_freq;
while (cur_freq <= stop_freq) { while (cur_freq <= stop_freq) {
wdt_kick(); /* sweep is progressing, not hung */
/* /*
* Program frequency into BCM4500 via indirect write. * Program frequency into BCM4500 via indirect write.
* The BCM4500 expects big-endian frequency bytes at page 0. * The BCM4500 expects big-endian frequency bytes at page 0.
@ -1129,7 +1233,10 @@ static void do_spectrum_sweep(void) {
i2c_buf[1] = (BYTE)(cur_freq >> 16); i2c_buf[1] = (BYTE)(cur_freq >> 16);
i2c_buf[2] = (BYTE)(cur_freq >> 8); i2c_buf[2] = (BYTE)(cur_freq >> 8);
i2c_buf[3] = (BYTE)(cur_freq); i2c_buf[3] = (BYTE)(cur_freq);
bcm_indirect_write_block(0x00, i2c_buf, 4); if (!bcm_indirect_write_block(0x00, i2c_buf, 4)) {
cur_freq += step_khz;
continue;
}
/* Wait for demod to settle */ /* Wait for demod to settle */
delay(10); delay(10);
@ -1155,8 +1262,15 @@ static void do_spectrum_sweep(void) {
ss_buf_idx = 0; ss_buf_idx = 0;
/* Wait for the buffer to be taken by host */ /* Wait for the buffer to be taken by host */
while (EP2CS & bmEPFULL) {
; WORD ep2_to = EP2_TIMEOUT;
while (EP2CS & bmEPFULL) {
if (--ep2_to == 0) {
last_error = ERR_EP2_TIMEOUT;
return;
}
}
}
} }
cur_freq += step_khz; cur_freq += step_khz;
@ -1212,6 +1326,7 @@ static BOOL do_blind_scan(void) {
sr_cur = sr_min; sr_cur = sr_min;
while (sr_cur <= sr_max) { while (sr_cur <= sr_max) {
wdt_kick(); /* scan is progressing, not hung */
/* /*
* Program frequency (BE) and symbol rate (BE) into BCM4500. * Program frequency (BE) and symbol rate (BE) into BCM4500.
* We write both in a single block: 4 bytes SR + 4 bytes freq. * We write both in a single block: 4 bytes SR + 4 bytes freq.
@ -1220,16 +1335,25 @@ static BOOL do_blind_scan(void) {
i2c_buf[1] = (BYTE)(sr_cur >> 16); i2c_buf[1] = (BYTE)(sr_cur >> 16);
i2c_buf[2] = (BYTE)(sr_cur >> 8); i2c_buf[2] = (BYTE)(sr_cur >> 8);
i2c_buf[3] = (BYTE)(sr_cur); i2c_buf[3] = (BYTE)(sr_cur);
bcm_indirect_write_block(0x00, i2c_buf, 4); if (!bcm_indirect_write_block(0x00, i2c_buf, 4)) {
sr_cur += sr_step;
continue;
}
i2c_buf[0] = (BYTE)(freq_khz >> 24); i2c_buf[0] = (BYTE)(freq_khz >> 24);
i2c_buf[1] = (BYTE)(freq_khz >> 16); i2c_buf[1] = (BYTE)(freq_khz >> 16);
i2c_buf[2] = (BYTE)(freq_khz >> 8); i2c_buf[2] = (BYTE)(freq_khz >> 8);
i2c_buf[3] = (BYTE)(freq_khz); i2c_buf[3] = (BYTE)(freq_khz);
bcm_indirect_write_block(0x00, i2c_buf, 4); if (!bcm_indirect_write_block(0x00, i2c_buf, 4)) {
sr_cur += sr_step;
continue;
}
/* Issue tune command */ /* Issue tune command */
bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE); if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE)) {
sr_cur += sr_step;
continue;
}
/* Wait for acquisition attempt */ /* Wait for acquisition attempt */
delay(100); delay(100);
@ -1237,7 +1361,7 @@ static BOOL do_blind_scan(void) {
/* Check lock */ /* Check lock */
bs_lock_val = 0; bs_lock_val = 0;
bcm_direct_read(BCM_REG_LOCK, &bs_lock_val); bcm_direct_read(BCM_REG_LOCK, &bs_lock_val);
if (bs_lock_val & 0x20) { if (bs_lock_val & BCM_LOCK_BIT) {
/* Locked -- report back via EP0 */ /* Locked -- report back via EP0 */
EP0BUF[0] = (BYTE)(freq_khz); EP0BUF[0] = (BYTE)(freq_khz);
EP0BUF[1] = (BYTE)(freq_khz >> 8); EP0BUF[1] = (BYTE)(freq_khz >> 8);
@ -1276,8 +1400,10 @@ static void do_tune(void) {
static __xdata BYTE tune_i; static __xdata BYTE tune_i;
static __xdata BYTE tune_data[13]; /* 12 data + 1 scratch for reg addr */ static __xdata BYTE tune_data[13]; /* 12 data + 1 scratch for reg addr */
if (!(config_status & BM_STARTED)) if (!(config_status & BM_STARTED)) {
last_error = ERR_BCM_NOT_READY;
return; return;
}
/* /*
* Byte-reverse symbol rate (LE->BE) into tune_data[0..3] * Byte-reverse symbol rate (LE->BE) into tune_data[0..3]
@ -1309,17 +1435,21 @@ static void do_tune(void) {
} }
/* Poll BCM4500 for readiness */ /* Poll BCM4500 for readiness */
bcm_poll_ready(); if (!bcm_poll_ready())
return;
/* Write page 0 */ /* Write page 0 */
bcm_direct_write(BCM_REG_PAGE, 0x00); if (!bcm_direct_write(BCM_REG_PAGE, 0x00))
return;
/* Write all configuration data to BCM4500 data register */ /* Write all 12 configuration bytes to BCM4500 data register (0xA7).
tune_data[12] = BCM_REG_DATA; /* borrow byte past data (safe: 13 bytes in xdata) */ * Uses our timeout-protected multi-byte write instead of fx2lib i2c_write(). */
i2c_write(BCM4500_ADDR, 1, &tune_data[12], 12, tune_data); if (!i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_DATA, 12, tune_data))
return;
/* Execute indirect write */ /* Execute indirect write */
bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE); if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE))
return;
/* Wait for command completion */ /* Wait for command completion */
bcm_poll_ready(); bcm_poll_ready();
@ -1355,8 +1485,8 @@ BOOL handle_vendorcommand(BYTE cmd) {
/* EP0 data phase: wait for 10 bytes from host */ /* EP0 data phase: wait for 10 bytes from host */
EP0BCL = 0; EP0BCL = 0;
SYNCDELAY; SYNCDELAY;
while (EP0CS & bmEPBUSY) if (!ep0_wait_data()) return TRUE;
; if (EP0BCL < 10) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_tune(); do_tune();
return TRUE; return TRUE;
@ -1370,6 +1500,9 @@ BOOL handle_vendorcommand(BYTE cmd) {
EP0BCL = 6; EP0BCL = 6;
return TRUE; return TRUE;
} }
/* Zero-fill before reads so I2C failures return zeros, not stale data */
EP0BUF[0] = 0; EP0BUF[1] = 0; EP0BUF[2] = 0;
EP0BUF[3] = 0; EP0BUF[4] = 0; EP0BUF[5] = 0;
/* Read signal quality via indirect registers */ /* Read signal quality via indirect registers */
bcm_indirect_read(0x00, &EP0BUF[0]); bcm_indirect_read(0x00, &EP0BUF[0]);
bcm_indirect_read(0x01, &EP0BUF[1]); bcm_indirect_read(0x01, &EP0BUF[1]);
@ -1549,8 +1682,8 @@ BOOL handle_vendorcommand(BYTE cmd) {
/* EP0 data phase: receive message bytes */ /* EP0 data phase: receive message bytes */
EP0BCL = 0; EP0BCL = 0;
SYNCDELAY; SYNCDELAY;
while (EP0CS & bmEPBUSY) if (!ep0_wait_data()) return TRUE;
; if (EP0BCL < (BYTE)wlen) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
/* Copy message from EP0BUF to diseqc_msg buffer */ /* Copy message from EP0BUF to diseqc_msg buffer */
{ {
BYTE di; BYTE di;
@ -1575,10 +1708,10 @@ BOOL handle_vendorcommand(BYTE cmd) {
/* 0x92: GET_FW_VERS -- return firmware version and build date */ /* 0x92: GET_FW_VERS -- return firmware version and build date */
case GET_FW_VERS: case GET_FW_VERS:
EP0BUF[0] = 0x00; /* patch -> version 3.04.0 */ EP0BUF[0] = 0x00; /* patch -> version 3.05.0 */
EP0BUF[1] = 0x04; /* minor */ EP0BUF[1] = 0x05; /* minor */
EP0BUF[2] = 0x03; /* major */ EP0BUF[2] = 0x03; /* major */
EP0BUF[3] = 0x0F; /* day = 15 */ EP0BUF[3] = 0x10; /* day = 16 */
EP0BUF[4] = 0x02; /* month = 2 */ EP0BUF[4] = 0x02; /* month = 2 */
EP0BUF[5] = 0x1A; /* year - 2000 = 26 */ EP0BUF[5] = 0x1A; /* year - 2000 = 26 */
EP0BCH = 0; EP0BCH = 0;
@ -1597,8 +1730,8 @@ BOOL handle_vendorcommand(BYTE cmd) {
/* EP0 data phase: wait for 10 bytes from host */ /* EP0 data phase: wait for 10 bytes from host */
EP0BCL = 0; EP0BCL = 0;
SYNCDELAY; SYNCDELAY;
while (EP0CS & bmEPBUSY) if (!ep0_wait_data()) return TRUE;
; if (EP0BCL < 10) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_spectrum_sweep(); do_spectrum_sweep();
return TRUE; return TRUE;
@ -1624,8 +1757,8 @@ BOOL handle_vendorcommand(BYTE cmd) {
/* EP0 data phase: wait for 16 bytes from host */ /* EP0 data phase: wait for 16 bytes from host */
EP0BCL = 0; EP0BCL = 0;
SYNCDELAY; SYNCDELAY;
while (EP0CS & bmEPBUSY) if (!ep0_wait_data()) return TRUE;
; if (EP0BCL < 16) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_blind_scan(); do_blind_scan();
return TRUE; return TRUE;
@ -1640,8 +1773,7 @@ BOOL handle_vendorcommand(BYTE cmd) {
/* Try START + address + write, see if ACK comes back */ /* Try START + address + write, see if ACK comes back */
I2CS |= bmSTART; I2CS |= bmSTART;
I2DAT = a << 1; /* write direction */ I2DAT = a << 1; /* write direction */
while (!(I2CS & bmDONE)) if (!i2c_wait_done()) break;
;
if (I2CS & bmACK) { if (I2CS & bmACK) {
/* Device responded at this address */ /* Device responded at this address */
byte_idx = a >> 3; byte_idx = a >> 3;
@ -1649,8 +1781,7 @@ BOOL handle_vendorcommand(BYTE cmd) {
EP0BUF[byte_idx] |= (1 << bit); EP0BUF[byte_idx] |= (1 << bit);
} }
I2CS |= bmSTOP; I2CS |= bmSTOP;
while (I2CS & bmSTOP) if (!i2c_wait_stop()) break;
;
} }
EP0BCH = 0; EP0BCH = 0;
EP0BCL = 16; EP0BCL = 16;
@ -1739,6 +1870,9 @@ BOOL handle_vendorcommand(BYTE cmd) {
* in a single USB transfer instead of 3 separate reads. */ * in a single USB transfer instead of 3 separate reads. */
case SIGNAL_MONITOR: { case SIGNAL_MONITOR: {
BYTE sm_val; BYTE sm_val;
/* Zero-fill before reads so I2C failures return zeros, not stale data */
EP0BUF[0] = 0; EP0BUF[1] = 0; EP0BUF[2] = 0;
EP0BUF[3] = 0; EP0BUF[4] = 0; EP0BUF[5] = 0;
/* SNR: indirect regs 0x00-0x01 */ /* SNR: indirect regs 0x00-0x01 */
bcm_indirect_read(0x00, &EP0BUF[0]); bcm_indirect_read(0x00, &EP0BUF[0]);
bcm_indirect_read(0x01, &EP0BUF[1]); bcm_indirect_read(0x01, &EP0BUF[1]);
@ -1778,8 +1912,8 @@ BOOL handle_vendorcommand(BYTE cmd) {
BYTE dwell = (BYTE)wval; BYTE dwell = (BYTE)wval;
EP0BCL = 0; EP0BCL = 0;
SYNCDELAY; SYNCDELAY;
while (EP0CS & bmEPBUSY) if (!ep0_wait_data()) return TRUE;
; if (EP0BCL < 10) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_tune(); do_tune();
if (dwell > 0) if (dwell > 0)
delay(dwell); delay(dwell);
@ -1822,8 +1956,8 @@ BOOL handle_vendorcommand(BYTE cmd) {
case PARAM_SWEEP: case PARAM_SWEEP:
EP0BCL = 0; EP0BCL = 0;
SYNCDELAY; SYNCDELAY;
while (EP0CS & bmEPBUSY) if (!ep0_wait_data()) return TRUE;
; if (EP0BCL < 16) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_param_sweep(); do_param_sweep();
return TRUE; return TRUE;
@ -1831,8 +1965,8 @@ BOOL handle_vendorcommand(BYTE cmd) {
case ADAPTIVE_BLIND_SCAN: case ADAPTIVE_BLIND_SCAN:
EP0BCL = 0; EP0BCL = 0;
SYNCDELAY; SYNCDELAY;
while (EP0CS & bmEPBUSY) if (!ep0_wait_data()) return TRUE;
; if (EP0BCL < 18) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_adaptive_blind_scan(); do_adaptive_blind_scan();
return TRUE; return TRUE;
@ -1866,7 +2000,9 @@ BOOL handle_vendorcommand(BYTE cmd) {
EP0BUF[9] = sd_last_lock; EP0BUF[9] = sd_last_lock;
EP0BUF[10] = (config_status & BM_ARMED) ? 1 : 0; EP0BUF[10] = (config_status & BM_ARMED) ? 1 : 0;
EP0BUF[11] = sd_had_sync; EP0BUF[11] = sd_had_sync;
/* Reset counters if wValue=1 */ /* Reset counters if wValue=1.
* Non-atomic read+reset is safe: single-threaded main loop,
* ISR only sets got_sud (never touches diag counters). */
if (wval == 1) { if (wval == 1) {
sd_poll_count = 0; sd_poll_count = 0;
sd_overflow_count = 0; sd_overflow_count = 0;
@ -1960,6 +2096,27 @@ void hispeed_isr(void) __interrupt (HISPEED_ISR) {
CLEAR_HISPEED(); CLEAR_HISPEED();
} }
/* Software watchdog Timer0 ISR: fires every ~16.384ms.
* If the main loop stops kicking, cut LNB power for safety.
* wdt_armed states: 0=off, 1=armed, 2=fired (power was cut). */
void timer0_isr(void) __interrupt (1) {
if (wdt_armed != 1) return;
if (wdt_counter > 0) {
wdt_counter--;
} else {
/* Main loop stalled — cut LNB power for safety.
* IOA RMW race note: if the main loop is genuinely hung (which
* it must be for wdt_counter to reach 0), it is not executing
* IOA modifications. If by rare coincidence we interrupt an IOA
* RMW, the main loop's stale write re-enables power but then
* wdt_armed=2 prevents wdt_kick() from rearming, so the next
* ISR tick exits immediately and the main loop's own guard
* checks (BM_STARTED etc.) will prevent further I2C activity. */
IOA = (IOA & ~PIN_PWR_EN) | PIN_PWR_DIS;
wdt_armed = 2; /* fired — main loop must not re-arm */
}
}
/* ---------- Main ---------- */ /* ---------- Main ---------- */
void main(void) { void main(void) {
@ -2048,7 +2205,11 @@ void main(void) {
EA = 1; /* global interrupt enable */ EA = 1; /* global interrupt enable */
wdt_init(); /* start software watchdog (~2s timeout) */
while (TRUE) { while (TRUE) {
wdt_kick(); /* main loop alive — reset watchdog */
if (got_sud) { if (got_sud) {
handle_setupdata(); handle_setupdata();
got_sud = FALSE; got_sud = FALSE;