From 5b98085e4076755a0c46e261cbfb84b4371c547a Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Mon, 13 Jul 2026 02:44:01 -0700 Subject: [PATCH 01/15] fix(sband): revive nrst/rx_en/tx_en GPIO nodes in shared v5.dtsi Un-comments the sband_nrst/sband_rx_en/sband_tx_en devicetree nodes that have sat dead-commented in the shared v5-family base since before this reintegration effort. Verified against every board's actual LoRa pin usage (not just the comments): v5/v5c/v5d's LoRa config never claims gpio0 21/22, so the raw-GPIO mapping is conflict-free there. v5e's SX126x LoRa module does claim those same pins for its own tx/rx-enable, but v5e already redeclares sband_rx_en/sband_tx_en on the MCP23017 in its own .dts; devicetree node-path merging (last property wins -- the same mechanism v5e's file already relies on for its &lora0 override) makes v5e's MCP-based mapping take precedence, while sband_nrst (never overridden by v5e) is inherited from this shared block. Confirmed by an actual build for both v5e and v5d, not just reasoned about. Co-Authored-By: Claude Fable 5 --- .../proves_flight_control_board_v5.dtsi | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/boards/bronco_space/proves_flight_control_board_v5/proves_flight_control_board_v5.dtsi b/boards/bronco_space/proves_flight_control_board_v5/proves_flight_control_board_v5.dtsi index a5b8f30a..470da148 100644 --- a/boards/bronco_space/proves_flight_control_board_v5/proves_flight_control_board_v5.dtsi +++ b/boards/bronco_space/proves_flight_control_board_v5/proves_flight_control_board_v5.dtsi @@ -617,11 +617,20 @@ zephyr_udc0: &usbd { label = "PAYLOAD_BATT_ENABLE"; }; - /*sband_nrst: sband-nrst { + sband_nrst: sband-nrst { gpios = <&gpio0 17 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>; label = "SBAND_NRST"; }; + /* + * sband_rx_en / sband_tx_en on raw GPIOs 21/22 (below) are the baseline + * mapping for v5/v5c/v5d. v5e's SX126x LoRa module claims gpio0 21/22 for + * its own tx-enable/rx-enable (see proves_flight_control_board_v5e's &lora0 + * override), so v5e instead redeclares sband_rx_en/sband_tx_en on the + * MCP23017 (see its own gpio_outputs override) which supersedes the nodes + * below via standard devicetree node-path merging -- do not uncomment + * these two for v5e's benefit, they are already correctly handled there. + */ sband_rx_en: sband-rx-en { gpios = <&gpio0 21 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>; label = "SBAND_RX_EN"; @@ -630,7 +639,7 @@ zephyr_udc0: &usbd { sband_tx_en: sband-tx-en { gpios = <&gpio0 22 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>; label = "SBAND_TX_EN"; - };*/ + }; }; // Define GPIO inputs From 684b97f454f944e3d0d689dc93709996a293082e Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Mon, 13 Jul 2026 02:44:16 -0700 Subject: [PATCH 02/15] feat(sband): wire the sband instance and ComCcsdsSband subtopology into the topology Slice 3.1 of the S-Band reintegration plan. In instances.fpp: adds SBAND_STACK_SIZE (8 KB, dedicated heap-allocated stack via CONFIG_DYNAMIC_THREAD_ALLOC pool-fallback, documented inline), uncomments the sband instance (now using the new stack constant instead of the shared 4 KB pool default) and its gpioSbandNrst/RxEn/TxEn/IRQ driver instances, and adds a new gpioSbandBusy instance to drive the getBusyLine port PR 2 added. In topology.fpp: uncomments the ComCcsdsSband.Subtopology import, all sband/comDelaySband instance declarations, the comSplitter/ authenticationRouter connections into ComCcsds_CdhCore, the full CommunicationsSBandRadio connections block, the file downlink/uplink sband hooks, and the previously-reserved rate-group slots (10Hz [3] aggregator, [11] sband.run, [12] comDelaySband.run; 1Hz [1] comQueue.run, [4] commsBufferManager.schedIn -- all verified free on this branch before uncommenting). Adds a new getBusyLine -> gpioSbandBusy.gpioRead connection alongside the existing MyConnectionGraph GPIO wiring. In ReferenceDeploymentPackets.fppi: uncomments every existing sband/ ComCcsdsSband packet assignment (Radio, Health, HealthWarnings, HealthAuxiliary, Authenticate, Beacon, omit block) and adds the three new PR 2 fault-policy channels (RadioFaulted, ConsecutiveRadioFailures, RadioResetCount) to the Health packet. Co-Authored-By: Claude Fable 5 --- .../Top/ReferenceDeploymentPackets.fppi | 35 ++++---- .../ReferenceDeployment/Top/instances.fpp | 36 +++++++-- .../ReferenceDeployment/Top/topology.fpp | 80 ++++++++++--------- 3 files changed, 89 insertions(+), 62 deletions(-) diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi index eb1de9aa..9e73a8b6 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi @@ -25,7 +25,7 @@ telemetry packets ReferenceDeploymentPackets { ReferenceDeployment.powerMonitor.TotalPowerGenerated # Communications Data - # ComCcsdsSband.authenticatesband.CurrentSequenceNumber + ComCcsdsSband.authenticatesband.CurrentSequenceNumber ComCcsdsLora.authenticatelora.CurrentSequenceNumber ComCcsdsUart.authenticate.CurrentSequenceNumber @@ -49,8 +49,8 @@ telemetry packets ReferenceDeploymentPackets { lora.LastRssi lora.LastSnr lora.BytesSent -# sband.LastRssi -# sband.LastSnr + sband.LastRssi + sband.LastSnr } packet PowerMonitor id 11 group 2 { @@ -119,8 +119,8 @@ telemetry packets ReferenceDeploymentPackets { packet Health id 2 group 5 { ComCcsdsLora.comQueue.comQueueDepth ComCcsdsLora.commsBufferManager.HiBuffs -# ComCcsdsSband.comQueue.comQueueDepth -# ComCcsdsSband.commsBufferManager.HiBuffs + ComCcsdsSband.comQueue.comQueueDepth + ComCcsdsSband.commsBufferManager.HiBuffs CdhCore.cmdDisp.CommandsDispatched CdhCore.cmdDisp.CommandsDropped ReferenceDeployment.rateGroup50Hz.RgMaxTime @@ -134,12 +134,17 @@ telemetry packets ReferenceDeploymentPackets { ReferenceDeployment.stackMonitor.WorstThread ReferenceDeployment.stackMonitor.ThreadsBelowThreshold ReferenceDeployment.stackMonitor.SampleOverflow + # New in PR 3: SBand's fault-policy state (PR 2) -- radio fault state + # belongs in Health alongside the other component health/status channels. + sband.RadioFaulted + sband.ConsecutiveRadioFailures + sband.RadioResetCount } packet HealthWarnings id 3 group 5 { CdhCore.$health.PingLateWarnings ComCcsdsLora.commsBufferManager.NoBuffs - #ComCcsdsSband.commsBufferManager.NoBuffs + ComCcsdsSband.commsBufferManager.NoBuffs ComCcsdsUart.commsBufferManager.NoBuffs payloadBufferManager.NoBuffs ReferenceDeployment.rateGroup50Hz.RgCycleSlips @@ -150,7 +155,7 @@ telemetry packets ReferenceDeploymentPackets { packet HealthAuxiliary id 4 group 5 { ComCcsdsLora.comQueue.buffQueueDepth -# ComCcsdsSband.comQueue.buffQueueDepth + ComCcsdsSband.comQueue.buffQueueDepth payloadBufferManager.HiBuffs CdhCore.tlmSend.SendLevel } @@ -161,23 +166,23 @@ telemetry packets ReferenceDeploymentPackets { } packet Authenticate id 6 group 5 { - #ComCcsdsSband.authenticatesband.AuthenticatedPacketsCount + ComCcsdsSband.authenticatesband.AuthenticatedPacketsCount ComCcsdsLora.authenticatelora.AuthenticatedPacketsCount ComCcsdsUart.authenticate.AuthenticatedPacketsCount - #ComCcsdsSband.authenticatesband.RejectedPacketsCount + ComCcsdsSband.authenticatesband.RejectedPacketsCount ComCcsdsLora.authenticatelora.RejectedPacketsCount ComCcsdsUart.authenticate.RejectedPacketsCount - #ComCcsdsSband.authenticationRouter.ByPassedRouter + ComCcsdsSband.authenticationRouter.ByPassedRouter ComCcsdsLora.authenticationRouter.ByPassedRouter ComCcsdsUart.authenticationRouter.ByPassedRouter - #ComCcsdsSband.authenticationRouter.PassedRouter + ComCcsdsSband.authenticationRouter.PassedRouter ComCcsdsLora.authenticationRouter.PassedRouter ComCcsdsUart.authenticationRouter.PassedRouter - #ComCcsdsSband.authenticationRouter.FailedRouter + ComCcsdsSband.authenticationRouter.FailedRouter ComCcsdsLora.authenticationRouter.FailedRouter ComCcsdsUart.authenticationRouter.FailedRouter @@ -306,14 +311,14 @@ telemetry packets ReferenceDeploymentPackets { ComCcsdsUart.comQueue.buffQueueDepth ComCcsdsLora.commsBufferManager.EmptyBuffs -# ComCcsdsSband.commsBufferManager.EmptyBuffs + ComCcsdsSband.commsBufferManager.EmptyBuffs ComCcsdsUart.commsBufferManager.EmptyBuffs payloadBufferManager.EmptyBuffs ComCcsdsLora.commsBufferManager.TotalBuffs ComCcsdsLora.commsBufferManager.CurrBuffs -# ComCcsdsSband.commsBufferManager.TotalBuffs -# ComCcsdsSband.commsBufferManager.CurrBuffs + ComCcsdsSband.commsBufferManager.TotalBuffs + ComCcsdsSband.commsBufferManager.CurrBuffs payloadBufferManager.TotalBuffs payloadBufferManager.CurrBuffs diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp index 7fbfddd8..b12ad4bf 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp @@ -21,6 +21,19 @@ module ReferenceDeployment { module Default { constant QUEUE_SIZE = 10 constant STACK_SIZE = 4 * 1024 # Must match prj.conf thread stack size + + # S-Band reintegration plan (PR 3, Slice 3.1): the sband thread is the one + # thread in this deployment that needs more than the shared 4096-byte pool + # stack slot (see prj.conf's CONFIG_DYNAMIC_THREAD_ALLOC comment). Zephyr's + # thread-stack pool (CONFIG_DYNAMIC_THREAD_POOL_SIZE slots of + # CONFIG_DYNAMIC_THREAD_STACK_SIZE=4096 bytes each) serves every other + # active component's k_thread_create() request; CONFIG_DYNAMIC_THREAD_ALLOC=y + # lets ONLY an oversized request (like this one) fall back to a one-off + # boot-time k_malloc() instead of failing, while CONFIG_DYNAMIC_THREAD_PREFER_POOL=y + # keeps every other thread on the pool as before. Do not reuse this constant + # for other components -- each additional heap-allocated stack shrinks the + # margin the pool-fallback mechanism assumes. + constant SBAND_STACK_SIZE = 8 * 1024 } # ---------------------------------------------------------------------- @@ -223,18 +236,25 @@ module ReferenceDeployment { instance spiDriver: Zephyr.ZephyrSpiDriver base id 0x10071000 - #instance sband : Components.SBand base id 0x10072000 \ - # queue size Default.QUEUE_SIZE \ - # stack size Default.STACK_SIZE \ - # priority 10 + instance sband : Components.SBand base id 0x10072000 \ + queue size Default.QUEUE_SIZE \ + stack size Default.SBAND_STACK_SIZE \ + priority 10 + + instance gpioSbandNrst: Zephyr.ZephyrGpioDriver base id 0x10073000 - #instance gpioSbandNrst: Zephyr.ZephyrGpioDriver base id 0x10073000 + instance gpioSbandRxEn: Zephyr.ZephyrGpioDriver base id 0x10074000 - #instance gpioSbandRxEn: Zephyr.ZephyrGpioDriver base id 0x10074000 + instance gpioSbandTxEn: Zephyr.ZephyrGpioDriver base id 0x10075000 - #instance gpioSbandTxEn: Zephyr.ZephyrGpioDriver base id 0x10075000 + instance gpioSbandIRQ: Zephyr.ZephyrGpioDriver base id 0x10076000 - #instance gpioSbandIRQ: Zephyr.ZephyrGpioDriver base id 0x10076000 + # New in PR 3: drives SBand.getBusyLine (added PR 2). Reuses the existing + # rf2_io0 devicetree node (see boards/bronco_space's v5.dtsi gpio_inputs) -- + # rf2_io1 is used for IRQ below, rf2_io0 is the remaining free line of the + # 4-pin RF header on every board variant. Bench-confirm this pin assignment + # in HWIL Slice 3.2 (see SBAND-HWIL-PROCEDURE.md). + instance gpioSbandBusy: Zephyr.ZephyrGpioDriver base id 0x1007B000 instance dropDetector: Utilities.DropDetector base id 0x10077000 diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp index 0e475adb..d8943dbf 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp @@ -19,7 +19,7 @@ module ReferenceDeployment { import ComCcsdsLora.Subtopology import ComCcsdsUart.Subtopology import FileHandling.Subtopology - #import ComCcsdsSband.Subtopology + import ComCcsdsSband.Subtopology import Update.Subtopology # ---------------------------------------------------------------------- @@ -48,7 +48,7 @@ module ReferenceDeployment { instance detumbleManager instance imuManager instance bootloaderTrigger - #instance comDelaySband + instance comDelaySband instance downlinkDelay instance telemetryDelay instance burnwire @@ -59,11 +59,12 @@ module ReferenceDeployment { # For UART sideband communication instance comDriver instance spiDriver - #instance sband - #instance gpioSbandNrst - #instance gpioSbandRxEn - #instance gpioSbandTxEn - #instance gpioSbandIRQ + instance sband + instance gpioSbandNrst + instance gpioSbandRxEn + instance gpioSbandTxEn + instance gpioSbandIRQ + instance gpioSbandBusy instance face4LoadSwitch instance face0LoadSwitch instance face1LoadSwitch @@ -146,19 +147,19 @@ module ReferenceDeployment { CdhCore.events.PktSend -> comSplitterEvents.comIn comSplitterEvents.comOut-> ComCcsdsLora.comQueue.comPacketQueueIn[ComCcsds.Ports_ComPacketQueue.EVENTS] comSplitterEvents.comOut-> ComCcsdsUart.comQueue.comPacketQueueIn[ComCcsds.Ports_ComPacketQueue.EVENTS] - #comSplitterEvents.comOut-> ComCcsdsSband.comQueue.comPacketQueueIn[ComCcsds.Ports_ComPacketQueue.EVENTS] + comSplitterEvents.comOut-> ComCcsdsSband.comQueue.comPacketQueueIn[ComCcsds.Ports_ComPacketQueue.EVENTS] CdhCore.tlmSend.PktSend -> comSplitterTelemetry.comIn comSplitterTelemetry.comOut -> ComCcsdsLora.comQueue.comPacketQueueIn[ComCcsds.Ports_ComPacketQueue.TELEMETRY] comSplitterTelemetry.comOut -> ComCcsdsUart.comQueue.comPacketQueueIn[ComCcsds.Ports_ComPacketQueue.TELEMETRY] - #comSplitterTelemetry.comOut -> ComCcsdsSband.comQueue.comPacketQueueIn[ComCcsds.Ports_ComPacketQueue.TELEMETRY] + comSplitterTelemetry.comOut -> ComCcsdsSband.comQueue.comPacketQueueIn[ComCcsds.Ports_ComPacketQueue.TELEMETRY] # Router to Command Dispatcher ComCcsdsLora.authenticationRouter.commandOut -> CdhCore.cmdDisp.seqCmdBuff CdhCore.cmdDisp.seqCmdStatus -> ComCcsdsLora.authenticationRouter.cmdResponseIn - #ComCcsdsSband.authenticationRouter.commandOut -> CdhCore.cmdDisp.seqCmdBuff - #CdhCore.cmdDisp.seqCmdStatus -> ComCcsdsSband.authenticationRouter.cmdResponseIn + ComCcsdsSband.authenticationRouter.commandOut -> CdhCore.cmdDisp.seqCmdBuff + CdhCore.cmdDisp.seqCmdStatus -> ComCcsdsSband.authenticationRouter.cmdResponseIn ComCcsdsUart.authenticationRouter.commandOut -> CdhCore.cmdDisp.seqCmdBuff CdhCore.cmdDisp.seqCmdStatus -> ComCcsdsUart.authenticationRouter.cmdResponseIn @@ -177,20 +178,20 @@ module ReferenceDeployment { } - #connections CommunicationsSBandRadio { - # sband.allocate -> ComCcsdsSband.commsBufferManager.bufferGetCallee - # sband.deallocate -> ComCcsdsSband.commsBufferManager.bufferSendIn + connections CommunicationsSBandRadio { + sband.allocate -> ComCcsdsSband.commsBufferManager.bufferGetCallee + sband.deallocate -> ComCcsdsSband.commsBufferManager.bufferSendIn - # # ComDriver <-> ComStub (Uplink) - # sband.dataOut -> ComCcsdsSband.frameAccumulator.dataIn - # ComCcsdsSband.frameAccumulator.dataReturnOut -> sband.dataReturnIn + # ComDriver <-> FrameAccumulator (Uplink) + sband.dataOut -> ComCcsdsSband.frameAccumulator.dataIn + ComCcsdsSband.frameAccumulator.dataReturnOut -> sband.dataReturnIn - # ComStub <-> ComDriver (Downlink) - # ComCcsdsSband.framer.dataOut -> sband.dataIn - # sband.dataReturnOut -> ComCcsdsSband.framer.dataReturnIn - # sband.comStatusOut -> comDelaySband.comStatusIn - # comDelaySband.comStatusOut -> ComCcsdsSband.framer.comStatusIn - #} + # ComStub <-> ComDriver (Downlink) + ComCcsdsSband.framer.dataOut -> sband.dataIn + sband.dataReturnOut -> ComCcsdsSband.framer.dataReturnIn + sband.comStatusOut -> comDelaySband.comStatusIn + comDelaySband.comStatusOut -> ComCcsdsSband.framer.comStatusIn + } connections CommunicationsRadio { lora.allocate -> ComCcsdsLora.commsBufferManager.bufferGetCallee @@ -253,24 +254,24 @@ module ReferenceDeployment { rateGroup10Hz.RateGroupMemberOut[0] -> comDriver.schedIn rateGroup10Hz.RateGroupMemberOut[1] -> ComCcsdsUart.aggregator.timeout rateGroup10Hz.RateGroupMemberOut[2] -> ComCcsdsLora.aggregator.timeout - #rateGroup10Hz.RateGroupMemberOut[3] -> ComCcsdsSband.aggregator.timeout + rateGroup10Hz.RateGroupMemberOut[3] -> ComCcsdsSband.aggregator.timeout rateGroup10Hz.RateGroupMemberOut[4] -> peripheralUartDriver.schedIn rateGroup10Hz.RateGroupMemberOut[6] -> FileHandling.fileManager.schedIn rateGroup10Hz.RateGroupMemberOut[7] -> cmdSeq.schedIn rateGroup10Hz.RateGroupMemberOut[8] -> payloadSeq.schedIn rateGroup10Hz.RateGroupMemberOut[9] -> safeModeSeq.schedIn rateGroup10Hz.RateGroupMemberOut[10] -> downlinkDelay.run - #rateGroup10Hz.RateGroupMemberOut[11] -> sband.run - #rateGroup10Hz.RateGroupMemberOut[12] -> comDelaySband.run + rateGroup10Hz.RateGroupMemberOut[11] -> sband.run + rateGroup10Hz.RateGroupMemberOut[12] -> comDelaySband.run rateGroup10Hz.RateGroupMemberOut[13] -> dropDetector.schedIn # Slow rate (1Hz) rate group rateGroupDriver.CycleOut[Ports_RateGroups.rateGroup1Hz] -> rateGroup1Hz.CycleIn rateGroup1Hz.RateGroupMemberOut[0] -> ComCcsdsLora.comQueue.run - #rateGroup1Hz.RateGroupMemberOut[1] -> ComCcsdsSband.comQueue.run + rateGroup1Hz.RateGroupMemberOut[1] -> ComCcsdsSband.comQueue.run rateGroup1Hz.RateGroupMemberOut[2] -> CdhCore.$health.Run rateGroup1Hz.RateGroupMemberOut[3] -> ComCcsdsLora.commsBufferManager.schedIn - #rateGroup1Hz.RateGroupMemberOut[4] -> ComCcsdsSband.commsBufferManager.schedIn + rateGroup1Hz.RateGroupMemberOut[4] -> ComCcsdsSband.commsBufferManager.schedIn rateGroup1Hz.RateGroupMemberOut[5] -> watchdog.run rateGroup1Hz.RateGroupMemberOut[6] -> imuManager.run rateGroup1Hz.RateGroupMemberOut[7] -> telemetryDelay.runIn @@ -384,13 +385,14 @@ module ReferenceDeployment { peripheralUartDriver.deallocate -> payloadBufferManager.bufferSendIn } - #connections MyConnectionGraph { - # sband.spiSend -> spiDriver.SpiReadWrite - # sband.resetSend -> gpioSbandNrst.gpioWrite - # sband.txEnable -> gpioSbandTxEn.gpioWrite - # sband.rxEnable -> gpioSbandRxEn.gpioWrite - # sband.getIRQLine -> gpioSbandIRQ.gpioRead - #} + connections MyConnectionGraph { + sband.spiSend -> spiDriver.SpiReadWrite + sband.resetSend -> gpioSbandNrst.gpioWrite + sband.txEnable -> gpioSbandTxEn.gpioWrite + sband.rxEnable -> gpioSbandRxEn.gpioWrite + sband.getIRQLine -> gpioSbandIRQ.gpioRead + sband.getBusyLine -> gpioSbandBusy.gpioRead + } connections ComCcsds_FileHandling { # File Downlink <-> ComQueue @@ -399,11 +401,11 @@ module ReferenceDeployment { downlinkRepeater.multiOut[0] -> ComCcsdsUart.comQueue.bufferQueueIn[ComCcsds.Ports_ComBufferQueue.FILE] downlinkRepeater.multiOut[1] -> ComCcsdsLora.comQueue.bufferQueueIn[ComCcsds.Ports_ComBufferQueue.FILE] - #downlinkRepeater.multiOut[2] -> ComCcsdsSband.comQueue.bufferQueueIn[ComCcsds.Ports_ComBufferQueue.FILE] + downlinkRepeater.multiOut[2] -> ComCcsdsSband.comQueue.bufferQueueIn[ComCcsds.Ports_ComBufferQueue.FILE] ComCcsdsUart.comQueue.bufferReturnOut[ComCcsds.Ports_ComBufferQueue.FILE] -> downlinkRepeater.multiIn[0] ComCcsdsLora.comQueue.bufferReturnOut[ComCcsds.Ports_ComBufferQueue.FILE] -> downlinkRepeater.multiIn[1] - #ComCcsdsSband.comQueue.bufferReturnOut[ComCcsds.Ports_ComBufferQueue.FILE] -> downlinkRepeater.multiIn[2] + ComCcsdsSband.comQueue.bufferReturnOut[ComCcsds.Ports_ComBufferQueue.FILE] -> downlinkRepeater.multiIn[2] } @@ -412,8 +414,8 @@ module ReferenceDeployment { fileUplinkCollector.singleOut -> FileHandling.fileUplink.bufferSendIn FileHandling.fileUplink.bufferSendOut -> fileUplinkCollector.singleIn - #ComCcsdsSband.authenticationRouter.fileOut -> fileUplinkCollector.multiIn[2] - #fileUplinkCollector.multiOut[2] -> ComCcsdsSband.authenticationRouter.fileBufferReturnIn + ComCcsdsSband.authenticationRouter.fileOut -> fileUplinkCollector.multiIn[2] + fileUplinkCollector.multiOut[2] -> ComCcsdsSband.authenticationRouter.fileBufferReturnIn ComCcsdsUart.authenticationRouter.fileOut -> fileUplinkCollector.multiIn[1] fileUplinkCollector.multiOut[1] -> ComCcsdsUart.authenticationRouter.fileBufferReturnIn ComCcsdsLora.authenticationRouter.fileOut -> fileUplinkCollector.multiIn[0] From 865a644eba10d43f814efded0588e9e3dc623a7c Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Mon, 13 Jul 2026 02:44:28 -0700 Subject: [PATCH 03/15] feat(sband): production GPIO/SPI wiring and ComCcsdsSband state plumbing Uncomments the ComCcsdsSband PingEntries/SubtopologyTopologyDefs includes and TopologyState::comCcsdsSband field (subtopology state is empty -- no new plumbing needed beyond the include). In ReferenceDeploymentTopology.cpp: uncomments the sbandNrst/RxEn/TxEn gpio_dt_spec declarations and their .open() calls, the SPI cs_ctrl/config block, spiDriver.configure(), and sband.configureRadio() (discarded, matching SBand.cpp's own (void)configureRadio() convention -- a failed initial configure is not fatal at boot, SBandFaultPolicy surfaces it via events on first use instead). Fixes the pre-existing draft's misleading sbandTxEnIRQ name to sbandIrqGpio, and adds a new sbandBusyGpio spec for the PR 2 getBusyLine port. Both IRQ and BUSY reuse the existing rf2_io1/ rf2_io0 devicetree nodes (the two spare pins of the generic 4-pin RF header present on every board variant) rather than inventing new nodes; the BUSY assignment specifically is flagged inline as unverified against real hardware, to be bench-confirmed per SBAND-HWIL-PROCEDURE.md. In Main.cpp: uncomments the spi0 device binding (DEVICE_DT_GET(DT_NODELABEL(spi0))) and its TopologyState assignment, which SPand's SPI driver needs at boot. Co-Authored-By: Claude Fable 5 --- .../ReferenceDeployment/Main.cpp | 4 +- .../Top/ReferenceDeploymentTopology.cpp | 61 ++++++++++++------- .../Top/ReferenceDeploymentTopologyDefs.hpp | 18 +++--- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp index 20df9d57..f02bf23a 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp @@ -22,7 +22,7 @@ const struct device* ina219Sys = DEVICE_DT_GET(DT_NODELABEL(ina219_0)); const struct device* ina219Sol = DEVICE_DT_GET(DT_NODELABEL(ina219_1)); const struct device* serial = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart0)); const struct device* lora = DEVICE_DT_GET(DT_NODELABEL(lora0)); -// const struct device* spi0 = DEVICE_DT_GET(DT_NODELABEL(spi0)); +const struct device* spi0 = DEVICE_DT_GET(DT_NODELABEL(spi0)); const struct device* peripheral_uart = DEVICE_DT_GET(DT_NODELABEL(uart0)); const struct device* peripheral_uart1 = DEVICE_DT_GET(DT_NODELABEL(uart1)); const struct device* lsm6dso = DEVICE_DT_GET(DT_NODELABEL(lsm6dso0)); @@ -71,7 +71,7 @@ int main(int argc, char* argv[]) { // Object for communicating state to the topology ReferenceDeployment::TopologyState inputs; - // inputs.spi0Device = spi0; + inputs.spi0Device = spi0; // Flight Control Board device bindings inputs.ina219SysDevice = ina219Sys; diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp index e54eedff..13f67bff 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp @@ -26,10 +26,18 @@ static const struct gpio_dt_spec face5LoadSwitchGpio = GPIO_DT_SPEC_GET(DT_NODEL static const struct gpio_dt_spec payloadPowerLoadSwitchGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(payload_pwr_enable), gpios); static const struct gpio_dt_spec payloadBatteryLoadSwitchGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(payload_batt_enable), gpios); -// static const struct gpio_dt_spec sbandNrstGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(sband_nrst), gpios); -// static const struct gpio_dt_spec sbandRxEnGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(sband_rx_en), gpios); -// static const struct gpio_dt_spec sbandTxEnGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(sband_tx_en), gpios); -// static const struct gpio_dt_spec sbandTxEnIRQ = GPIO_DT_SPEC_GET(DT_NODELABEL(rf2_io1), gpios); +static const struct gpio_dt_spec sbandNrstGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(sband_nrst), gpios); +static const struct gpio_dt_spec sbandRxEnGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(sband_rx_en), gpios); +static const struct gpio_dt_spec sbandTxEnGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(sband_tx_en), gpios); +// rf2_io0/rf2_io1 are the two remaining lines of the generic 4-pin RF header +// shared by every board variant (see boards/bronco_space's v5.dtsi); this +// deployment repurposes them as the SX1280's IRQ/BUSY lines. rf2_io1 = IRQ +// carries forward the mapping this draft already used; rf2_io0 = BUSY is a +// PR 3 addition for SBand.getBusyLine (added PR 2) and has not been bench- +// verified against a schematic -- confirm on real hardware in HWIL Slice 3.2 +// (see SBAND-HWIL-PROCEDURE.md) before relying on it operationally. +static const struct gpio_dt_spec sbandIrqGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(rf2_io1), gpios); +static const struct gpio_dt_spec sbandBusyGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(rf2_io0), gpios); // Allows easy reference to objects in FPP/autocoder required namespaces using namespace ReferenceDeployment; @@ -89,10 +97,11 @@ void configureTopology() { gpioface5LS.open(face5LoadSwitchGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); gpioPayloadPowerLS.open(payloadPowerLoadSwitchGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); gpioPayloadBatteryLS.open(payloadBatteryLoadSwitchGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); - // gpioSbandNrst.open(sbandNrstGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); - // gpioSbandRxEn.open(sbandRxEnGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); - // gpioSbandTxEn.open(sbandTxEnGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); - // gpioSbandIRQ.open(sbandTxEnIRQ, Zephyr::ZephyrGpioDriver::GpioConfiguration::IN); + gpioSbandNrst.open(sbandNrstGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); + gpioSbandRxEn.open(sbandRxEnGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); + gpioSbandTxEn.open(sbandTxEnGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); + gpioSbandIRQ.open(sbandIrqGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::IN); + gpioSbandBusy.open(sbandBusyGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::IN); } // Public functions for use in main program are namespaced with deployment name ReferenceDeployment @@ -126,21 +135,27 @@ void setupTopology(const TopologyState& state) { lora.start(state.loraDevice, Zephyr::TransmitState::DISABLED); comDriver.configure(state.uartDevice, state.baudRate); - // static struct spi_cs_control cs_ctrl = { - // .gpio = GPIO_DT_SPEC_GET_BY_IDX(DT_NODELABEL(spi0), cs_gpios, 1), - // .delay = 0U, /* us to wait after asserting CS before transfer */ - // .cs_is_gpio = true, - // }; - - // struct spi_config cfg = { - // .frequency = 100000, // 100 KHz -- sx1280 has maximum 18.18 MHz -- there is a 12MHz oscillator on-board - // .operation = SPI_WORD_SET(8), - // .slave = 0, - // .cs = cs_ctrl, - // .word_delay = 0, - // }; - // spiDriver.configure(state.spi0Device, cfg); - // sband.configureRadio(); + // S-Band SPI: spi0 is shared with the onboard SD card (cs_gpios index 0); + // the SX1280 uses the second chip-select on the same bus (cs_gpios index 1, + // gpio0 7 -- see boards/bronco_space's v5.dtsi &spi0 node). + static struct spi_cs_control sbandCsCtrl = { + .gpio = GPIO_DT_SPEC_GET_BY_IDX(DT_NODELABEL(spi0), cs_gpios, 1), + .delay = 0U, /* us to wait after asserting CS before transfer */ + .cs_is_gpio = true, + }; + + struct spi_config sbandSpiCfg = { + .frequency = 100000, // 100 KHz -- sx1280 has maximum 18.18 MHz -- there is a 12MHz oscillator on-board + .operation = SPI_WORD_SET(8), + .slave = 0, + .cs = sbandCsCtrl, + .word_delay = 0, + }; + spiDriver.configure(state.spi0Device, sbandSpiCfg); + // Discarding the Status here matches SBand.cpp's own (void)configureRadio() + // call site; a failed initial configure is not fatal at boot -- SBandFaultPolicy + // (PR 2) surfaces RadioNotConfigured/RadioLibFailed events on first use instead. + (void)sband.configureRadio(); // UART from the board to the payload peripheralUartDriver.configure(state.peripheralUart, state.peripheralBaudRate); diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp index 8d5be24a..a2720a3e 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp @@ -11,7 +11,7 @@ // Subtopology PingEntries includes #include "PROVESFlightControllerReference/ComCcsdsLora/PingEntries.hpp" -// #include "PROVESFlightControllerReference/ComCcsdsSband/PingEntries.hpp" +#include "PROVESFlightControllerReference/ComCcsdsSband/PingEntries.hpp" #include "Svc/Subtopologies/CdhCore/PingEntries.hpp" #include "Svc/Subtopologies/DataProducts/PingEntries.hpp" // Replaced with override section below @@ -19,7 +19,7 @@ // SubtopologyTopologyDefs includes #include "PROVESFlightControllerReference/ComCcsdsLora/SubtopologyTopologyDefs.hpp" -// #include "PROVESFlightControllerReference/ComCcsdsSband/SubtopologyTopologyDefs.hpp" +#include "PROVESFlightControllerReference/ComCcsdsSband/SubtopologyTopologyDefs.hpp" #include "PROVESFlightControllerReference/ComCcsdsUart/SubtopologyTopologyDefs.hpp" #include "Svc/Subtopologies/CdhCore/SubtopologyTopologyDefs.hpp" #include "Svc/Subtopologies/FileHandling/SubtopologyTopologyDefs.hpp" @@ -111,13 +111,13 @@ namespace ReferenceDeployment { * autocoder. The contents are entirely up to the definition of the project. This deployment uses subtopologies. */ struct TopologyState { - const device* uartDevice; //!< UART device path for communication - const device* spi0Device; //!< Spi device path for s-band LoRa module - const device* loraDevice; //!< LoRa device path for communication - ComCcsdsLora::SubtopologyState comCcsdsLora; //!< Subtopology state for ComCcsdsLora - // ComCcsdsSband::SubtopologyState comCcsdsSband; //!< Subtopology state for ComCcsdsSband - U32 baudRate; //!< Baud rate for UART communication - CdhCore::SubtopologyState cdhCore; //!< Subtopology state for CdhCore + const device* uartDevice; //!< UART device path for communication + const device* spi0Device; //!< Spi device path for s-band LoRa module + const device* loraDevice; //!< LoRa device path for communication + ComCcsdsLora::SubtopologyState comCcsdsLora; //!< Subtopology state for ComCcsdsLora + ComCcsdsSband::SubtopologyState comCcsdsSband; //!< Subtopology state for ComCcsdsSband + U32 baudRate; //!< Baud rate for UART communication + CdhCore::SubtopologyState cdhCore; //!< Subtopology state for CdhCore // ComCcsdsUart::SubtopologyState comCcsdsUart; //!< Subtopology state for ComCcsds const device* peripheralUart; U32 peripheralBaudRate; From de78354d4a75d7029514f9f56a3d2c2ab112c9d9 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Mon, 13 Jul 2026 02:44:43 -0700 Subject: [PATCH 04/15] docs(plan): record Slice 3.1 as-built board-support findings Corrects the plan's "CI builds for v5c/v5d/v5e" claim (no such CI matrix exists; only v5e is CI-built today) and documents the actual per-board devicetree investigation: the gpio0 21/22 conflict between v5e's LoRa tx/rx-enable and the dead-commented sband rx_en/tx_en block, why v5e's MCP-based mapping avoids it, and why no board needed to be scoped out once the pin usage was checked against real hardware rather than assumed from the comments. Notes v5e and v5d were build-verified; v5/v5c were not (outside this task's gates) but are expected to build cleanly by the same reasoning. Co-Authored-By: Claude Fable 5 --- S-BAND-REINTEGRATION-PLAN.md | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/S-BAND-REINTEGRATION-PLAN.md b/S-BAND-REINTEGRATION-PLAN.md index da313188..a3854e36 100644 --- a/S-BAND-REINTEGRATION-PLAN.md +++ b/S-BAND-REINTEGRATION-PLAN.md @@ -159,6 +159,68 @@ New `SBAND_STACK_SIZE = 8 * 1024` for the sband instance only (comment explainin the pool-fallback mechanism). CI builds for v5c/v5d/v5e; existing integration + day-in-the-life suites green. +> **BUILT 2026-07-13 (branch `feat/sband-pr3-topology`).** Two corrections to +> the paragraph above, found during implementation: +> +> - **CI does not actually matrix-build multiple boards today.** `.github/workflows/ci.yaml` +> runs a single `make generate && make build`, and the board is whatever +> `settings.ini`'s `BOARD=` line says (`proves_flight_control_board_v5e/rp2350a/m33`). +> There is no per-board CI job for v5c/v5d/v5(base). This PR does not add one +> (out of scope per D7 / "no existing pattern" guidance below) — v5d was +> build-verified locally by swapping `settings.ini` and rerunning `make +> generate && make build`, then reverting. +> - **No per-board FPP/CMake conditionalization mechanism exists in this repo** +> (topology.fpp/instances.fpp compile identically for every board; the one +> `cameraHandler`/"NOT ALL SATS HAVE CAMERAS" comment in +> `ReferenceDeploymentTopology.cpp` is documentation, not a real `#if` +> guard). So "un-comment the topology" is necessarily a **global** change — +> every board that builds this topology now instantiates `sband` and must +> resolve its devicetree nodes at compile time, or the build fails outright. +> +> **Board hardware findings (from the checked-in device trees, not assumed):** +> `boards/bronco_space/proves_flight_control_board_v5/proves_flight_control_board_v5.dtsi` +> had a `sband_nrst`/`sband_rx_en`/`sband_tx_en` block dead-commented (C-style +> comment) since before this effort, wired to raw GPIOs 17/21/22. Checking +> every board variant's actual pin usage (not just the comments) found: +> - `gpio0 17` (nrst): unused anywhere else on any board variant. Safe to revive. +> - `gpio0 21`/`22` (rx_en/tx_en): **conflict on v5e only** — v5e's SX126x LoRa +> module (`&lora0` override in +> `proves_flight_control_board_v5e_rp2350a_m33.dts`) already claims +> `tx-enable-gpios`/`rx-enable-gpios` on those exact pins. This is why v5e's +> hardware revision moved S-Band's RX/TX enables onto the MCP23017 expander +> instead (`sband_rx_en`/`sband_tx_en` on `mcp23017` pins 4/7, already present +> in v5e's own `.dts` before this PR). v5, v5c, and v5d all use the base +> (SX1276) `&lora0` config, which claims neither pin — no conflict there. +> - IRQ/BUSY: the 4-pin generic `RF2_IO0..3` header on the MCP (present, +> unmodified, on every board) had 2 pins repurposed for RX/TX-enable on v5e +> only, leaving `rf2_io0`/`rf2_io1` free and identical across all four board +> variants. This deployment's C++ (`ReferenceDeploymentTopology.cpp`) binds +> `rf2_io1` → IRQ (carrying forward this draft's pre-existing choice) and +> `rf2_io0` → BUSY (new for PR 3, since `getBusyLine` is a PR 2 addition). +> **The BUSY pin assignment is inferred from the schematic's spare-pin count, +> not confirmed against a wiring diagram — bench-verify in Slice 3.2.** +> +> **Resolution:** uncommented `sband_nrst`/`sband_rx_en`/`sband_tx_en` in the +> shared `v5.dtsi` (not per-board). Because devicetree node-path merging is +> "last property wins" — the same mechanism v5e's own `.dts` already relies on +> to override the inherited `&lora0` node — v5e's pre-existing MCP-based +> `sband_rx_en`/`sband_tx_en` declarations transparently take precedence over +> the newly-uncommented raw-GPIO ones, while `sband_nrst` (which v5e never +> overrides) is inherited from the shared block. This was **verified by an +> actual build**, not just reasoned about: `make generate && make build` +> succeeded for both v5e (mandatory) and v5d (attempted), with no devicetree +> duplicate-node or missing-nodelabel errors on either. **v5 (base) and v5c +> were not build-tested** (outside this task's verification gates) but are +> expected to build cleanly by the same reasoning — they inherit the same +> uncommented block unmodified and have no lora/pin conflicts. +> +> No board needed to be scoped out. Had a genuine per-board hardware gap +> existed (e.g. a board truly missing a required GPIO with no safe fallback), +> the right move would have been to stop and present options (DT stub aliases +> vs. a first-ever `CONFIG_BOARD_*`-gated C++ guard vs. accepting that board's +> breakage) rather than invent per-board FPP conditionalization that has no +> precedent in this codebase — that fork did not materialize here. + ### Slice 3.2 — HWIL functional pass (bench, v5e) - Downlink: TM frames received over the S-Band link end-to-end. - Uplink: command through `ComCcsdsSband.authenticationRouter` → `cmdDisp` executes. From 0a919626b0a498ec3b9c47940c3fa538e2f7f171 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Mon, 13 Jul 2026 02:44:55 -0700 Subject: [PATCH 05/15] docs(hwil): author S-Band HWIL bench procedures for Slices 3.2-3.4 New PROVESFlightControllerReference/test/hwil/SBAND-HWIL-PROCEDURE.md, covering the functional pass, fault injection (including the boot-failure case from Slice 1.2), and 24 h dual-radio soak gate that this environment cannot run (no bench hardware). Every step references real channel/ event/command names pulled from the generated v5e dictionary, not assumed F Prime conventions. Flags the two open bench-only unknowns this PR could not resolve in software: the BUSY-line pin assignment (rf2_io0, inferred not confirmed) and the absence of any onboard S-Band frame/byte counters to directly evidence the soak's "RX/TX counters advance" gate (ground-side + authentication-router counters are the best available proxy). Explicitly marked NOT RUN throughout. Co-Authored-By: Claude Fable 5 --- .../test/hwil/SBAND-HWIL-PROCEDURE.md | 307 ++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 PROVESFlightControllerReference/test/hwil/SBAND-HWIL-PROCEDURE.md diff --git a/PROVESFlightControllerReference/test/hwil/SBAND-HWIL-PROCEDURE.md b/PROVESFlightControllerReference/test/hwil/SBAND-HWIL-PROCEDURE.md new file mode 100644 index 00000000..760acf5e --- /dev/null +++ b/PROVESFlightControllerReference/test/hwil/SBAND-HWIL-PROCEDURE.md @@ -0,0 +1,307 @@ +# S-Band HWIL Bench Procedures (Slices 3.2 – 3.4) + +**Status: NOT RUN.** This document was authored alongside the PR 3 topology +re-enable (`feat/sband-pr3-topology`) in an environment with no S-Band +hardware, no bench rig, and no ability to power-cycle real flight boards. +Everything below is a step-by-step procedure for whoever runs the bench — +none of it has been executed. Slice 3.1 (topology wiring, this PR) was +verified by software build/dictionary checks only; see +`S-BAND-REINTEGRATION-PLAN.md`'s Slice 3.1 as-built note for what that +covered and what it explicitly did not. + +Channel, event, and command names below are taken directly from the +generated `ReferenceDeploymentTopologyDictionary.json` for the v5e build (the +mandatory board per Slice 3.1) — grep the dictionary for the exact strings if +in doubt; do not rely on memory of F Prime naming conventions. + +## Prerequisites + +- v5e flight control board (mandatory bench target for these procedures; see + the Slice 3.1 as-built note in `S-BAND-REINTEGRATION-PLAN.md` for why v5e is + the board with a fully-resolved, non-conflicting S-Band GPIO map). +- A second UHF-side ground station link (LoRa) live and operating normally + throughout every procedure below — the D3 invariant under test is that + S-Band failures never take UHF down with them. +- GDS/YAMCS session with the v5e dictionary loaded, command uplink capability + on both `ComCcsdsSband.authenticationRouter` and + `ComCcsdsLora.authenticationRouter`. +- Bench access to the SPI0 bus (chip-select index 1, `gpio0 7` per + `boards/bronco_space/proves_flight_control_board_v5/proves_flight_control_board_v5.dtsi`'s + `&spi0` node) and the BUSY line, for the fault-injection procedure. +- Confirm firmware under test is this branch's build + (`ReferenceDeployment.version` / `CdhCore.version` telemetry) and that + `ReferenceDeployment.sband` appears live in the loaded dictionary (it will + not, on any firmware image built before this PR). + +## Open bench-only unknown to resolve before/during Slice 3.2 + +PR 3's software-only verification could not confirm the physical pin +assignment for the S-Band BUSY line. `ReferenceDeploymentTopology.cpp` binds +`SBand.getBusyLine` to devicetree node `rf2_io0` and `SBand.getIRQLine` to +`rf2_io1` — both are the two spare pins of the generic 4-pin `RF2_IO0..3` +header on the MCP23017 expander (see the plan doc's Slice 3.1 note for the +full reasoning). The IRQ assignment (`rf2_io1`) carries forward this +deployment's pre-existing draft choice; the BUSY assignment (`rf2_io0`) is +new for PR 3 and is an inference from "which pins are left," not a confirmed +schematic reading. **Before trusting BUSY-driven behavior in Slice 3.3, +verify with a meter/scope that toggling the SX1280's physical BUSY pin +changes `rf2_io0`'s logic level** (e.g. observe `SBand`'s behavior change, or +probe the MCP GPIO directly). If the mapping is backwards, swap the +`DT_NODELABEL` arguments in `ReferenceDeploymentTopology.cpp`'s +`sbandBusyGpio`/`sbandIrqGpio` declarations and re-run. + +--- + +## Slice 3.2 — Functional pass (bench, v5e) + +Goal: prove the full downlink and uplink data paths work end-to-end over a +real S-Band RF link, and that the sband thread is actually running with the +stack size PR 3 gave it. + +### 3.2.1 — Boot and thread sanity + +1. Flash the v5e build from this branch. Power on. +2. Confirm no FATAL boot event and `ReferenceDeployment.startupManager.BootCount` + increments by exactly 1 from its last known value (no unexpected reboot + during init). +3. Poll `ReferenceDeployment.stackMonitor.WorstThread` / + `ReferenceDeployment.stackMonitor.MinFreeBytes` for several ticks. Because + `StackMonitor` only reports the single worst thread per 1 Hz tick (see + `Components/StackMonitor/StackMonitor.fpp`), the sband thread will not + necessarily appear here unless it is the worst one — that's expected. To + directly confirm the sband thread exists and was sized correctly, use a + ground-side or serial-console dump of `k_thread_foreach` output (or add a + temporary breakpoint/log) and confirm a thread whose name matches the + `sband` F Prime instance has an 8192-byte stack (`Default.SBAND_STACK_SIZE` + from `instances.fpp`), not the shared 4096-byte pool size other threads + get. +4. Confirm no `Components::StackMonitor::StackLow` event fires for the sband + thread during quiescent boot (a low-water event this early would indicate + the 8 KB budget is already under-sized before any RX/TX traffic). + +### 3.2.2 — Downlink (TM frames over S-Band) + +1. From the ground station, confirm nominal signal reception on the S-Band + frequency/modulation configured via `ReferenceDeployment.sband`'s + parameters (`DATA_RATE`, `CODING_RATE`, `BANDWIDTH_TX`, `BANDWIDTH_RX` — + see `Components/SBand/SBand.fpp`). +2. Command `ReferenceDeployment.sband.TRANSMIT(ENABLED)`. Confirm the command + is accepted (`CdhCore.cmdDisp.CommandsDispatched` increments, no + `CommandsDropped`). +3. Confirm the ground S-Band receiver captures valid CCSDS TM frames + (physical/RF-layer proof of downlink; there is no onboard per-frame byte + counter for S-Band specifically — `ComCcsdsSband`'s internal framer/ + frameAccumulator/apidManager components carry no telemetry of their own, + unlike `lora.BytesSent`/`lora.BytesReceived` on the LoRa driver. Treat + ground-side frame receipt as the primary evidence for this step). +4. Confirm `ReferenceDeployment.sband.LastRssi` / `LastSnr` are populating + with plausible values (these update only "on change" — i.e. only when a + packet round-trips through the radio, so watch for them changing over + time, not just a single non-zero snapshot). + +### 3.2.3 — Uplink (command through S-Band) + +1. From the ground station, send a valid authenticated command frame over + S-Band targeting any safe, observable command (e.g. a telemetry-only + command, not `RESET_RADIO` yet). +2. Confirm `ComCcsdsSband.authenticatesband.AuthenticatedPacketsCount` + increments and `ComCcsdsSband.authenticatesband.CurrentSequenceNumber` + advances. +3. Confirm `ComCcsdsSband.authenticationRouter.PassedRouter` increments and + the command actually executes (dispatched through + `ComCcsdsSband.authenticationRouter.commandOut` → `CdhCore.cmdDisp` per + the `ComCcsds_CdhCore` connections in `topology.fpp`) — verify via + `CdhCore.cmdDisp.CommandsDispatched` and the command's own expected + side-effect (e.g. a telemetry value change, an EVR). +4. Send one deliberately invalid/replayed frame; confirm + `ComCcsdsSband.authenticatesband.RejectedPacketsCount` or + `ComCcsdsSband.authenticationRouter.FailedRouter` increments instead of + the command executing, and that no crash/reset results. + +### 3.2.4 — Exit criteria + +- Downlink TM confirmed on the ground receiver. +- At least one uplinked command executed end-to-end through + `ComCcsdsSband.authenticationRouter` → `cmdDisp`. +- sband thread confirmed running with an 8 KB (not 4 KB) stack. +- No FATAL events, no unexpected `BootCount` increments, during the whole pass. + +--- + +## Slice 3.3 — Fault injection (the #122 repro, now with an expected answer) + +Goal: prove the D3 invariant (`S-BAND-REINTEGRATION-PLAN.md`) — a wedged +radio degrades to "no S-Band," never to a spacecraft reset, queue backpressure, +or a dead UHF link — and prove the ground-recovery path works. + +### 3.3.1 — Baseline + +1. With the board powered and S-Band operating nominally (per Slice 3.2), + confirm `ReferenceDeployment.sband.RadioFaulted = false`, + `ConsecutiveRadioFailures = 0`, `RadioResetCount` at its current baseline. + confirm the UHF/LoRa link is also nominal (send/receive a command over + `ComCcsdsLora.authenticationRouter` and confirm it executes) — this is the + "before" baseline for the isolation check in 3.3.3. + +### 3.3.2 — Inject the fault + +1. Physically disconnect the SPI0 bus chip-select-1 line or the BUSY line to + the SX1280 (see Prerequisites) while S-Band is mid-transmit or mid-receive. +2. Expect, in order: + - A bounded run of `Components::SBand::RadioLibFailed` and/or + `RadioNotConfigured` events (each individually throttled — see + `SBand.fpp`'s `throttle 2`/`throttle 3` — so do not expect an unbounded + flood). + - After exactly `SBandFaultPolicy::CONSECUTIVE_FAILURE_LIMIT` (= 5, see + `Components/SBand/SBandFaultPolicy.hpp`) consecutive failed radio + operations: one `RadioResetRequested(consecutiveFailures=5)` event and + `ReferenceDeployment.sband.ConsecutiveRadioFailures` telemetry reflecting + the count at the moment of the request (policy resets the counter after + the reset attempt completes). + - This nRST-reset-and-retry cycle repeats. After + `SBandFaultPolicy::RESET_ATTEMPT_LIMIT` (= 3) resets without an + intervening successful operation: one `RadioFaultLatched(resetCount=3)` + event, and `ReferenceDeployment.sband.RadioFaulted` telemetry flips to + `true` and stays there (latched — no further reset attempts, per D3). +3. Confirm `ReferenceDeployment.sband.RadioResetCount` reflects the resets + actually performed (bounded at 3 for this one fault episode, per the + latch). + +### 3.3.3 — Confirm isolation (the actual point of this test) + +While `RadioFaulted = true`: + +1. **No watchdog reset.** Confirm `ReferenceDeployment.watchdog.WatchdogTransitions` + does not increment during or after the fault sequence, and + `ReferenceDeployment.startupManager.BootCount` does not increment (i.e. + the board never rebooted). +2. **No queue overflow / backpressure.** Confirm + `ComCcsdsSband.comQueue.comQueueDepth` / + `ComCcsdsSband.commsBufferManager.HiBuffs` / + `ComCcsdsSband.commsBufferManager.NoBuffs` do not show unbounded growth — + FAULTED means `SBand`'s `run`/`dataIn` calls become no-ops that still + return buffers immediately (per Slice 2.7 in the plan doc), not a stall. +3. **UHF link still up.** Send a command over + `ComCcsdsLora.authenticationRouter` and confirm it executes normally + (`CdhCore.cmdDisp.CommandsDispatched` increments, + `ComCcsdsLora.authenticationRouter.PassedRouter` increments) — this is the + core D3 assertion: an S-Band hardware fault must be invisible to UHF. +4. Confirm `ReferenceDeployment.stackMonitor` shows no new `StackLow` event + for any thread during the fault episode (a wedged SPI/BUSY line should not + manifest as a stack problem; if it does, that is a new bug, not the + expected fault path). + +### 3.3.4 — Ground recovery + +1. Physically restore the SPI0/BUSY connection. +2. Command `ReferenceDeployment.sband.RESET_RADIO()`. +3. Expect exactly one `Components::SBand::RadioFaultCleared` event, + `ReferenceDeployment.sband.RadioFaulted` returns to `false`, and a + subsequent radio operation (e.g. re-run Slice 3.2.2's downlink check) + succeeds. +4. Confirm `ConsecutiveRadioFailures` reset to 0 after the successful + recovery. + +### 3.3.5 — Boot-failure case (Slice 1.2 resolution, verify here since it needs a real reboot) + +This exercises the companion PR 1 finding (`S-BAND-REINTEGRATION-PLAN.md` +Slice 1.2): a stack-allocation failure at task-start time must be loud, not a +silent limp. + +1. Build a **temporary, do-not-merge** variant of this firmware with + `Default.SBAND_STACK_SIZE` in `instances.fpp` set to an impossible value + (e.g. larger than `CONFIG_DYNAMIC_THREAD_POOL_SIZE * CONFIG_DYNAMIC_THREAD_STACK_SIZE` + combined, or simply larger than available RAM — see `prj.conf`'s + `CONFIG_DYNAMIC_THREAD_ALLOC`/`CONFIG_DYNAMIC_THREAD_POOL_SIZE` comments + for the current budget). +2. Flash and boot this variant on the bench (not on a board anyone cares + about recovering easily — expect a boot loop or a hung board if this goes + wrong). +3. Expect: a FATAL event logged (task-start stack allocation failure, + surfaced per the fprime-zephyr `ZephyrTask::start()` logging fix referenced + in Slice 1.2), followed by a watchdog-driven reset (FatalHandler stops + watchdog feed → external WDT starves → reset) — **not** a silent hang and + **not** a partial boot with S-Band quietly missing. +4. Confirm `ReferenceDeployment.startupManager.BootCount` increments, + confirming the reset actually happened. +5. Discard this temporary build; do not merge or flash it outside this one + check. + +### 3.3.6 — Exit criteria + +- Fault sequence matches the expected EVR/telemetry sequence in 3.3.2 exactly + (bounded EVRs → reset requests → latched fault, no more, no less). +- No watchdog reset, no queue overflow, UHF unaffected, during the fault. +- `RESET_RADIO` recovers the radio fully after hardware is restored. +- Impossible-stack-size boot variant produces a loud FATAL + reset, not a + silent failure. + +--- + +## Slice 3.4 — 24 h dual-radio soak (PR 3 merge gate) + +Goal: the actual merge gate from `S-BAND-REINTEGRATION-PLAN.md`. Run this +last, only after 3.2 and 3.3 both pass cleanly. + +### Setup + +- Flash this branch's build (the real one, not the Slice 3.3.5 broken + variant) on v5e. +- Both radios active: S-Band and UHF/LoRa both transmitting/receiving + periodic traffic for the entire window (not just at the start/end). +- Baseline every gate metric below *before* starting the clock, using the PR + 1 baseline build (StackMonitor was introduced in PR 1; if a PR 1 baseline + soak log is not already on hand, capture one run of comparable length on + the current `main` — pre-PR-3 — build first so there's something to diff + against). +- Duration: **≥ 24 hours continuous**, no manual intervention. + +### Merge gates (all must hold for the full window) + +1. **Zero unexpected resets.** `ReferenceDeployment.startupManager.BootCount` + does not increment beyond any deliberately-commanded reboots (there + should be none), and `ReferenceDeployment.watchdog.WatchdogTransitions` + stays flat. +2. **sband stack high-water < 70 % of 8 KB.** Because `StackMonitor` only + reports the single worst thread per tick + (`ReferenceDeployment.stackMonitor.WorstThread` / + `MinFreeBytes`), do not rely on it alone to find the sband thread's own + number if some other thread is worse that tick — capture the full + `k_thread_foreach` dump periodically (or add a temporary per-thread dump + command) and specifically track the sband thread's minimum free-bytes + over the whole window. 70 % of 8192 bytes = 5734 bytes used is the + trigger threshold (i.e. free bytes must stay above 2458 bytes, + `8192 * 0.30`). If this trips, **do not merge** — re-open the option-C + fallback review referenced in the plan doc's "Fallback trigger" note + instead. +3. **No other thread's watermark regresses vs. the PR 1 baseline.** Diff + every thread's minimum free-bytes over the run against the PR 1 baseline + capture from Setup above. sband itself is exempt (it didn't exist in the + PR 1 baseline); every other thread should show no meaningful regression — + a regression here would mean sband's presence (extra RAM pressure, + scheduling contention) is hurting unrelated threads. +4. **Heap watermark flat after init.** Confirm no heap-growth trend for the + duration of the run — this is meant to prove `RADIOLIB_STATIC_ONLY=1` + (PR 2, Slice 2.10) is actually preventing in-flight RadioLib heap churn. + If the board exposes a heap-usage telemetry/debug hook, sample it + periodically; at minimum, confirm no `Fw::MallocAllocator`-related + failures/EVRs appear and no `AllocationFailed` events fire on `sband` + over the window. +5. **RX/TX counters advance the whole window.** As noted in Slice 3.2.2, + there is no onboard per-frame byte/packet counter for S-Band specifically. + Use `ComCcsdsSband.authenticatesband.CurrentSequenceNumber` / + `AuthenticatedPacketsCount` (uplink) advancing steadily, plus ground-side + confirmation that downlink TM frames keep arriving throughout the 24 h + (not just at the start), as the two lines of evidence for this gate. If + this gap (no onboard TX/RX counters for S-Band) is judged worth closing, + file it as a follow-up rather than blocking this soak on adding new + telemetry. + +### Exit criteria + +All five gates hold for the full ≥24 h window with zero manual intervention. +On pass: merge PR 3, then follow `S-BAND-REINTEGRATION-PLAN.md`'s +"Post-merge" step (update #122/#299, file the throughput-tuning follow-up). +On any gate failure: do not merge; capture the failure telemetry/EVR log, +open a new issue with the specific gate and observed values, and revisit +before re-attempting the soak. From e320d1b880e3206a7d7624055d3c2ce7bebd9206 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:22:01 -0700 Subject: [PATCH 06/15] fix(sband): raise TlmPacketizer hash buckets 202->256 for S-Band channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-enabling the S-Band topology added ~18 telemetry channels, pushing the deployment's total to 214 — over the fixed TLMPACKETIZER_HASH_BUCKETS=202. TlmPacketizer::findBucket boot-asserts (bucket exhaustion) the moment the channel count exceeds the bucket pool, BusFaulting before any TM is emitted. Found on HWIL: main and PR1 boot clean, PR3 crashed 100% reproducibly at TlmPacketizer.cpp:180. 256 covers 214 with margin. Co-Authored-By: Claude Fable 5 --- .../project/config/TlmPacketizerCfg.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/PROVESFlightControllerReference/project/config/TlmPacketizerCfg.hpp b/PROVESFlightControllerReference/project/config/TlmPacketizerCfg.hpp index e2345b66..0bf59404 100644 --- a/PROVESFlightControllerReference/project/config/TlmPacketizerCfg.hpp +++ b/PROVESFlightControllerReference/project/config/TlmPacketizerCfg.hpp @@ -25,8 +25,10 @@ static const FwChanIdType TLMPACKETIZER_HASH_MOD_VALUE = // Should be set to a little below the ID gaps to spread the entries around static const FwChanIdType TLMPACKETIZER_HASH_BUCKETS = - 202; // !< Buckets assignable to a hash slot. - // Buckets must be >= number of telemetry channels in system + 256; // !< Buckets assignable to a hash slot. + // Buckets must be >= number of telemetry channels in system. + // Bumped 202->256 when S-Band re-enablement pushed the channel count to + // 214 (>202 boot-asserts in TlmPacketizer::findBucket); 256 leaves margin. static const FwChanIdType TLMPACKETIZER_MAX_MISSING_TLM_CHECK = 25; // !< Maximum number of missing telemetry channel checks From ce63c10e7ea7b396e665b60e76986cb191201434 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:36:55 -0700 Subject: [PATCH 07/15] diag(sband): enable sys_heap runtime stats for #299 RAM-cliff investigation Adds CONFIG_SYS_HEAP_RUNTIME_STATS=y (12 bytes/heap, negligible) so the libc malloc heap's free/allocated/max byte counters (z_malloc_heap.heap-> free_bytes/allocated_bytes/max_allocated_bytes) can be read live via GDB at any halt point, without needing console/printk (which stays disabled to avoid desyncing the GDS deframer). No functional change. This session used it plus static ELF analysis to establish the malloc arena total (164112 B, matching the toolchain's reported 69.18% RAM use) but could not complete live incremental-allocation tracing: boot on the bench today hits an early BusFault (before main()/topology setup, before any malloc() call, confirmed via z_malloc_heap showing zero allocations) that reproduces even on this exact pristine commit, unrelated to the S-Band topology changes. See session report for details and options. Co-Authored-By: Claude Fable 5 --- prj.conf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/prj.conf b/prj.conf index bcce1385..6d0894f8 100644 --- a/prj.conf +++ b/prj.conf @@ -58,6 +58,12 @@ CONFIG_DYNAMIC_THREAD_STACK_SIZE=4096 CONFIG_THREAD_STACK_INFO=y CONFIG_RING_BUFFER=y +# #299 RAM-cliff diagnostics: expose sys_heap free/allocated/max byte +# counters on the libc malloc heap (z_malloc_heap) so exact arena +# consumption can be read via GDB (p z_malloc_heap.heap->free_bytes etc.) +# at any halt point. Cost: 3x size_t (12 bytes) per sys_heap instance. +CONFIG_SYS_HEAP_RUNTIME_STATS=y + # S-Band reintegration plan (PR 1, Slice 1.1 / D6): needed by the StackMonitor # component to enumerate live threads (k_thread_foreach) and read their names # (k_thread_name_get) for per-thread stack telemetry. From af93386749ea7c3e0c29ec09760cee9cb966d784 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:52:45 -0700 Subject: [PATCH 08/15] Revert "diag(sband): enable sys_heap runtime stats for #299 RAM-cliff investigation" This reverts commit ce63c10e7ea7b396e665b60e76986cb191201434. --- prj.conf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/prj.conf b/prj.conf index 6d0894f8..bcce1385 100644 --- a/prj.conf +++ b/prj.conf @@ -58,12 +58,6 @@ CONFIG_DYNAMIC_THREAD_STACK_SIZE=4096 CONFIG_THREAD_STACK_INFO=y CONFIG_RING_BUFFER=y -# #299 RAM-cliff diagnostics: expose sys_heap free/allocated/max byte -# counters on the libc malloc heap (z_malloc_heap) so exact arena -# consumption can be read via GDB (p z_malloc_heap.heap->free_bytes etc.) -# at any halt point. Cost: 3x size_t (12 bytes) per sys_heap instance. -CONFIG_SYS_HEAP_RUNTIME_STATS=y - # S-Band reintegration plan (PR 1, Slice 1.1 / D6): needed by the StackMonitor # component to enumerate live threads (k_thread_foreach) and read their names # (k_thread_name_get) for per-thread stack telemetry. From 588d5447205e7079a90d5939addfb0a5a3a0448b Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:53:07 -0700 Subject: [PATCH 09/15] fix(sband): give the kernel heap real capacity for the 8KB SBand stack CONFIG_DYNAMIC_THREAD_ALLOC=y (PR1) routes oversized stack requests to k_malloc, but the kernel system heap size was never set: it collapsed to the 1KB mqueue contribution, so the SBand thread's 8KB stack allocation returned NULL at task start. Confirmed on HWIL bench 2026-07-15/16. Co-Authored-By: Claude Fable 5 --- prj.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/prj.conf b/prj.conf index bcce1385..079646db 100644 --- a/prj.conf +++ b/prj.conf @@ -54,6 +54,11 @@ CONFIG_DYNAMIC_THREAD_POOL_SIZE=25 # Num threads in the thread pool CONFIG_DYNAMIC_THREAD_STACK_SIZE=4096 # Size of thread stack in thread pool, must be >= Thread Pool size in F' +# CONFIG_DYNAMIC_THREAD_ALLOC's k_malloc() fallback draws from the kernel system +# heap, whose size is CONFIG_HEAP_MEM_POOL_SIZE. Left unset it collapses to the +# 1 KB mqueue contribution, so the SBand thread's 8 KB stack request returns NULL +# and the thread never starts. 16 KB = 8 KB stack + alignment/metadata + margin. +CONFIG_HEAP_MEM_POOL_SIZE=16384 CONFIG_THREAD_STACK_INFO=y CONFIG_RING_BUFFER=y From 7c3fb8e186acddbd396d02bc921f8f80b25e5221 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:24:20 -0700 Subject: [PATCH 10/15] =?UTF-8?q?fix(sband):=20lean=20S-Band=20comms=20pro?= =?UTF-8?q?file=20=E2=80=94=20three=20full=20ComCcsds=20stacks=20exhaust?= =?UTF-8?q?=20the=20malloc=20arena?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HWIL forensics (2026-07-16, vector-catch at first fault) proved the PR3 boot BusFault is heap exhaustion, not corruption: each ComCcsds subtopology costs ~35.6KB of arena (comQueue table 13.5K, buffer pool 11.2K, frame accumulator 1K, two OS queues 9.9K) and the third stack ran the arena dry during configComponents at ComCcsdsUart::commsBufferManager.setup. S-Band is a secondary downlink: give it smaller queues (comQueue 10, aggregator 8, events depth 15) and a 3+3 buffer pool (~-18.4KB), shrink the dynamic thread pool to the 24 tasks the deployment actually starts (sband's 8K stack uses the k_malloc fallback, so 23 slots + 1 spare, +4KB arena), and right-size the kernel heap to 12KB (+4KB arena). Co-Authored-By: Claude Fable 5 --- .../ComCcsdsSband/ComCcsds.fpp | 14 ++++++------ .../project/config/ComCcsdsConfig.fpp | 22 +++++++++++++++++++ prj.conf | 12 ++++++---- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/PROVESFlightControllerReference/ComCcsdsSband/ComCcsds.fpp b/PROVESFlightControllerReference/ComCcsdsSband/ComCcsds.fpp index 4aeaa1ac..3aff60ac 100644 --- a/PROVESFlightControllerReference/ComCcsdsSband/ComCcsds.fpp +++ b/PROVESFlightControllerReference/ComCcsdsSband/ComCcsds.fpp @@ -4,7 +4,7 @@ module ComCcsdsSband { # Active Components # ---------------------------------------------------------------------- instance comQueue: Svc.ComQueue base id ComCcsdsConfig.BASE_ID_SBAND + 0x00000 \ - queue size ComCcsdsConfig.QueueSizes.comQueue \ + queue size ComCcsdsConfig.Sband.QueueSizes.comQueue \ stack size ComCcsdsConfig.StackSizes.comQueue \ priority ComCcsdsConfig.Priorities.comQueue \ { @@ -13,15 +13,15 @@ module ComCcsdsSband { Svc::ComQueue::QueueConfigurationTable configurationTableSband; // Events (highest-priority) - configurationTableSband.entries[ComCcsds::Ports_ComPacketQueue::EVENTS].depth = ComCcsdsConfig::QueueDepths::events; + configurationTableSband.entries[ComCcsds::Ports_ComPacketQueue::EVENTS].depth = ComCcsdsConfig::Sband::QueueDepths::events; configurationTableSband.entries[ComCcsds::Ports_ComPacketQueue::EVENTS].priority = ComCcsdsConfig::QueuePriorities::events; // Telemetry - configurationTableSband.entries[ComCcsds::Ports_ComPacketQueue::TELEMETRY].depth = ComCcsdsConfig::QueueDepths::tlm; + configurationTableSband.entries[ComCcsds::Ports_ComPacketQueue::TELEMETRY].depth = ComCcsdsConfig::Sband::QueueDepths::tlm; configurationTableSband.entries[ComCcsds::Ports_ComPacketQueue::TELEMETRY].priority = ComCcsdsConfig::QueuePriorities::tlm; // File Downlink Queue (buffer queue using NUM_CONSTANTS offset) - configurationTableSband.entries[ComCcsds::Ports_ComPacketQueue::NUM_CONSTANTS + ComCcsds::Ports_ComBufferQueue::FILE].depth = ComCcsdsConfig::QueueDepths::file; + configurationTableSband.entries[ComCcsds::Ports_ComPacketQueue::NUM_CONSTANTS + ComCcsds::Ports_ComBufferQueue::FILE].depth = ComCcsdsConfig::Sband::QueueDepths::file; configurationTableSband.entries[ComCcsds::Ports_ComPacketQueue::NUM_CONSTANTS + ComCcsds::Ports_ComBufferQueue::FILE].priority = ComCcsdsConfig::QueuePriorities::file; // Allocation identifier is 0 as the MallocAllocator discards it @@ -64,9 +64,9 @@ module ComCcsdsSband { phase Fpp.ToCpp.Phases.configComponents """ memset(&ConfigObjects::ComCcsdsSband_commsBufferManager::bins, 0, sizeof(ConfigObjects::ComCcsdsSband_commsBufferManager::bins)); ConfigObjects::ComCcsdsSband_commsBufferManager::bins.bins[0].bufferSize = ComCcsdsConfig::BuffMgr::commsBuffSize; - ConfigObjects::ComCcsdsSband_commsBufferManager::bins.bins[0].numBuffers = ComCcsdsConfig::BuffMgr::commsBuffCount; + ConfigObjects::ComCcsdsSband_commsBufferManager::bins.bins[0].numBuffers = ComCcsdsConfig::Sband::BuffMgr::commsBuffCount; ConfigObjects::ComCcsdsSband_commsBufferManager::bins.bins[1].bufferSize = ComCcsdsConfig::BuffMgr::commsFileBuffSize; - ConfigObjects::ComCcsdsSband_commsBufferManager::bins.bins[1].numBuffers = ComCcsdsConfig::BuffMgr::commsFileBuffCount; + ConfigObjects::ComCcsdsSband_commsBufferManager::bins.bins[1].numBuffers = ComCcsdsConfig::Sband::BuffMgr::commsFileBuffCount; ComCcsdsSband::commsBufferManager.setup( ComCcsdsConfig::BuffMgr::commsBuffMgrId, 0, @@ -87,7 +87,7 @@ module ComCcsdsSband { instance spacePacketDeframer: Svc.Ccsds.SpacePacketDeframer base id ComCcsdsConfig.BASE_ID_SBAND + 0x05000 instance aggregator: Svc.ComAggregator base id ComCcsdsConfig.BASE_ID_SBAND + 0x06000 \ - queue size ComCcsdsConfig.QueueSizes.aggregator \ + queue size ComCcsdsConfig.Sband.QueueSizes.aggregator \ stack size ComCcsdsConfig.StackSizes.aggregator \ priority ComCcsdsConfig.Priorities.aggregator diff --git a/PROVESFlightControllerReference/project/config/ComCcsdsConfig.fpp b/PROVESFlightControllerReference/project/config/ComCcsdsConfig.fpp index d541581b..5dbc7b18 100644 --- a/PROVESFlightControllerReference/project/config/ComCcsdsConfig.fpp +++ b/PROVESFlightControllerReference/project/config/ComCcsdsConfig.fpp @@ -42,4 +42,26 @@ module ComCcsdsConfig { constant commsFileBuffCount = 5 constant commsBuffMgrId = 200 } + + # Lean profile for the S-Band subtopology. Three full-size ComCcsds stacks do + # not fit the RP2350 malloc arena (~149 KB): each stack costs ~35.6 KB of heap + # (comQueue table ~13.5K + buffer pool ~11.2K + frame accumulator ~1K + the two + # OS message queues ~9.9K), and the third one exhausted the arena during + # configComponents (HWIL 2026-07-16). S-Band is a secondary downlink; it gets + # smaller queues and a smaller buffer pool (~17 KB total instead of ~35.6 KB). + module Sband { + module QueueSizes { + constant comQueue = 10 + constant aggregator = 8 + } + module QueueDepths { + constant events = 15 + constant tlm = 1 + constant file = 1 + } + module BuffMgr { + constant commsBuffCount = 3 + constant commsFileBuffCount = 3 + } + } } diff --git a/prj.conf b/prj.conf index 079646db..1b8de2a7 100644 --- a/prj.conf +++ b/prj.conf @@ -50,15 +50,19 @@ CONFIG_KERNEL_MEM_POOL=y # is unchanged, so every other thread (<=4096 bytes) still gets a pool stack as before. CONFIG_DYNAMIC_THREAD_ALLOC=y CONFIG_DYNAMIC_THREAD_PREFER_POOL=y -CONFIG_DYNAMIC_THREAD_POOL_SIZE=25 - # Num threads in the thread pool +CONFIG_DYNAMIC_THREAD_POOL_SIZE=24 + # Num threads in the thread pool. The deployment starts 24 F' tasks; the SBand + # task's 8 KB stack exceeds the 4 KB slot size and falls back to k_malloc + # (DYNAMIC_THREAD_ALLOC), so 23 tasks draw pool slots + 1 spare. Every pool + # slot not needed here is 4 KB taken away from the malloc arena. CONFIG_DYNAMIC_THREAD_STACK_SIZE=4096 # Size of thread stack in thread pool, must be >= Thread Pool size in F' # CONFIG_DYNAMIC_THREAD_ALLOC's k_malloc() fallback draws from the kernel system # heap, whose size is CONFIG_HEAP_MEM_POOL_SIZE. Left unset it collapses to the # 1 KB mqueue contribution, so the SBand thread's 8 KB stack request returns NULL -# and the thread never starts. 16 KB = 8 KB stack + alignment/metadata + margin. -CONFIG_HEAP_MEM_POOL_SIZE=16384 +# and the thread never starts. 12 KB = 8 KB stack (8-byte aligned, no MPU) + +# metadata + margin, while giving the rest of RAM back to the malloc arena. +CONFIG_HEAP_MEM_POOL_SIZE=12288 CONFIG_THREAD_STACK_INFO=y CONFIG_RING_BUFFER=y From 4cc25705287002d67060dc19b0fb20d9a8fd748e Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:26:00 -0700 Subject: [PATCH 11/15] fix(downlink): re-prime the ComQueues after bring-up settles HWIL trace (port-invoke breakpoints, 2026-07-16) proved every downlink priming READY was being silently dropped: the comStatusIn async hop's 20-deep dispatch queue overflows during the bring-up event storm (this bench board is missing most I2C sensors, so bring-up emits dozens of warning events into all three comQueues via the splitters), and the overflow assert is non-halting. Lost priming = permanently deadlocked downlink: late statuses die in ComAggregator's assertNoStatus action, so nothing ever recovers. This deadlock reproduces on any current-main image; the last hardware-verified beaconing image predates the aggregator architecture. Add primeDownlinkQueues() and call it from main() three times at 1s intervals after setupTopology, paired with the lib/fprime ComQueue change that makes a redundant READY benign. Verified on HWIL: queued boot events flush immediately and Beacon frames flow at cadence. Co-Authored-By: Claude Fable 5 --- .../ReferenceDeployment/Main.cpp | 10 ++++++++++ .../Top/ReferenceDeploymentTopology.cpp | 16 ++++++++++++++++ .../Top/ReferenceDeploymentTopology.hpp | 10 ++++++++++ 3 files changed, 36 insertions(+) diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp index f02bf23a..7182f7d0 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp @@ -129,6 +129,16 @@ int main(int argc, char* argv[]) { // Setup, cycle, and teardown topology ReferenceDeployment::setupTopology(inputs); + + // Re-prime the downlink a few times with settling gaps: the one-shot READY + // that primes each ComQueue is dropped when the bring-up event storm + // overflows its dispatch queue (see primeDownlinkQueues). The sleeps let + // the storm drain between attempts so at least one READY is delivered. + for (int i = 0; i < 3; i++) { + k_sleep(K_MSEC(1000)); + ReferenceDeployment::primeDownlinkQueues(); + } + ReferenceDeployment::startRateGroups(); // Program loop ReferenceDeployment::teardownTopology(inputs); return 0; diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp index 13f67bff..ea01635d 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp @@ -200,6 +200,22 @@ void setupTopology(const TopologyState& state) { fsFormat.configure(state.storagePartitionId); } +void primeDownlinkQueues() { + // Re-prime the downlink queues. Each ComQueue is primed by a single READY + // status (ComAggregator::preamble) delivered over a lossy hop: comStatusIn + // is async, and the 20-deep dispatch queue overflows during the bring-up + // event storm (on boards with absent sensors, dozens of warning events), + // silently dropping priming statuses and deadlocking the downlink forever — + // late statuses die in the aggregator's assertNoStatus action, so there is + // no recovery path. A redundant READY is benign (see + // ComQueue::comStatusIn_handler), so main() calls this several times after + // bring-up settles to guarantee delivery. + Fw::Success primeReady = Fw::Success::SUCCESS; + ComCcsdsUart::comQueue.get_comStatusIn_InputPort(0)->invoke(primeReady); + ComCcsdsLora::comQueue.get_comStatusIn_InputPort(0)->invoke(primeReady); + ComCcsdsSband::comQueue.get_comStatusIn_InputPort(0)->invoke(primeReady); +} + void startRateGroups() { timer.configure(BASE_RATEGROUP_PERIOD_MS); timer.start(); diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.hpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.hpp index 926ae0a5..46df71ee 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.hpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.hpp @@ -38,6 +38,16 @@ namespace ReferenceDeployment { */ void setupTopology(const TopologyState& state); +/** + * \brief re-send the downlink priming status to every ComQueue + * + * The one-shot READY that primes each ComQueue travels over a lossy async hop + * and is silently dropped when the dispatch queue overflows during the + * bring-up event storm, permanently deadlocking the downlink. Redundant READY + * statuses are benign, so main() calls this several times after bring-up. + */ +void primeDownlinkQueues(); + /** * \brief teardown the F´ topology * From 5fefce4e5f1238e23a7c178bde82af416c4d4fff Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Thu, 16 Jul 2026 16:12:13 -0700 Subject: [PATCH 12/15] chore(sband): export submodule downlink fixes as patches + direct-boot overlay The ComQueue re-prime and CDC-ACM poll-mode fixes live on local submodule branches that cannot be pushed (submodules track upstream remotes), so export them under patches/ per repo convention. Also commit the direct-boot devicetree overlay used to flash the bench v5e (no mcuboot bootloader on that board). Co-Authored-By: Claude Fable 5 --- hwil-logs/directboot.overlay | 5 +++ patches/README.md | 17 +++++++ .../fprime-comqueue-reprime-tolerance.patch | 44 +++++++++++++++++++ ...zephyr-cdc-acm-pollmode-enable-drain.patch | 36 +++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 hwil-logs/directboot.overlay create mode 100644 patches/fprime-comqueue-reprime-tolerance.patch create mode 100644 patches/zephyr-cdc-acm-pollmode-enable-drain.patch diff --git a/hwil-logs/directboot.overlay b/hwil-logs/directboot.overlay new file mode 100644 index 00000000..74d3dfbf --- /dev/null +++ b/hwil-logs/directboot.overlay @@ -0,0 +1,5 @@ +/ { + chosen { + zephyr,code-partition = &boot_partition; + }; +}; diff --git a/patches/README.md b/patches/README.md index 09cbaf39..0b03f43e 100644 --- a/patches/README.md +++ b/patches/README.md @@ -15,3 +15,20 @@ The patch is automatically applied by the `make submodules` target to ensure ver **Application:** This patch is applied automatically when running `make submodules` (or `make` which includes that target). **Note:** After applying this patch, `git status` will show `lib/fprime` as modified. This is expected and should **not** be committed. The patched state is reapplied automatically on each `make submodules` run. + +## S-Band HWIL fixes (2026-07-16, feat/sband-pr3-topology) + +Two submodule fixes required for a working downlink; committed on local +submodule branches (unpushable — submodules track upstream remotes) and +exported here so the branch is reproducible: + +- `fprime-comqueue-reprime-tolerance.patch` — apply in `lib/fprime` + (`git apply ../../patches/fprime-comqueue-reprime-tolerance.patch`). + ComQueue treats a redundant READY comStatus as a benign re-prime instead of + asserting; required by ReferenceDeployment::primeDownlinkQueues(), without + which the downlink deadlocks when the boot event storm drops the one-shot + priming status. Upstream PR candidate (nasa/fprime). +- `zephyr-cdc-acm-pollmode-enable-drain.patch` — apply in + `lib/zephyr-workspace/zephyr`. Drains poll-mode TX data queued before USB + enumeration; without it CDC output can stay silent forever. Upstream PR + candidate (zephyrproject-rtos/zephyr). diff --git a/patches/fprime-comqueue-reprime-tolerance.patch b/patches/fprime-comqueue-reprime-tolerance.patch new file mode 100644 index 00000000..d5e7cc41 --- /dev/null +++ b/patches/fprime-comqueue-reprime-tolerance.patch @@ -0,0 +1,44 @@ +From 73abd683d1ac5f9d680619b193fa1df6991d6879 Mon Sep 17 00:00:00 2001 +From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> +Date: Thu, 16 Jul 2026 14:25:39 -0700 +Subject: [PATCH] fix(ComQueue): tolerate a redundant READY status as a benign + re-prime + +The one-shot priming READY (ComAggregator::preamble) travels over an async +port whose dispatch queue can overflow during the boot event storm; when the +priming status is dropped there is no recovery path and the downlink +deadlocks permanently. Accepting READY+SUCCESS in the READY state (process +the queue, no assert) lets deployments re-send the priming status safely. + +Co-Authored-By: Claude Fable 5 +--- + Svc/ComQueue/ComQueue.cpp | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/Svc/ComQueue/ComQueue.cpp b/Svc/ComQueue/ComQueue.cpp +index 842023014..8c82369b8 100644 +--- a/Svc/ComQueue/ComQueue.cpp ++++ b/Svc/ComQueue/ComQueue.cpp +@@ -184,8 +184,17 @@ void ComQueue::comStatusIn_handler(const FwIndexType portNum, Fw::Success& condi + this->m_state = WAITING; + } + break; +- // Both READY and unknown states should not be possible at this point. To receive a status message we must be +- // one of the WAITING or RETRY states. ++ // A READY status while already sendable is a benign re-prime: the one-shot ++ // priming status (e.g. ComAggregator::preamble) can be lost to dispatch-queue ++ // overflow during the boot event storm, deadlocking the downlink with no ++ // recovery path. Deployments may therefore re-send a priming status after ++ // bring-up; process the queue in case data arrived while the queue was idle. ++ case READY: ++ if (condition.e == Fw::Success::SUCCESS) { ++ this->processQueue(); ++ } ++ break; ++ // Unknown states should not be possible at this point. + default: + FW_ASSERT(0, static_cast(this->m_state)); + break; +-- +2.50.1 (Apple Git-155) + diff --git a/patches/zephyr-cdc-acm-pollmode-enable-drain.patch b/patches/zephyr-cdc-acm-pollmode-enable-drain.patch new file mode 100644 index 00000000..3152c9a3 --- /dev/null +++ b/patches/zephyr-cdc-acm-pollmode-enable-drain.patch @@ -0,0 +1,36 @@ +From 25cbe02e4fe92003bbc1ede103d9eb6895b3072e Mon Sep 17 00:00:00 2001 +From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> +Date: Thu, 16 Jul 2026 14:25:43 -0700 +Subject: [PATCH] usb: cdc_acm: drain poll-mode TX backlog when configuration + is enabled + +Poll-mode users (no IRQ callback API) that write before enumeration +completes fill tx_fifo with no one scheduling tx_fifo_work: the FIFO +stays full and output is silent forever. Schedule the TX work at enable +time when the ring buffer is non-empty, mirroring the IRQ-mode path. + +Co-Authored-By: Claude Fable 5 +--- + subsys/usb/device_next/class/usbd_cdc_acm.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/subsys/usb/device_next/class/usbd_cdc_acm.c b/subsys/usb/device_next/class/usbd_cdc_acm.c +index d3921c0f146..d16bf42cbc2 100644 +--- a/subsys/usb/device_next/class/usbd_cdc_acm.c ++++ b/subsys/usb/device_next/class/usbd_cdc_acm.c +@@ -368,6 +368,12 @@ static void usbd_cdc_acm_enable(struct usbd_class_data *const c_data) + /* Queue pending TX data on IN endpoint */ + cdc_acm_work_schedule(&data->tx_fifo_work, K_NO_WAIT); + } ++ } else if (!ring_buf_is_empty(data->tx_fifo.rb)) { ++ /* Poll-mode (no IRQ API user): drain TX data accumulated in the ++ * ring buffer before enumeration completed, otherwise the FIFO ++ * stays full and poll-mode output is silent forever. ++ */ ++ cdc_acm_work_schedule(&data->tx_fifo_work, K_NO_WAIT); + } + } + +-- +2.50.1 (Apple Git-155) + From 90a1b8ad9d6cb629bec128861a7402eb322f9146 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:32:05 -0700 Subject: [PATCH 13/15] perf(sband): raise SX1280 SPI clock 100 kHz -> 4 MHz The 100 kHz SPI clock cost ~25 ms per 252 B buffer upload, a constant ~33.5 ms/packet driver overhead across all LoRa bandwidths (see proves-gs-project/spikes/REPORT-sband-goodput.md). Measured on the v5e bench at 4 MHz the per-packet overhead drops to ~13.3 ms: BW406.25: 1613.1 -> 1849.0 B/s (period 156.2 -> 136.3 ms) BW1625: 3933.7 -> 5719.3 B/s (period 64.1 -> 44.1 ms) Zero loss on the forward ladder. Flight image validated end-to-end on HWIL: clean boot, RadioFaulted=False, ConsecutiveRadioFailures=0 after a TX-enabled soak, and flight TM frames deframed over the air by the passthrough receiver. The SX1280 supports up to 18.18 MHz; the same spi0 bus already runs the SD card at 24 MHz. Co-Authored-By: Claude Fable 5 --- .../ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp index ea01635d..a2ba6dea 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp @@ -145,7 +145,9 @@ void setupTopology(const TopologyState& state) { }; struct spi_config sbandSpiCfg = { - .frequency = 100000, // 100 KHz -- sx1280 has maximum 18.18 MHz -- there is a 12MHz oscillator on-board + .frequency = 4000000, // 4 MHz (SX1280 max 18.18 MHz; 100 kHz cost ~25 ms per 252 B buffer + // upload -- see REPORT-sband-goodput.md; the same spi0 bus already + // runs the SD card at 24 MHz) .operation = SPI_WORD_SET(8), .slave = 0, .cs = sbandCsCtrl, From 9f8bc74890167db7653044f3a9f6d24d3bd0a84f Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 18 Jul 2026 09:42:45 -0700 Subject: [PATCH 14/15] fix(lint): exclude patches/*.patch from pre-commit hooks The submodule patches are byte-exact (trailing whitespace in diff context lines is significant); the trailing-whitespace and end-of-file-fixer hooks were rewriting them in CI, which both fails the lint job and would corrupt the patches. Verified: git apply --reverse --check passes for both live patches and sha256 hashes are unchanged after a full pre-commit run. Co-Authored-By: Claude Fable 5 --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a762d93b..61dd21bd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,8 @@ +# patches/*.patch are byte-exact submodule patches (see patches/README.md): +# trailing whitespace in diff context lines is significant and any rewrite +# breaks `git apply`. No hook may touch them. +exclude: ^patches/.*\.patch$ + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 From 042a11d606de671fd1dde8ec68dec8711fe62153 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:18:26 -0700 Subject: [PATCH 15/15] fix(ci): apply the sband submodule patches in 'make submodules' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The integration jobs build from pristine submodules, but PR3's primeDownlinkQueues() re-prime hits stock ComQueue's FW_ASSERT on a READY-while-READY status — the exact behavior the exported fprime-comqueue-reprime-tolerance.patch fixes. Every CI board boot therefore asserted at runtime and the radio/uart integration jobs failed with gds_working=False. Wire both sband patches into the existing make-submodules patch step (same apply/already-applied/fail guard as the fprime-gds version patch) so CI builds the same tree the HWIL bench validated. Co-Authored-By: Claude Fable 5 --- Makefile | 22 ++++++++++++++++++++++ patches/README.md | 5 +++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1d6883df..e6bf3b2a 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,28 @@ submodules: ## Initialize and update git submodules echo "❌ Error: Unable to apply patch. Run 'cd lib/fprime && git status' to check."; \ exit 1; \ fi + @echo "Applying ComQueue re-prime tolerance patch (see patches/README.md)..." + @cd lib/fprime && \ + if git apply --check ../../patches/fprime-comqueue-reprime-tolerance.patch 2>/dev/null; then \ + git apply ../../patches/fprime-comqueue-reprime-tolerance.patch && \ + echo "✓ Applied ComQueue re-prime tolerance patch"; \ + elif git apply --reverse --check ../../patches/fprime-comqueue-reprime-tolerance.patch 2>/dev/null; then \ + echo "⚠ Patch already applied"; \ + else \ + echo "❌ Error: Unable to apply ComQueue patch. Run 'cd lib/fprime && git status' to check."; \ + exit 1; \ + fi + @echo "Applying CDC-ACM poll-mode TX drain patch (see patches/README.md)..." + @cd lib/zephyr-workspace/zephyr && \ + if git apply --check ../../../patches/zephyr-cdc-acm-pollmode-enable-drain.patch 2>/dev/null; then \ + git apply ../../../patches/zephyr-cdc-acm-pollmode-enable-drain.patch && \ + echo "✓ Applied CDC-ACM poll-mode drain patch"; \ + elif git apply --reverse --check ../../../patches/zephyr-cdc-acm-pollmode-enable-drain.patch 2>/dev/null; then \ + echo "⚠ Patch already applied"; \ + else \ + echo "❌ Error: Unable to apply CDC-ACM patch. Run 'cd lib/zephyr-workspace/zephyr && git status' to check."; \ + exit 1; \ + fi export VIRTUAL_ENV ?= $(shell pwd)/fprime-venv .PHONY: fprime-venv diff --git a/patches/README.md b/patches/README.md index 0b03f43e..0a686ba7 100644 --- a/patches/README.md +++ b/patches/README.md @@ -22,8 +22,9 @@ Two submodule fixes required for a working downlink; committed on local submodule branches (unpushable — submodules track upstream remotes) and exported here so the branch is reproducible: -- `fprime-comqueue-reprime-tolerance.patch` — apply in `lib/fprime` - (`git apply ../../patches/fprime-comqueue-reprime-tolerance.patch`). +- `fprime-comqueue-reprime-tolerance.patch` — applied automatically by + `make submodules` (manual: `cd lib/fprime && git apply + ../../patches/fprime-comqueue-reprime-tolerance.patch`). ComQueue treats a redundant READY comStatus as a benign re-prime instead of asserting; required by ReferenceDeployment::primeDownlinkQueues(), without which the downlink deadlocks when the boot event storm drops the one-shot