From e50296ceab22d49463b7bbe7174a98532afba9f5 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Jun 2022 16:30:14 +0200 Subject: [PATCH] Adds implementation of factory_reset command. (#624) * Adds implementation of factory_reset command. Adds unit test for factory reset and reboot commands. * Adds a reboot after the factory reset is complete and quiesced. * Removes wrong comment. --- src/openlcb/MemoryConfig.cxx | 17 +++++++++ src/openlcb/MemoryConfig.cxxtest | 59 +++++++++++++++++++++++++++++++- src/openlcb/MemoryConfig.hxx | 25 ++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/openlcb/MemoryConfig.cxx b/src/openlcb/MemoryConfig.cxx index fd543f154..f6d6ee215 100644 --- a/src/openlcb/MemoryConfig.cxx +++ b/src/openlcb/MemoryConfig.cxx @@ -44,6 +44,8 @@ #include "can_ioctl.h" #endif +#include "openlcb/ConfigUpdateFlow.hxx" + extern "C" { /// Implement this function (usually in HwInit.cxx) to enter the /// bootloader. Usual implementations write some magic value to RAM and the @@ -65,6 +67,21 @@ void reboot() namespace openlcb { +#ifdef GTEST +static constexpr unsigned FACTORY_RESET_REBOOT_DELAY_MSEC = 50; +#else +static constexpr unsigned FACTORY_RESET_REBOOT_DELAY_MSEC = 500; +#endif + + +void __attribute__((weak)) MemoryConfigHandler::handle_factory_reset() +{ + static_cast(ConfigUpdateFlow::instance()) + ->factory_reset(); + (new RebootTimer(service())) + ->start(MSEC_TO_NSEC(FACTORY_RESET_REBOOT_DELAY_MSEC)); +} + FileMemorySpace::FileMemorySpace(int fd, address_t len) : fileSize_(len) , name_(nullptr) diff --git a/src/openlcb/MemoryConfig.cxxtest b/src/openlcb/MemoryConfig.cxxtest index 88dba9471..839a3115c 100644 --- a/src/openlcb/MemoryConfig.cxxtest +++ b/src/openlcb/MemoryConfig.cxxtest @@ -32,9 +32,12 @@ * @date 23 Feb 2014 */ -#include "utils/async_datagram_test_helper.hxx" #include "openlcb/MemoryConfig.hxx" +#include "utils/async_datagram_test_helper.hxx" +#include "utils/ConfigUpdateListener.hxx" +#include "openlcb/ConfigUpdateFlow.hxx" + #include #include #include @@ -372,6 +375,60 @@ TEST_F(MemoryConfigTest, GetSpaceInfoRO_NZLA) wait(); } +struct GlobalMock : public Singleton { + MOCK_METHOD0(reboot, void()); + MOCK_METHOD0(factory_reset, void()); +}; + +extern "C" void reboot() +{ + GlobalMock::instance()->reboot(); +} + +struct FactoryResetListener : public DefaultConfigUpdateListener +{ + void factory_reset(int fd) override + { + GlobalMock::instance()->factory_reset(); + } + + UpdateAction apply_configuration( + int fd, bool initial_load, BarrierNotifiable *done) + { + done->notify(); + return UPDATED; + } +}; + +TEST_F(MemoryConfigTest, Reboot) +{ + StrictMock mock; + // Normally, a reboot function never returns. We can't do that under linux + // though, so the stack will generate an error datagram reply. + expect_packet(":X19A4822AN077C1041;"); // unsupported command + + EXPECT_CALL(mock, reboot()); + send_packet(":X1A22A77CN20A9;"); + wait(); +} + +TEST_F(MemoryConfigTest, FactoryReset) +{ + StrictMock mock; + ConfigUpdateFlow update_flow{ifCan_.get()}; + update_flow.TEST_set_fd(23); + + FactoryResetListener l; + expect_packet(":X19A2822AN077C00;"); // received OK, no response + + EXPECT_CALL(mock, factory_reset()); + send_packet(":X1A22A77CN20AA;"); + wait(); + + EXPECT_CALL(mock, reboot()); + twait(); +} + static const char MEMORY_BLOCK_DATA[] = "abrakadabra12345678xxxxyyyyzzzzwww."; class StaticBlockTest : public MemoryConfigTest diff --git a/src/openlcb/MemoryConfig.hxx b/src/openlcb/MemoryConfig.hxx index 04dd3cffc..218a7b151 100644 --- a/src/openlcb/MemoryConfig.hxx +++ b/src/openlcb/MemoryConfig.hxx @@ -482,6 +482,11 @@ private: #endif return respond_reject(Defs::ERROR_UNIMPLEMENTED_SUBCMD); } + case MemoryConfigDefs::COMMAND_FACTORY_RESET: + { + handle_factory_reset(); + return respond_ok(0); + } case MemoryConfigDefs::COMMAND_OPTIONS: { return call_immediately(STATE(handle_options)); @@ -513,6 +518,26 @@ private: } } + /// Used internally by the factory_reset implementation to reboot the + /// binary asynchronously. + class RebootTimer : public ::Timer + { + public: + RebootTimer(Service *s) + : ::Timer(s->executor()->active_timers()) + { } + + long long timeout() override + { + reboot(); + return DELETE; + } + }; + + /// Invokes the openlcb config handler to do a factory reset. Starts a + /// timer to reboot the device after a little time. + void handle_factory_reset(); + Action ok_response_sent() OVERRIDE { if (!response_.empty())