Preserve regulator recovery through planned resets and storage failures
This commit is contained in:
1 parent
c4a1d485ff
commit
c2998ae8d9
11 files changed
+236
-16
No files matched your search
@@ -3604,7 +3604,7 @@ void setup() {
|
||||
FILESYSTEM* regulator_prefs_fs = store.getPrimaryFS();
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (!mesh::storage::internalPrimaryFilesystemReady(primary_fs_boot)
|
||||
&& !store.isVolatilePrimaryFS()) regulator_prefs_fs = nullptr;
|
||||
|| store.isVolatilePrimaryFS()) regulator_prefs_fs = nullptr;
|
||||
#endif
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
|
||||
@@ -204,7 +204,11 @@ void setup() {
|
||||
#else
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, fs)) {
|
||||
FILESYSTEM* regulator_prefs_fs = fs;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (volatile_primary_fs) regulator_prefs_fs = nullptr;
|
||||
#endif
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
mesh::hilStartupTrace("fs_ready");
|
||||
|
||||
@@ -138,7 +138,11 @@ void setup() {
|
||||
#else
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, fs)) {
|
||||
FILESYSTEM* regulator_prefs_fs = fs;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (volatile_primary_fs) regulator_prefs_fs = nullptr;
|
||||
#endif
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
#ifdef DISPLAY_CLASS
|
||||
|
||||
@@ -163,7 +163,11 @@ void setup() {
|
||||
#else
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, fs)) {
|
||||
FILESYSTEM* regulator_prefs_fs = fs;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (volatile_primary_fs) regulator_prefs_fs = nullptr;
|
||||
#endif
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
#ifdef DISPLAY_CLASS
|
||||
|
||||
@@ -33,6 +33,7 @@ static bool ota_ble_started = false;
|
||||
// Intentional reboot paths share the recovery policy's clean-reset marker.
|
||||
// Keep this optional for standalone board/library builds without the policy.
|
||||
extern "C" void meshRegulatorCleanReset() __attribute__((weak));
|
||||
extern "C" void meshRegulatorResumeRecovery() __attribute__((weak));
|
||||
|
||||
namespace {
|
||||
volatile mesh::power::RecoveryRecord regulator_recovery_record
|
||||
@@ -313,9 +314,15 @@ void NRF52Board::feedWatchdog(bool enabled) {
|
||||
// setting is turned off, deliberately stop reloading it; the resulting
|
||||
// watchdog reset is the only software-only way to return it to the stopped
|
||||
// state. On that next boot the disabled preference prevents it starting.
|
||||
if (!enabled) return;
|
||||
|
||||
const bool running = NRF_WDT->RUNSTATUS != 0;
|
||||
if (!enabled) {
|
||||
if (running && !watchdog_disable_reset_pending) {
|
||||
watchdog_disable_reset_pending = true;
|
||||
if (meshRegulatorCleanReset) meshRegulatorCleanReset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!running) {
|
||||
// Keep running during CPU sleep: the flash-driver failure this protects
|
||||
// against sleeps in sd_app_evt_wait(). Pause while halted so breakpoints
|
||||
@@ -329,6 +336,12 @@ void NRF52Board::feedWatchdog(bool enabled) {
|
||||
}
|
||||
|
||||
reloadWatchdogChannels();
|
||||
if (watchdog_disable_reset_pending) {
|
||||
watchdog_disable_reset_pending = false;
|
||||
// Cancel a planned watchdog-off reset with fresh recovery state. Feed
|
||||
// first so the callback's battery sampling cannot race an imminent expiry.
|
||||
if (meshRegulatorResumeRecovery) meshRegulatorResumeRecovery();
|
||||
}
|
||||
#else
|
||||
(void)enabled;
|
||||
#endif
|
||||
|
||||
@@ -37,7 +37,8 @@ protected:
|
||||
uint8_t startup_reason;
|
||||
char *ota_name;
|
||||
bool cpu_dcdc_supported = false;
|
||||
mesh::power::RegulatorMode cpu_regulator_mode = mesh::power::RegulatorMode::Ldo;
|
||||
mesh::power::RegulatorMode cpu_regulator_mode = mesh::power::RegulatorMode::Default;
|
||||
bool watchdog_disable_reset_pending = false;
|
||||
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
void pwrmgtInit() { initPowerMgr(); }
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include "NRF52VoltageRules.h"
|
||||
#include "NRF52VoltagePolicy.h"
|
||||
#include "PersistentStoreFormat.h"
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include "AtomicFileWriter.h"
|
||||
#endif
|
||||
#include <Arduino.h>
|
||||
#include <Dispatcher.h>
|
||||
#include <stdio.h>
|
||||
@@ -51,6 +54,22 @@ bool readFile(const char* name, uint8_t (&data)[12]) {
|
||||
}
|
||||
bool save(RegulatorMode mcu, RegulatorMode radio, uint8_t fallback) {
|
||||
if (!settings_fs) return false;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
uint8_t data[12], checked[12];
|
||||
encode(mcu, radio, fallback, data);
|
||||
// The shared LittleFS writer already stages, flushes, checks size/CRC32C,
|
||||
// atomically publishes and verifies the public file. Reuse it rather than
|
||||
// linking a second replacement transaction into flash-constrained targets.
|
||||
mesh::AtomicFileWriter file(settings_fs, path);
|
||||
if (!file || file.write(data, sizeof(data)) != sizeof(data) || !file.commit()) return false;
|
||||
// Retain the format validation and exact-byte comparison as well as the
|
||||
// writer's independent transaction checksum. Legacy .bak files remain
|
||||
// readable at boot and are retired only after this verified publication.
|
||||
if (!readFile(path, checked) || memcmp(data, checked, sizeof(data)) != 0) return false;
|
||||
settings_fs->remove(backup);
|
||||
saved_fallback = fallback;
|
||||
return true;
|
||||
#else
|
||||
settings_fs->remove(temporary);
|
||||
if (settings_fs->exists(temporary)) return false;
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
@@ -78,6 +97,7 @@ bool save(RegulatorMode mcu, RegulatorMode radio, uint8_t fallback) {
|
||||
settings_fs->remove(backup);
|
||||
saved_fallback = fallback;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t readBatteryPercent(bool& valid) {
|
||||
@@ -256,9 +276,25 @@ void markRegulatorCleanReset() {
|
||||
}
|
||||
}
|
||||
|
||||
void resumeRegulatorRecovery() {
|
||||
if (!initialized) return;
|
||||
bool valid = false;
|
||||
const uint8_t percent = readBatteryPercent(valid);
|
||||
const uint32_t now = millis();
|
||||
const uint8_t active_dcdc =
|
||||
(activeRegulatorMode(true) == RegulatorMode::Dcdc ? RECOVERY_MCU : 0)
|
||||
| (activeRegulatorMode(false) == RegulatorMode::Dcdc ? RECOVERY_RADIO : 0);
|
||||
recovery().arm(active_dcdc, now, percent, valid);
|
||||
last_service = last_battery = now;
|
||||
}
|
||||
|
||||
}} // namespace mesh::power
|
||||
|
||||
extern "C" void meshRegulatorCleanReset() {
|
||||
// Intentional resets can happen before the settings/filesystem is ready.
|
||||
mesh::power::markRegulatorCleanReset();
|
||||
}
|
||||
|
||||
extern "C" void meshRegulatorResumeRecovery() {
|
||||
mesh::power::resumeRegulatorRecovery();
|
||||
}
|
||||
@@ -21,3 +21,4 @@ bool regulatorRecoveryAvailable();
|
||||
}}
|
||||
|
||||
extern "C" void meshRegulatorCleanReset();
|
||||
extern "C" void meshRegulatorResumeRecovery();
|
||||
+16
-2
@@ -34,6 +34,13 @@ uint8_t configuredBatteryPercent(uint16_t mv) {
|
||||
#if !defined(NRF52_PLATFORM)
|
||||
#include <helpers/RegulatorPlatform.cpp>
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM)
|
||||
// Compile the production writer implementation, including independent CRC32C
|
||||
// validation and the public-file verification after an attempted rename.
|
||||
#define ATOMIC_FILE_WRITER_IMPLEMENTATION
|
||||
#include <helpers/AtomicFileWriter.h>
|
||||
#undef ATOMIC_FILE_WRITER_IMPLEMENTATION
|
||||
#endif
|
||||
#include <helpers/RegulatorSettings.cpp>
|
||||
using namespace mesh::power;
|
||||
static void retainedClear() {
|
||||
@@ -157,12 +164,19 @@ int main(int argc,char** argv) {
|
||||
command("set mcu.regulator ldo");const auto old=fs.files["/regulator_v1"];
|
||||
if(failure==0)fs.fail_write=true;
|
||||
if(failure==1)fs.fail_open=true;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if(failure==2)fs.fail_rename_from="/regulator_v1.tmp";
|
||||
if(failure==3)fs.fail_read_path="/regulator_v1.tmp";
|
||||
if(failure==4)fs.corrupt_write=true;
|
||||
#else
|
||||
if(failure==2)fs.fail_rename_from="/regulator_v1";
|
||||
if(failure==3)fs.fail_rename_from="/regulator_v1.tmp";
|
||||
if(failure==4) {fs.fail_rename_from="/regulator_v1.tmp";fs.fail_restore=true;}
|
||||
#endif
|
||||
assert(command("set radio.regulator ldo").find("save failed")!=std::string::npos);
|
||||
assert(requestedRegulatorMode(false)==RegulatorMode::Default);
|
||||
fs.fail_write=fs.fail_open=fs.fail_restore=false;fs.fail_rename_from.clear();
|
||||
fs.fail_write=fs.fail_open=fs.fail_restore=fs.corrupt_write=false;
|
||||
fs.fail_rename_from.clear();fs.fail_read_path.clear();
|
||||
fresh(board,radio,fs,ResetKind::Cold,true);
|
||||
assert(requestedRegulatorMode(true)==RegulatorMode::Ldo);
|
||||
assert(requestedRegulatorMode(false)==RegulatorMode::Default);
|
||||
@@ -222,7 +236,7 @@ int main(int argc,char** argv) {
|
||||
assert(beginRegulatorSettings(board,radio,nullptr));radio.initialize();
|
||||
assert(command("set radio.regulator ldo").find("save failed")!=std::string::npos);
|
||||
assert(requestedRegulatorMode(false)==RegulatorMode::Default);
|
||||
assert(radio.active==RegulatorMode::Dcdc);
|
||||
assert(radio.active==RegulatorMode::Dcdc && fs.files.empty());
|
||||
} else if(test=="failed_radio") {
|
||||
fresh(board,radio,fs,ResetKind::Cold,true);
|
||||
for(unsigned n=1;n<=3;++n) {
|
||||
|
||||
@@ -28,6 +28,128 @@ def run_native(source):
|
||||
|
||||
|
||||
class Nrf52RegulatorTests(unittest.TestCase):
|
||||
def test_watchdog_off_marks_once_and_cancellation_rearms_after_reload(self):
|
||||
source = BOARD.read_text()
|
||||
methods = "\n".join(body(source, signature) for signature in (
|
||||
"void NRF52Board::feedWatchdog(bool enabled)",
|
||||
"void NRF52Board::serviceWatchdog()",
|
||||
))
|
||||
reload = body(source, "static void reloadWatchdogChannels()")
|
||||
fixture = r'''
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include "helpers/RegulatorRecovery.h"
|
||||
using namespace mesh::power;
|
||||
constexpr uint32_t WDT_RR_RR_Reload = 0x6e524635;
|
||||
constexpr unsigned WDT_CONFIG_SLEEP_Run = 1, WDT_CONFIG_SLEEP_Pos = 0;
|
||||
constexpr unsigned WDT_CONFIG_HALT_Pause = 0, WDT_CONFIG_HALT_Pos = 1;
|
||||
constexpr unsigned WDT_RREN_RR0_Msk = 1;
|
||||
static unsigned reloads, clean_marks, resumes;
|
||||
static uint32_t now = 1000;
|
||||
static uint8_t fresh_percent = 75;
|
||||
struct ReloadRegister {
|
||||
uint32_t value = 0;
|
||||
void operator=(uint32_t next) { assert(next == WDT_RR_RR_Reload); value = next; ++reloads; }
|
||||
};
|
||||
struct Watchdog {
|
||||
uint32_t RUNSTATUS = 0, RREN = 0, CONFIG = 0, CRV = 0, TASKS_START = 0;
|
||||
ReloadRegister RR[8];
|
||||
} watchdog;
|
||||
#define NRF_WDT (&watchdog)
|
||||
static volatile RecoveryRecord retained{};
|
||||
static RecoveryPolicy policy(retained);
|
||||
static void (*meshRegulatorCleanReset)();
|
||||
static void (*meshRegulatorResumeRecovery)();
|
||||
void markClean() { ++clean_marks; policy.markCleanReset(); }
|
||||
void resumeRecovery() {
|
||||
// The callback can sample the ADC only after the nearly-expired hardware
|
||||
// watchdog has been reloaded. Both bootloader-owned enabled channels matter.
|
||||
assert(watchdog.RR[0].value == WDT_RR_RR_Reload);
|
||||
assert(watchdog.RR[3].value == WDT_RR_RR_Reload);
|
||||
++resumes;
|
||||
policy.arm(RECOVERY_DOMAINS, now, fresh_percent, true);
|
||||
}
|
||||
struct NRF52Board {
|
||||
bool watchdog_disable_reset_pending = false;
|
||||
void feedWatchdog(bool);
|
||||
void serviceWatchdog();
|
||||
};
|
||||
#if NRF52_WATCHDOG_TIMEOUT_SECONDS > 0
|
||||
@RELOAD@
|
||||
#endif
|
||||
@METHODS@
|
||||
int main() {
|
||||
constexpr bool callbacks = @CALLBACKS@;
|
||||
meshRegulatorCleanReset = callbacks ? markClean : nullptr;
|
||||
meshRegulatorResumeRecovery = callbacks ? resumeRecovery : nullptr;
|
||||
policy.begin(ResetKind::Cold, 0);
|
||||
policy.arm(RECOVERY_DOMAINS, 1, 75, true);
|
||||
for (unsigned crash = 0; crash < 2; ++crash) {
|
||||
policy.begin(ResetKind::Crash, 0);
|
||||
policy.arm(RECOVERY_DOMAINS, 1, 75, true);
|
||||
}
|
||||
assert(policy.crashCount(RECOVERY_MCU) == 2);
|
||||
NRF52Board board;
|
||||
board.feedWatchdog(false);
|
||||
board.feedWatchdog(false);
|
||||
assert(clean_marks == 0 && resumes == 0 && reloads == 0);
|
||||
assert(policy.crashCount(RECOVERY_MCU) == 2); // Never-started WDT is neutral.
|
||||
board.feedWatchdog(true);
|
||||
#if NRF52_WATCHDOG_TIMEOUT_SECONDS == 0
|
||||
watchdog.RUNSTATUS = 1;
|
||||
board.feedWatchdog(false);
|
||||
board.feedWatchdog(true);
|
||||
board.serviceWatchdog();
|
||||
assert(clean_marks == 0 && resumes == 0 && reloads == 0);
|
||||
assert(policy.crashCount(RECOVERY_MCU) == 2);
|
||||
return 0;
|
||||
#else
|
||||
assert(watchdog.TASKS_START == 1 && watchdog.RREN == 1);
|
||||
assert(watchdog.CRV == NRF52_WATCHDOG_TIMEOUT_SECONDS * 32768UL);
|
||||
assert(reloads == 1 && clean_marks == 0 && resumes == 0);
|
||||
assert(policy.crashCount(RECOVERY_MCU) == 2); // Normal feeding keeps streak.
|
||||
watchdog.RUNSTATUS = 1;
|
||||
watchdog.RREN = 9; // A bootloader can leave more than RR0 enabled.
|
||||
const unsigned before_off = reloads;
|
||||
board.feedWatchdog(false);
|
||||
board.feedWatchdog(false);
|
||||
assert(reloads == before_off && clean_marks == unsigned(callbacks));
|
||||
assert(board.watchdog_disable_reset_pending);
|
||||
if (callbacks) {
|
||||
RecoveryPolicy planned_reset(retained);
|
||||
planned_reset.begin(ResetKind::Crash, 0);
|
||||
assert(planned_reset.crashCount(RECOVERY_MCU) == 0 && planned_reset.fallbackMask() == 0);
|
||||
}
|
||||
// Keep the existing explicit service hook: it feeds already-running WDT
|
||||
// channels without starting one or changing a deliberate-off transition.
|
||||
board.serviceWatchdog();
|
||||
assert(reloads == before_off + 2 && resumes == 0);
|
||||
watchdog.RR[0].value = watchdog.RR[3].value = 0;
|
||||
board.feedWatchdog(true);
|
||||
assert(!board.watchdog_disable_reset_pending);
|
||||
assert(reloads == before_off + 4 && resumes == unsigned(callbacks));
|
||||
board.feedWatchdog(true);
|
||||
assert(clean_marks == unsigned(callbacks) && resumes == unsigned(callbacks));
|
||||
if (callbacks) {
|
||||
RecoveryPolicy real_crash(retained);
|
||||
real_crash.begin(ResetKind::Crash, 0);
|
||||
assert(real_crash.crashCount(RECOVERY_MCU) == 1);
|
||||
assert(real_crash.crashCount(RECOVERY_RADIO) == 1 && real_crash.fallbackMask() == 0);
|
||||
}
|
||||
// A subsequent real off transition is marked once again.
|
||||
board.feedWatchdog(false);
|
||||
board.feedWatchdog(false);
|
||||
assert(clean_marks == 2 * unsigned(callbacks));
|
||||
assert(resumes == unsigned(callbacks));
|
||||
#endif
|
||||
}
|
||||
'''.replace("@RELOAD@", reload).replace("@METHODS@", methods)
|
||||
for timeout in (0, 60):
|
||||
for callbacks in (False, True):
|
||||
with self.subTest(timeout=timeout, callbacks=callbacks):
|
||||
run_native(("#define NRF52_WATCHDOG_TIMEOUT_SECONDS %d\n" % timeout)
|
||||
+ fixture.replace("@CALLBACKS@", str(callbacks).lower()))
|
||||
|
||||
def test_saved_cpu_mode_uses_checked_sdk_and_rejects_unsupported_boards(self):
|
||||
source = BOARD.read_text()
|
||||
methods = "\n".join(body(source, signature) for signature in (
|
||||
@@ -36,6 +158,8 @@ class Nrf52RegulatorTests(unittest.TestCase):
|
||||
"mesh::power::RegulatorMode NRF52Board::activeMcuRegulatorMode() const",
|
||||
"bool NRF52Board::applyMcuRegulator(mesh::power::RegulatorMode mode)",
|
||||
))
|
||||
initial_mode = (ROOT / "src/helpers/NRF52Board.h").read_text().split(
|
||||
"cpu_regulator_mode = ", 1)[1].split(";", 1)[0]
|
||||
run_native(r'''
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
@@ -62,7 +186,7 @@ uint32_t sd_power_dcdc_mode_set(uint8_t mode) {
|
||||
}
|
||||
struct NRF52Board {
|
||||
bool cpu_dcdc_supported = false;
|
||||
RegulatorMode cpu_regulator_mode = RegulatorMode::Ldo;
|
||||
RegulatorMode cpu_regulator_mode = @INITIAL_MODE@;
|
||||
uint8_t mcuRegulatorModes() const;
|
||||
RegulatorMode defaultMcuRegulatorMode() const;
|
||||
RegulatorMode activeMcuRegulatorMode() const;
|
||||
@@ -81,7 +205,13 @@ int main() {
|
||||
board.cpu_dcdc_supported = true;
|
||||
assert(board.mcuRegulatorModes() == 3);
|
||||
assert(board.defaultMcuRegulatorMode() == RegulatorMode::Dcdc);
|
||||
assert(board.activeMcuRegulatorMode() == RegulatorMode::Default);
|
||||
assert(!board.applyMcuRegulator(static_cast<RegulatorMode>(255)));
|
||||
softdevice = 1;
|
||||
setter_result = 4;
|
||||
assert(!board.applyMcuRegulator(RegulatorMode::Ldo));
|
||||
assert(board.activeMcuRegulatorMode() == RegulatorMode::Default);
|
||||
setter_calls = 0;
|
||||
for (softdevice = 0; softdevice < 2; ++softdevice) {
|
||||
power.DCDCEN = 77;
|
||||
query_result = 5;
|
||||
@@ -104,7 +234,7 @@ int main() {
|
||||
setter_calls = 0;
|
||||
}
|
||||
}
|
||||
'''.replace("@METHODS@", methods))
|
||||
'''.replace("@METHODS@", methods).replace("@INITIAL_MODE@", initial_mode))
|
||||
|
||||
def test_reset_classifier_excludes_wakes_and_intentional_resets(self):
|
||||
method = body(BOARD.read_text(), "ResetKind platformResetKind()")
|
||||
|
||||
@@ -29,18 +29,24 @@ struct File {
|
||||
size_t read(uint8_t*,size_t);
|
||||
size_t write(const uint8_t*,size_t);
|
||||
void close() {}
|
||||
void flush() {}
|
||||
bool isDirectory() const { return false; }
|
||||
};
|
||||
struct MockFS {
|
||||
std::map<std::string,std::vector<uint8_t>> files;
|
||||
bool fail_open=false,fail_write=false,fail_restore=false;
|
||||
std::string fail_rename_from;
|
||||
bool fail_open=false,fail_write=false,fail_restore=false,corrupt_write=false;
|
||||
std::string fail_rename_from,fail_read_path;
|
||||
unsigned writes=0,renames=0;
|
||||
bool exists(const char* name) const { return files.count(name); }
|
||||
bool remove(const char* name) { return files.erase(name)!=0; }
|
||||
bool rename(const char* from,const char* to) {
|
||||
++renames;
|
||||
if(fail_rename_from==from || (fail_restore && std::string(from)=="/regulator_v1.bak"))return false;
|
||||
if(!files.count(from)||files.count(to))return false;
|
||||
if(!files.count(from))return false;
|
||||
#if !defined(NRF52_PLATFORM)
|
||||
if(files.count(to))return false;
|
||||
#endif
|
||||
// Adafruit LittleFS atomically replaces an existing destination.
|
||||
files[to]=files[from];files.erase(from);return true;
|
||||
}
|
||||
File open(const char* name,const char* mode,bool=false) {
|
||||
@@ -54,14 +60,16 @@ inline bool File::open(const char* key,int flags) {
|
||||
}
|
||||
inline size_t File::size() const { return valid ? fs->files[name].size() : 0; }
|
||||
inline size_t File::read(uint8_t* bytes,size_t length) {
|
||||
if(!valid)return 0;
|
||||
if(!valid || fs->fail_read_path==name)return 0;
|
||||
const auto& data=fs->files[name];length=std::min(length,data.size()-cursor);
|
||||
memcpy(bytes,data.data()+cursor,length);cursor+=length;return length;
|
||||
}
|
||||
inline size_t File::write(const uint8_t* bytes,size_t length) {
|
||||
if(!valid)return 0;
|
||||
++fs->writes;if(fs->fail_write)length/=2;
|
||||
auto& data=fs->files[name];data.insert(data.end(),bytes,bytes+length);return length;
|
||||
auto& data=fs->files[name];data.insert(data.end(),bytes,bytes+length);
|
||||
if(fs->corrupt_write && !data.empty())data.back()^=1;
|
||||
return length;
|
||||
}
|
||||
#define FILESYSTEM MockFS
|
||||
#define FILE_O_READ 0
|
||||
@@ -222,6 +230,11 @@ class RegulatorSettingsTests(unittest.TestCase):
|
||||
self.assertEqual(setup.count(hook),1)
|
||||
self.assertLess(setup.index('board.begin()'),setup.index(hook))
|
||||
self.assertLess(setup.index(hook),setup.index('radio_init()'))
|
||||
self.assertIn('beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)',setup)
|
||||
if role == 'companion_radio':
|
||||
self.assertIn('store.isVolatilePrimaryFS()',setup)
|
||||
else:
|
||||
self.assertIn('if (volatile_primary_fs) regulator_prefs_fs = nullptr',setup)
|
||||
if role == 'simple_repeater':
|
||||
self.assertEqual(loop.count('serviceCommandInterfaces()'),1)
|
||||
loop = method(source,'serviceCommandInterfaces() {')
|
||||
|
||||
Reference in new issue
Block a user