diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 2f5cc5e9a..930502497 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -37,12 +37,12 @@ public: */ class Radio { public: - virtual uint8_t regulatorModes() const { return 0; } - virtual power::RegulatorMode defaultRegulatorMode() const { return power::RegulatorMode::Default; } + MESH_REGULATOR_VIRTUAL uint8_t regulatorModes() const { return 0; } + MESH_REGULATOR_VIRTUAL power::RegulatorMode defaultRegulatorMode() const { return power::RegulatorMode::Default; } // Cache a selection before the first hardware initialization. Save later // choices for the next reboot; never change a live RX/TX regulator here. - virtual bool configureBootRegulator(power::RegulatorMode) { return false; } - virtual power::RegulatorMode activeRegulatorMode() const { return power::RegulatorMode::Default; } + MESH_REGULATOR_VIRTUAL bool configureBootRegulator(power::RegulatorMode) { return false; } + MESH_REGULATOR_VIRTUAL power::RegulatorMode activeRegulatorMode() const { return power::RegulatorMode::Default; } static constexpr uint32_t CarrierWaveDefaultMillis = 10000; static constexpr uint32_t CarrierWaveMaxMillis = 60000; virtual bool supportsCarrierWave() const { return false; } diff --git a/src/MeshCore.h b/src/MeshCore.h index 819f87433..9b6daf6ff 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -146,12 +146,12 @@ public: virtual void onBeforeTransmit() { } virtual void onAfterTransmit() { } virtual void reboot() = 0; - virtual uint8_t mcuRegulatorModes() const { return 0; } - virtual power::RegulatorMode defaultMcuRegulatorMode() const { + MESH_REGULATOR_VIRTUAL uint8_t mcuRegulatorModes() const { return 0; } + MESH_REGULATOR_VIRTUAL power::RegulatorMode defaultMcuRegulatorMode() const { return power::RegulatorMode::Default; } - virtual bool applyMcuRegulator(power::RegulatorMode) { return false; } - virtual power::RegulatorMode activeMcuRegulatorMode() const { + MESH_REGULATOR_VIRTUAL bool applyMcuRegulator(power::RegulatorMode) { return false; } + MESH_REGULATOR_VIRTUAL power::RegulatorMode activeMcuRegulatorMode() const { return power::RegulatorMode::Default; } // Reboot into a UF2-capable bootloader when the platform supports the diff --git a/src/helpers/RegulatorMode.h b/src/helpers/RegulatorMode.h index 240b1aae3..e8874586a 100644 --- a/src/helpers/RegulatorMode.h +++ b/src/helpers/RegulatorMode.h @@ -1,6 +1,24 @@ #pragma once #include +// The current integrated STM32WL driver has no selectable supply domain: +// MCU and radio share a regulator and need coordinated support first. Keep +// its unsupported defaults out of every board/radio vtable as well as the +// settings implementation. Other drivers/platforms retain virtual dispatch. +#define MESH_REGULATOR_FIXED_RADIO_CustomSTM32WLx 1 +#define MESH_REGULATOR_FIXED_RADIO_I(type) MESH_REGULATOR_FIXED_RADIO_##type +#define MESH_REGULATOR_FIXED_RADIO(type) MESH_REGULATOR_FIXED_RADIO_I(type) +#if defined(STM32_PLATFORM) && MESH_REGULATOR_FIXED_RADIO(RADIO_CLASS) +#define MESH_REGULATOR_FIXED_SUPPLY 1 +#define MESH_REGULATOR_VIRTUAL +#else +#define MESH_REGULATOR_FIXED_SUPPLY 0 +#define MESH_REGULATOR_VIRTUAL virtual +#endif +#undef MESH_REGULATOR_FIXED_RADIO +#undef MESH_REGULATOR_FIXED_RADIO_I +#undef MESH_REGULATOR_FIXED_RADIO_CustomSTM32WLx + namespace mesh { namespace power { enum class RegulatorMode : uint8_t { Default = 0, Dcdc = 1, Ldo = 2 }; diff --git a/src/helpers/RegulatorSettings.cpp b/src/helpers/RegulatorSettings.cpp index 41453708f..1fba0d97c 100644 --- a/src/helpers/RegulatorSettings.cpp +++ b/src/helpers/RegulatorSettings.cpp @@ -17,10 +17,7 @@ // nRF52 plus an external radio. Do not retain the unreachable persistence and // recovery machinery on these flash-constrained targets. Match this exact // driver: an STM32 target with a different radio keeps the ordinary path. -#define MESH_REGULATOR_FIXED_RADIO_CustomSTM32WLx 1 -#define MESH_REGULATOR_FIXED_RADIO_I(type) MESH_REGULATOR_FIXED_RADIO_##type -#define MESH_REGULATOR_FIXED_RADIO(type) MESH_REGULATOR_FIXED_RADIO_I(type) -#if defined(STM32_PLATFORM) && MESH_REGULATOR_FIXED_RADIO(RADIO_CLASS) +#if MESH_REGULATOR_FIXED_SUPPLY namespace mesh { namespace power { bool beginRegulatorSettings(MainBoard&, Radio&, FILESYSTEM*) { return true; } void serviceRegulatorSettings() {} @@ -320,9 +317,6 @@ void resumeRegulatorRecovery() { }} // namespace mesh::power #endif -#undef MESH_REGULATOR_FIXED_RADIO -#undef MESH_REGULATOR_FIXED_RADIO_I -#undef MESH_REGULATOR_FIXED_RADIO_CustomSTM32WLx extern "C" void meshRegulatorCleanReset() { // Intentional resets can happen before the settings/filesystem is ready. diff --git a/test/fixtures/regulator_api_dispatch.cpp b/test/fixtures/regulator_api_dispatch.cpp new file mode 100644 index 000000000..91fb97053 --- /dev/null +++ b/test/fixtures/regulator_api_dispatch.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +using mesh::power::RegulatorMode; + +#if MESH_REGULATOR_FIXED_SUPPLY && !defined(REQUIRE_VIRTUAL_REGULATOR_API) +#define REGULATOR_OVERRIDE +#else +#define REGULATOR_OVERRIDE override +#endif + +struct BoardDefaults : mesh::MainBoard { + uint16_t getBattMilliVolts() override { return 0; } + const char* getManufacturerName() const override { return "test"; } + uint8_t getStartupReason() const override { return 0; } + void reboot() override {} + void sleep(uint32_t) override {} // The host must not assemble the MCU's WFI. +}; +struct BoardSelection : BoardDefaults { + uint8_t mcuRegulatorModes() const REGULATOR_OVERRIDE { return 3; } + RegulatorMode defaultMcuRegulatorMode() const REGULATOR_OVERRIDE { return RegulatorMode::Dcdc; } + bool applyMcuRegulator(RegulatorMode) REGULATOR_OVERRIDE { return true; } + RegulatorMode activeMcuRegulatorMode() const REGULATOR_OVERRIDE { return RegulatorMode::Ldo; } +}; +struct RadioDefaults : mesh::Radio { + int recvRaw(uint8_t*, int) override { return 0; } + uint32_t getEstAirtimeFor(int) override { return 0; } + float packetScore(float, int) override { return 0; } + bool startSendRaw(const uint8_t*, int) override { return false; } + bool isSendComplete() override { return true; } + void onSendFinished() override {} + bool isInRecvMode() const override { return false; } +}; +struct RadioSelection : RadioDefaults { + uint8_t regulatorModes() const REGULATOR_OVERRIDE { return 3; } + RegulatorMode defaultRegulatorMode() const REGULATOR_OVERRIDE { return RegulatorMode::Dcdc; } + bool configureBootRegulator(RegulatorMode) REGULATOR_OVERRIDE { return true; } + RegulatorMode activeRegulatorMode() const REGULATOR_OVERRIDE { return RegulatorMode::Ldo; } +}; + +// Exercise the actual production base declarations through references. On +// fixed STM32WL the shadows cannot add a runtime capability; everywhere else +// all eight overrides must remain virtual and reachable through the API. +__attribute__((noinline)) void check(mesh::MainBoard& board, mesh::Radio& radio) { + constexpr bool fixed = EXPECT_FIXED_REGULATOR_API; + assert(board.mcuRegulatorModes() == (fixed ? 0 : 3)); + assert(radio.regulatorModes() == (fixed ? 0 : 3)); + assert(board.defaultMcuRegulatorMode() == (fixed ? RegulatorMode::Default : RegulatorMode::Dcdc)); + assert(radio.defaultRegulatorMode() == (fixed ? RegulatorMode::Default : RegulatorMode::Dcdc)); + assert(board.applyMcuRegulator(RegulatorMode::Dcdc) == !fixed); + assert(radio.configureBootRegulator(RegulatorMode::Dcdc) == !fixed); + assert(board.activeMcuRegulatorMode() == (fixed ? RegulatorMode::Default : RegulatorMode::Ldo)); + assert(radio.activeRegulatorMode() == (fixed ? RegulatorMode::Default : RegulatorMode::Ldo)); +} +int main() { + static_assert(MESH_REGULATOR_FIXED_SUPPLY == EXPECT_FIXED_REGULATOR_API, + "the fixed supply guard must match this exact platform/driver"); + BoardSelection board; + RadioSelection radio; + assert(board.mcuRegulatorModes() == 3 && radio.regulatorModes() == 3); + check(board, radio); +} diff --git a/test/test_regulator_settings.py b/test/test_regulator_settings.py index c2fed36fb..886d79825 100644 --- a/test/test_regulator_settings.py +++ b/test/test_regulator_settings.py @@ -276,5 +276,49 @@ class RegulatorSettingsTests(unittest.TestCase): self.assertNotIn('beginRegulatorSettings(',other) +class RegulatorAPIDispatchTests(unittest.TestCase): + def compile_api(self, flags, fixed, require_virtual=False): + compiler = os.environ.get('CXX') or shutil.which('g++') or shutil.which('clang++') + if not compiler: + self.skipTest('native C++ compiler unavailable') + with tempfile.TemporaryDirectory(prefix='mesh-regulator-api-') as directory: + folder = Path(directory) + (folder/'Stream.h').write_text('#pragma once\nclass Stream {};\n') + binary = folder/'test' + command = [compiler,'-std=c++17','-O2','-Wall','-Wextra','-Werror', + '-Wno-unused-parameter','-Wno-reorder',*SANITIZER_FLAGS,*flags, + f'-DEXPECT_FIXED_REGULATOR_API={int(fixed)}', + '-I',str(folder),'-I',str(ROOT/'src'), + str(ROOT/'test/fixtures/regulator_api_dispatch.cpp'),'-o',str(binary)] + if require_virtual: + command.append('-DREQUIRE_VIRTUAL_REGULATOR_API=1') + result = subprocess.run(command,text=True,capture_output=True,timeout=45) + if require_virtual and fixed: + self.assertNotEqual(result.returncode,0) + for method in ('mcuRegulatorModes','defaultMcuRegulatorMode', + 'applyMcuRegulator','activeMcuRegulatorMode','regulatorModes', + 'defaultRegulatorMode','configureBootRegulator','activeRegulatorMode'): + self.assertIn(method,result.stderr) + else: + self.assertEqual(result.returncode,0,result.stdout+result.stderr) + result = subprocess.run([str(binary)],text=True,capture_output=True,timeout=10) + self.assertEqual(result.returncode,0,result.stdout+result.stderr) + + def test_actual_fixed_stm32wl_base_defaults_are_not_overridden(self): + self.compile_api(['-DSTM32_PLATFORM=1','-DRADIO_CLASS=CustomSTM32WLx'],True) + + def test_all_eight_fixed_stm32wl_regulator_methods_reject_override_annotations(self): + self.compile_api(['-DSTM32_PLATFORM=1','-DRADIO_CLASS=CustomSTM32WLx'],True,True) + + def test_other_platforms_and_future_stm32_radios_keep_all_eight_virtual_methods(self): + for flags in ([],['-DESP32=1','-DRADIO_CLASS=CustomSTM32WLx'], + ['-DNRF52_PLATFORM=1','-DRADIO_CLASS=CustomSX1262'], + ['-DRP2040_PLATFORM=1','-DRADIO_CLASS=CustomSX1262'], + ['-DSTM32_PLATFORM=1','-DRADIO_CLASS=CustomSX1262'], + ['-DSTM32_PLATFORM=1']): + with self.subTest(flags=flags): + self.compile_api(flags,False,True) + + if __name__ == '__main__': unittest.main()