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 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/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/ReferenceDeployment/Main.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp index 20df9d57..7182f7d0 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; @@ -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/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/ReferenceDeploymentTopology.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp index e54eedff..a2ba6dea 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,29 @@ 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 = 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, + .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); @@ -185,6 +202,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 * 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; 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] 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/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 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. 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. 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 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..0a686ba7 100644 --- a/patches/README.md +++ b/patches/README.md @@ -15,3 +15,21 @@ 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` — 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 + 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) + diff --git a/prj.conf b/prj.conf index bcce1385..1b8de2a7 100644 --- a/prj.conf +++ b/prj.conf @@ -50,10 +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. 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