From db2221741f931374a3f565859c39ede4db635bdd Mon Sep 17 00:00:00 2001 From: Nav Date: Sun, 30 May 2021 16:52:32 +0100 Subject: [PATCH] TargetController suspension --- .../GdbRsp/Exceptions/DebugSessionAborted.hpp | 26 ++ src/DebugServers/GdbRsp/GdbRspDebugServer.cpp | 29 +- .../Microchip/AtmelICE/AtmelIce.hpp | 1 - .../Microchip/MplabSnap/MplabSnap.hpp | 1 - .../Microchip/PowerDebugger/PowerDebugger.hpp | 1 - src/EventManager/EventListener.cpp | 4 +- src/EventManager/EventListener.hpp | 34 ++ src/EventManager/Events/Events.hpp | 4 +- ...nged.hpp => InsightThreadStateChanged.hpp} | 10 +- .../Events/ReportTargetControllerState.hpp | 20 + .../Events/TargetControllerStateReported.hpp | 23 ++ src/TargetController/TargetController.cpp | 365 +++++++++++------- src/TargetController/TargetController.hpp | 39 +- .../TargetControllerConsole.cpp | 29 +- .../TargetControllerConsole.hpp | 6 + .../TargetControllerState.hpp | 10 + src/Targets/TargetDescriptor.hpp | 1 + 17 files changed, 441 insertions(+), 162 deletions(-) create mode 100644 src/DebugServers/GdbRsp/Exceptions/DebugSessionAborted.hpp rename src/EventManager/Events/{InsightStateChanged.hpp => InsightThreadStateChanged.hpp} (56%) create mode 100644 src/EventManager/Events/ReportTargetControllerState.hpp create mode 100644 src/EventManager/Events/TargetControllerStateReported.hpp create mode 100644 src/TargetController/TargetControllerState.hpp diff --git a/src/DebugServers/GdbRsp/Exceptions/DebugSessionAborted.hpp b/src/DebugServers/GdbRsp/Exceptions/DebugSessionAborted.hpp new file mode 100644 index 00000000..5cd406a2 --- /dev/null +++ b/src/DebugServers/GdbRsp/Exceptions/DebugSessionAborted.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "src/Exceptions/Exception.hpp" + +namespace Bloom::DebugServers::Gdb::Exceptions +{ + /** + * The GDB server may abort a debug session with the client, if an internal error occurs. One circumstance where + * this can happen is when the TargetController is not able to service the debug session for whatever reason. + * + * See GdbRspDebugServer::serve() for handling code. + */ + class DebugSessionAborted: public Bloom::Exceptions::Exception + { + public: + explicit DebugSessionAborted(const std::string& message): Bloom::Exceptions::Exception(message) { + this->message = message; + } + + explicit DebugSessionAborted(const char* message): Bloom::Exceptions::Exception(message) { + this->message = std::string(message); + } + + explicit DebugSessionAborted() = default; + }; +} diff --git a/src/DebugServers/GdbRsp/GdbRspDebugServer.cpp b/src/DebugServers/GdbRsp/GdbRspDebugServer.cpp index c5c90d2d..f4c5ea73 100644 --- a/src/DebugServers/GdbRsp/GdbRspDebugServer.cpp +++ b/src/DebugServers/GdbRsp/GdbRspDebugServer.cpp @@ -105,7 +105,19 @@ void GdbRspDebugServer::serve() { try { if (!this->clientConnection.has_value()) { Logger::info("Waiting for GDB RSP connection"); - this->waitForConnection(); + + while (!this->clientConnection.has_value()) { + this->waitForConnection(); + } + + /* + * Before proceeding with a new debug session, we must ensure that the TargetController is able to + * service it. + */ + if (!this->targetControllerConsole.isTargetControllerInService()) { + this->closeClientConnection(); + throw DebugSessionAborted("TargetController not in service"); + } } auto packets = this->clientConnection->readPackets(); @@ -121,12 +133,17 @@ void GdbRspDebugServer::serve() { return; } catch (const ClientCommunicationError& exception) { - Logger::error("GDB client communication error - " + exception.getMessage() + " - closing connection"); + Logger::error("GDB RSP client communication error - " + exception.getMessage() + " - closing connection"); this->closeClientConnection(); return; } catch (const ClientNotSupported& exception) { - Logger::error("Invalid GDB client - " + exception.getMessage() + " - closing connection"); + Logger::error("Invalid GDB RSP client - " + exception.getMessage() + " - closing connection"); + this->closeClientConnection(); + return; + + } catch (const DebugSessionAborted& exception) { + Logger::warning("GDB debug session aborted - " + exception.getMessage()); this->closeClientConnection(); return; @@ -163,13 +180,9 @@ void GdbRspDebugServer::waitForConnection() { this->clientConnection = Connection(this->interruptEventNotifier); this->clientConnection->accept(this->serverSocketFileDescriptor); - - Logger::info("Accepted GDP RSP connection from " + this->clientConnection->getIpAddress()); this->eventManager.triggerEvent(std::make_shared()); - } else { - // This method should not return until a connection has been established (or an exception is thrown) - return this->waitForConnection(); + Logger::info("Accepted GDP RSP connection from " + this->clientConnection->getIpAddress()); } } diff --git a/src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp b/src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp index 216083cf..abb7de51 100644 --- a/src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp +++ b/src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp @@ -69,7 +69,6 @@ namespace Bloom::DebugToolDrivers void close() override; - Protocols::CmsisDap::Edbg::EdbgInterface& getEdbgInterface() { return this->edbgInterface; } diff --git a/src/DebugToolDrivers/Microchip/MplabSnap/MplabSnap.hpp b/src/DebugToolDrivers/Microchip/MplabSnap/MplabSnap.hpp index 8e26f335..472ba776 100644 --- a/src/DebugToolDrivers/Microchip/MplabSnap/MplabSnap.hpp +++ b/src/DebugToolDrivers/Microchip/MplabSnap/MplabSnap.hpp @@ -55,7 +55,6 @@ namespace Bloom::DebugToolDrivers void close() override; - Protocols::CmsisDap::Edbg::EdbgInterface& getEdbgInterface() { return this->edbgInterface; } diff --git a/src/DebugToolDrivers/Microchip/PowerDebugger/PowerDebugger.hpp b/src/DebugToolDrivers/Microchip/PowerDebugger/PowerDebugger.hpp index cd5a9277..5cc8e1fe 100644 --- a/src/DebugToolDrivers/Microchip/PowerDebugger/PowerDebugger.hpp +++ b/src/DebugToolDrivers/Microchip/PowerDebugger/PowerDebugger.hpp @@ -55,7 +55,6 @@ namespace Bloom::DebugToolDrivers void close() override; - Protocols::CmsisDap::Edbg::EdbgInterface& getEdbgInterface() { return this->edbgInterface; } diff --git a/src/EventManager/EventListener.cpp b/src/EventManager/EventListener.cpp index b90b1801..df9a94ac 100644 --- a/src/EventManager/EventListener.cpp +++ b/src/EventManager/EventListener.cpp @@ -15,7 +15,7 @@ void EventListener::clearAllCallbacks() { void EventListener::registerEvent(GenericEventPointer event) { auto eventName = event->getName(); Logger::debug("Event \"" + eventName + "\" (" + std::to_string(event->id) - + ") registered for listener " + std::to_string(this->id)); + + ") registered for listener " + this->name); auto queueLock = this->eventQueueByEventType.acquireLock(); auto& eventQueueByType = this->eventQueueByEventType.getReference(); @@ -57,7 +57,7 @@ std::vector EventListener::getEvents() { void EventListener::dispatchEvent(GenericEventPointer event) { auto eventName = event->getName(); - Logger::debug("Dispatching event " + eventName + "."); + Logger::debug("Dispatching event " + eventName + " (" + std::to_string(event->id) + ")."); // Dispatch the event to all registered handlers auto mappingLock = this->eventTypeToCallbacksMapping.acquireLock(); auto& callbacks = this->eventTypeToCallbacksMapping.getReference().find(eventName)->second; diff --git a/src/EventManager/EventListener.hpp b/src/EventManager/EventListener.hpp index d3f20324..d4aada6b 100644 --- a/src/EventManager/EventListener.hpp +++ b/src/EventManager/EventListener.hpp @@ -141,6 +141,40 @@ namespace Bloom this->registeredEventTypes.getReference().insert(EventType::name); } + /** + * Clears all registered callbacks for a specific event type. + * + * @tparam EventType + */ + template + void deregisterCallbacksForEventType() { + static_assert( + std::is_base_of::value, + "EventType is not a derivation of Event" + ); + + { + auto mappingLock = this->eventTypeToCallbacksMapping.acquireLock(); + auto& mapping = this->eventTypeToCallbacksMapping.getReference(); + + if (mapping.contains(EventType::name)) { + mapping.at(EventType::name).clear(); + } + } + + { + auto registeredEventTypesLock = this->registeredEventTypes.acquireLock(); + this->registeredEventTypes.getReference().erase(EventType::name); + } + + auto queueLock = this->eventQueueByEventType.acquireLock(); + auto& eventQueueByType = this->eventQueueByEventType.getReference(); + + if (eventQueueByType.contains(EventType::name)) { + eventQueueByType.erase(EventType::name); + } + } + /** * Waits for an event (of type EventTypeA, EventTypeB or EventTypeC) to be dispatched to the listener. * Then returns the event object. If timeout is reached, an std::nullopt object will be returned. diff --git a/src/EventManager/Events/Events.hpp b/src/EventManager/Events/Events.hpp index 84eb6b65..e0d982c9 100644 --- a/src/EventManager/Events/Events.hpp +++ b/src/EventManager/Events/Events.hpp @@ -9,6 +9,8 @@ #include "DebugSessionStarted.hpp" #include "DebugSessionFinished.hpp" #include "TargetControllerThreadStateChanged.hpp" +#include "ReportTargetControllerState.hpp" +#include "TargetControllerStateReported.hpp" #include "ShutdownTargetController.hpp" #include "TargetControllerErrorOccurred.hpp" #include "ShutdownApplication.hpp" @@ -33,7 +35,7 @@ #include "ProgramCounterSetOnTarget.hpp" #include "ExtractTargetDescriptor.hpp" #include "TargetDescriptorExtracted.hpp" -#include "InsightStateChanged.hpp" +#include "InsightThreadStateChanged.hpp" #include "RetrieveTargetPinStates.hpp" #include "TargetPinStatesRetrieved.hpp" #include "SetTargetPinState.hpp" diff --git a/src/EventManager/Events/InsightStateChanged.hpp b/src/EventManager/Events/InsightThreadStateChanged.hpp similarity index 56% rename from src/EventManager/Events/InsightStateChanged.hpp rename to src/EventManager/Events/InsightThreadStateChanged.hpp index 27e80c84..420496ac 100644 --- a/src/EventManager/Events/InsightStateChanged.hpp +++ b/src/EventManager/Events/InsightThreadStateChanged.hpp @@ -7,19 +7,17 @@ namespace Bloom::Events { - class InsightStateChanged: public Event + class InsightThreadStateChanged: public Event { private: ThreadState state; public: - InsightStateChanged(ThreadState state): state(state) { + InsightThreadStateChanged(ThreadState state): state(state) {}; - }; - - static inline const std::string name = "InsightStateChanged"; + static inline const std::string name = "InsightThreadStateChanged"; std::string getName() const override { - return InsightStateChanged::name; + return InsightThreadStateChanged::name; } ThreadState getState() const { diff --git a/src/EventManager/Events/ReportTargetControllerState.hpp b/src/EventManager/Events/ReportTargetControllerState.hpp new file mode 100644 index 00000000..767e4476 --- /dev/null +++ b/src/EventManager/Events/ReportTargetControllerState.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "Event.hpp" + +namespace Bloom::Events +{ + class ReportTargetControllerState: public Event + { + public: + ReportTargetControllerState() {}; + + static inline const std::string name = "ReportTargetControllerState"; + + std::string getName() const override { + return ReportTargetControllerState::name; + } + }; +} diff --git a/src/EventManager/Events/TargetControllerStateReported.hpp b/src/EventManager/Events/TargetControllerStateReported.hpp new file mode 100644 index 00000000..1d848285 --- /dev/null +++ b/src/EventManager/Events/TargetControllerStateReported.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "Event.hpp" +#include "src/TargetController/TargetControllerState.hpp" + +namespace Bloom::Events +{ + class TargetControllerStateReported: public Event + { + public: + TargetControllerState state; + + TargetControllerStateReported(TargetControllerState state): state(state) {}; + + static inline const std::string name = "TargetControllerStateReported"; + + std::string getName() const override { + return TargetControllerStateReported::name; + } + }; +} diff --git a/src/TargetController/TargetController.cpp b/src/TargetController/TargetController.cpp index 1d4b1e5c..9aa634db 100644 --- a/src/TargetController/TargetController.cpp +++ b/src/TargetController/TargetController.cpp @@ -5,6 +5,7 @@ #include "TargetController.hpp" #include "src/Exceptions/InvalidConfig.hpp" #include "src/Exceptions/TargetControllerStartupFailure.hpp" +#include "src/Exceptions/DeviceCommunicationFailure.hpp" #include "src/Application.hpp" using namespace Bloom; @@ -19,9 +20,27 @@ void TargetController::run() { this->setThreadStateAndEmitEvent(ThreadState::READY); Logger::debug("TargetController ready and waiting for events."); - while (this->getState() == ThreadState::READY) { - this->fireTargetEvents(); - this->eventListener->waitAndDispatch(60); + while (this->getThreadState() == ThreadState::READY) { + try { + if (this->state == TargetControllerState::ACTIVE) { + this->fireTargetEvents(); + } + + this->eventListener->waitAndDispatch(60); + + } catch (const DeviceCommunicationFailure& exception) { + /* + * Upon a device communication failure, we assume Bloom has lost control of the debug tool. This could + * be the result of the user disconnecting the debug tool, or issuing a soft reset. + * The soft reset could have been issued via another application, without the user's knowledge. + * See https://github.com/navnavnav/Bloom/issues/3 for more on that. + * + * The TC will go into a suspended state and the DebugServer should terminate any active debug + * session. When the user attempts to start another debug session, we will try to re-connect to the + * debug tool. + */ + this->suspend(); + } } } catch (const TargetControllerStartupFailure& exception) { @@ -50,70 +69,11 @@ void TargetController::startup() { // Install Bloom's udev rules if not already installed this->checkUdevRules(); - auto debugToolName = this->environmentConfig.debugToolName; - auto targetName = this->environmentConfig.targetConfig.name; - auto supportedDebugTools = TargetController::getSupportedDebugTools(); - auto supportedTargets = TargetController::getSupportedTargets(); - - if (!supportedDebugTools.contains(debugToolName)) { - throw Exceptions::InvalidConfig( - "Debug tool name (\"" + debugToolName + "\") not recognised. Please check your configuration!" - ); - } - - if (!supportedTargets.contains(targetName)) { - throw Exceptions::InvalidConfig( - "Target name (\"" + targetName + "\") not recognised. Please check your configuration!" - ); - } - - // Initiate debug tool and target - this->setDebugTool(std::move(supportedDebugTools.at(debugToolName)())); - this->setTarget(supportedTargets.at(targetName)()); - - Logger::info("Connecting to debug tool"); - this->debugTool->init(); - - Logger::info("Debug tool connected"); - Logger::info("Debug tool name: " + this->debugTool->getName()); - Logger::info("Debug tool serial: " + this->debugTool->getSerialNumber()); - - if (!this->target->isDebugToolSupported(this->debugTool.get())) { - throw Exceptions::InvalidConfig( - "Debug tool (\"" + this->debugTool->getName() + "\") not supported " + - "by target (\"" + this->target->getName() + "\")." - ); - } - - this->target->setDebugTool(this->debugTool.get()); - this->target->preActivationConfigure(this->environmentConfig.targetConfig); - - Logger::info("Activating target"); - this->target->activate(); - Logger::info("Target activated"); - this->target->postActivationConfigure(); - - while (this->target->supportsPromotion()) { - auto promotedTarget = this->target->promote(); - - if (promotedTarget == nullptr - || std::type_index(typeid(*promotedTarget)) == std::type_index(typeid(*this->target)) - ) { - break; - } - - this->target.reset(promotedTarget.release()); - this->target->postPromotionConfigure(); - } - - Logger::info("Target ID: " + this->target->getHumanReadableId()); - Logger::info("Target name: " + this->target->getName()); - - if (this->target->getState() == TargetState::STOPPED) { - this->target->run(); - } - // Register event handlers + this->eventListener->registerCallbackForEventType( + std::bind(&TargetController::onStateReportRequest, this, std::placeholders::_1) + ); + this->eventListener->registerCallbackForEventType( std::bind(&TargetController::onShutdownTargetControllerEvent, this, std::placeholders::_1) ); @@ -122,6 +82,100 @@ void TargetController::startup() { std::bind(&TargetController::onDebugSessionStartedEvent, this, std::placeholders::_1) ); + this->resume(); +} + +void TargetController::checkUdevRules() { + auto bloomRulesPath = std::string("/etc/udev/rules.d/99-bloom.rules"); + auto latestBloomRulesPath = Application::getResourcesDirPath() + "/UDevRules/99-bloom.rules"; + + if (!std::filesystem::exists(bloomRulesPath)) { + Logger::warning("Bloom udev rules missing - attempting installation"); + + // We can only install them if we're running as root + if (!Application::isRunningAsRoot()) { + Logger::error("Bloom udev rules missing - cannot install udev rules without root privileges.\n" + "Running Bloom once with root privileges will allow it to automatically install the udev rules." + " Alternatively, instructions on manually installing the udev rules can be found " + "here: https://bloom.oscillate.io/docs/getting-started\nBloom may fail to connect to some debug tools " + "until this is resolved."); + return; + } + + if (!std::filesystem::exists(latestBloomRulesPath)) { + // This shouldn't happen, but it can if someone has been messing with the installation files + Logger::error("Unable to install Bloom udev rules - \"" + latestBloomRulesPath + "\" does not exist."); + return; + } + + std::filesystem::copy(latestBloomRulesPath, bloomRulesPath); + Logger::warning("Bloom udev rules installed - a reconnect of the debug tool may be required " + "before the new udev rules come into effect."); + } +} + +void TargetController::shutdown() { + if (this->getThreadState() == ThreadState::STOPPED) { + return; + } + + try { + Logger::info("Shutting down TargetController"); + this->eventManager.deregisterListener(this->eventListener->getId()); + this->releaseResources(); + + } catch (const std::exception& exception) { + Logger::error("Failed to properly shutdown TargetController. Error: " + std::string(exception.what())); + } + + this->setThreadStateAndEmitEvent(ThreadState::STOPPED); +} + +void TargetController::suspend() { + if (this->getThreadState() != ThreadState::READY) { + return; + } + + Logger::debug("Suspending TargetController"); + + try { + this->releaseResources(); + + } catch (const std::exception& exception) { + Logger::error("Failed to release connected debug tool and target resources. Error: " + + std::string(exception.what())); + } + + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + this->eventListener->deregisterCallbacksForEventType(); + + this->lastTargetState = TargetState::UNKNOWN; + this->cachedTargetDescriptor = std::nullopt; + + this->state = TargetControllerState::SUSPENDED; + this->eventManager.triggerEvent( + std::make_shared(this->state) + ); + + Logger::debug("TargetController suspended"); +} + +void TargetController::resume() { + this->acquireResources(); + this->eventListener->registerCallbackForEventType( std::bind(&TargetController::onDebugSessionFinishedEvent, this, std::placeholders::_1) ); @@ -170,7 +224,7 @@ void TargetController::startup() { std::bind(&TargetController::onSetProgramCounterEvent, this, std::placeholders::_1) ); - this->eventListener->registerCallbackForEventType( + this->eventListener->registerCallbackForEventType( std::bind(&TargetController::onInsightStateChangedEvent, this, std::placeholders::_1) ); @@ -181,68 +235,101 @@ void TargetController::startup() { this->eventListener->registerCallbackForEventType( std::bind(&TargetController::onSetPinStateEvent, this, std::placeholders::_1) ); -} -void TargetController::checkUdevRules() { - auto bloomRulesPath = std::string("/etc/udev/rules.d/99-bloom.rules"); - auto latestBloomRulesPath = Application::getResourcesDirPath() + "/UDevRules/99-bloom.rules"; + Logger::debug("TC resumed"); + this->state = TargetControllerState::ACTIVE; + this->eventManager.triggerEvent( + std::make_shared(this->state) + ); - if (!std::filesystem::exists(bloomRulesPath)) { - Logger::warning("Bloom udev rules missing - attempting installation"); - - // We can only install them if we're running as root - if (!Application::isRunningAsRoot()) { - Logger::error("Bloom udev rules missing - cannot install udev rules without root privileges.\n" - "Running Bloom once with root privileges will allow it to automatically install the udev rules." - " Alternatively, instructions on manually installing the udev rules can be found " - "here: https://bloom.oscillate.io/docs/getting-started\nBloom may fail to connect to some debug tools " - "until this is resolved."); - return; - } - - if (!std::filesystem::exists(latestBloomRulesPath)) { - // This shouldn't happen, but it can if someone has been messing with the installation files - Logger::error("Unable to install Bloom udev rules - \"" + latestBloomRulesPath + "\" does not exist."); - return; - } - - std::filesystem::copy(latestBloomRulesPath, bloomRulesPath); - Logger::warning("Bloom udev rules installed - a reconnect of the debug tool may be required " - "before the new udev rules come into effect."); + if (this->target->getState() != TargetState::RUNNING) { + this->target->run(); } } -void TargetController::shutdown() { - if (this->getState() == ThreadState::STOPPED) { - return; +void TargetController::acquireResources() { + auto debugToolName = this->environmentConfig.debugToolConfig.name; + auto targetName = this->environmentConfig.targetConfig.name; + + auto supportedDebugTools = TargetController::getSupportedDebugTools(); + auto supportedTargets = TargetController::getSupportedTargets(); + + if (!supportedDebugTools.contains(debugToolName)) { + throw Exceptions::InvalidConfig( + "Debug tool name (\"" + debugToolName + "\") not recognised. Please check your configuration!" + ); } - try { - Logger::info("Shutting down TargetController"); - //this->targetControllerEventListener->clearAllCallbacks(); - this->eventManager.deregisterListener(this->eventListener->getId()); - auto target = this->getTarget(); - auto debugTool = this->getDebugTool(); + if (!supportedTargets.contains(targetName)) { + throw Exceptions::InvalidConfig( + "Target name (\"" + targetName + "\") not recognised. Please check your configuration!" + ); + } - if (debugTool != nullptr && debugTool->isInitialised()) { - if (target != nullptr) { - /* - * We call deactivate() without checking if the target is activated. This will address any cases - * where a target is only partially activated (where the call to activate() failed). - */ - Logger::info("Deactivating target"); - target->deactivate(); - } + // Initiate debug tool and target + this->debugTool = supportedDebugTools.at(debugToolName)(); - Logger::info("Closing debug tool"); - debugTool->close(); + Logger::info("Connecting to debug tool"); + this->debugTool->init(); + + Logger::info("Debug tool connected"); + Logger::info("Debug tool name: " + this->debugTool->getName()); + Logger::info("Debug tool serial: " + this->debugTool->getSerialNumber()); + + this->target = supportedTargets.at(targetName)(); + + if (!this->target->isDebugToolSupported(this->debugTool.get())) { + throw Exceptions::InvalidConfig( + "Debug tool (\"" + this->debugTool->getName() + "\") not supported " + + "by target (\"" + this->target->getName() + "\")." + ); + } + + this->target->setDebugTool(this->debugTool.get()); + this->target->preActivationConfigure(this->environmentConfig.targetConfig); + + Logger::info("Activating target"); + this->target->activate(); + Logger::info("Target activated"); + this->target->postActivationConfigure(); + + while (this->target->supportsPromotion()) { + auto promotedTarget = this->target->promote(); + + if (promotedTarget == nullptr + || std::type_index(typeid(*promotedTarget)) == std::type_index(typeid(*this->target)) + ) { + break; } - } catch (const std::exception& exception) { - Logger::error("Failed to properly shutdown TargetController. Error: " + std::string(exception.what())); + this->target.reset(promotedTarget.release()); + this->target->postPromotionConfigure(); } - this->setStateAndEmitEvent(ThreadState::STOPPED); + Logger::info("Target ID: " + this->target->getHumanReadableId()); + Logger::info("Target name: " + this->target->getName()); +} + +void TargetController::releaseResources() { + auto target = this->getTarget(); + auto debugTool = this->getDebugTool(); + + if (debugTool != nullptr && debugTool->isInitialised()) { + if (target != nullptr) { + /* + * We call deactivate() without checking if the target is activated. This will address any cases + * where a target is only partially activated (where the call to activate() failed). + */ + Logger::info("Deactivating target"); + target->deactivate(); + } + + Logger::info("Closing debug tool"); + debugTool->close(); + } + + this->debugTool.reset(); + this->target.reset(); } void TargetController::fireTargetEvents() { @@ -276,6 +363,38 @@ void TargetController::onShutdownTargetControllerEvent(EventPointershutdown(); } +void TargetController::onStateReportRequest(EventPointer event) { + auto stateEvent = std::make_shared(this->state); + stateEvent->correlationId = event->id; + this->eventManager.triggerEvent(stateEvent); +} + +void TargetController::onDebugSessionStartedEvent(EventPointer) { + if (this->state == TargetControllerState::SUSPENDED) { + Logger::debug("Waking TargetController"); + + this->resume(); + this->fireTargetEvents(); + } + + this->target->reset(); + + if (this->target->getState() != TargetState::STOPPED) { + this->target->stop(); + } +} + +void TargetController::onDebugSessionFinishedEvent(EventPointer) { + if (this->target->getState() != TargetState::RUNNING) { + this->target->run(); + this->fireTargetEvents(); + } + + if (this->environmentConfig.debugToolConfig.releasePostDebugSession) { + this->suspend(); + } +} + void TargetController::onExtractTargetDescriptor(EventPointer event) { if (!this->cachedTargetDescriptor.has_value()) { this->cachedTargetDescriptor = this->target->getDescriptor(); @@ -444,20 +563,6 @@ void TargetController::onRemoveBreakpointEvent(EventPointer) { - this->target->reset(); - - if (this->target->getState() != TargetState::STOPPED) { - this->target->stop(); - } -} - -void TargetController::onDebugSessionFinishedEvent(EventPointer) { - if (this->target->getState() != TargetState::RUNNING) { - this->target->run(); - } -} - void TargetController::onSetProgramCounterEvent(EventPointer event) { try { if (this->target->getState() != TargetState::STOPPED) { diff --git a/src/TargetController/TargetController.hpp b/src/TargetController/TargetController.hpp index 55d7c3d8..1b2a84f5 100644 --- a/src/TargetController/TargetController.hpp +++ b/src/TargetController/TargetController.hpp @@ -8,14 +8,18 @@ #include #include "src/Helpers/Thread.hpp" -#include "src/Logger/Logger.hpp" -#include "src/EventManager/EventListener.hpp" +#include "TargetControllerState.hpp" + #include "src/DebugToolDrivers/DebugTools.hpp" #include "src/Targets/Target.hpp" #include "src/Targets/Targets.hpp" + #include "src/EventManager/EventManager.hpp" +#include "src/EventManager/EventListener.hpp" #include "src/EventManager/Events/Events.hpp" +#include "src/Logger/Logger.hpp" + namespace Bloom { /** @@ -30,6 +34,11 @@ namespace Bloom class TargetController: public Thread { private: + /** + * The TC starts off in a suspended state. TargetController::resume() is invoked from the startup routine. + */ + TargetControllerState state = TargetControllerState::SUSPENDED; + ApplicationConfig applicationConfig; EnvironmentConfig environmentConfig; @@ -138,14 +147,6 @@ namespace Bloom return mapping; } - void setDebugTool(std::unique_ptr debugTool) { - this->debugTool = std::move(debugTool); - } - - void setTarget(std::unique_ptr target) { - this->target = std::move(target); - } - Targets::Target* getTarget() { return this->target.get(); } @@ -186,6 +187,15 @@ namespace Bloom */ void shutdown(); + + void suspend(); + + void resume(); + + void acquireResources(); + + void releaseResources(); + /** * Should fire any events queued on the target. */ @@ -215,6 +225,13 @@ namespace Bloom */ void run(); + /** + * Reports the current state of the TargetController. + * + * @param event + */ + void onStateReportRequest(Events::EventPointer event); + /** * Obtains a TargetDescriptor from the target and includes it in a TargetDescriptorExtracted event. * @@ -322,7 +339,7 @@ namespace Bloom * * @param event */ - void onInsightStateChangedEvent(Events::EventPointer event); + void onInsightStateChangedEvent(Events::EventPointer event); /** * Will attempt to obtain the pin states from the target. Will emit a TargetPinStatesRetrieved event on success. diff --git a/src/TargetController/TargetControllerConsole.cpp b/src/TargetController/TargetControllerConsole.cpp index 17b765f0..eb79d47d 100644 --- a/src/TargetController/TargetControllerConsole.cpp +++ b/src/TargetController/TargetControllerConsole.cpp @@ -9,6 +9,33 @@ using namespace Bloom::Targets; using namespace Bloom::Events; using namespace Bloom::Exceptions; +TargetControllerState TargetControllerConsole::getTargetControllerState() { + auto getStateEvent = std::make_shared(); + this->eventManager.triggerEvent(getStateEvent); + auto responseEvent = this->eventListener.waitForEvent< + Events::TargetControllerStateReported, + Events::TargetControllerErrorOccurred + >(this->defaultTimeout, getStateEvent->id); + + if (!responseEvent.has_value() + || !std::holds_alternative>(responseEvent.value()) + ) { + throw Exception("Unexpected response from TargetController"); + } + + auto stateReportedEvent = std::get>(responseEvent.value()); + return stateReportedEvent->state; +} + +bool TargetControllerConsole::isTargetControllerInService() noexcept { + try { + return this->getTargetControllerState() == TargetControllerState::ACTIVE; + + } catch (const std::runtime_error&) { + return false; + } +} + Targets::TargetDescriptor TargetControllerConsole::getTargetDescriptor() { auto extractEvent = std::make_shared(); this->eventManager.triggerEvent(extractEvent); @@ -217,4 +244,4 @@ void TargetControllerConsole::setPinState(int variantId, TargetPinDescriptor pin updateEvent->pinState = pinState; this->eventManager.triggerEvent(updateEvent); -} \ No newline at end of file +} diff --git a/src/TargetController/TargetControllerConsole.hpp b/src/TargetController/TargetControllerConsole.hpp index 3bffd425..6fb3d05c 100644 --- a/src/TargetController/TargetControllerConsole.hpp +++ b/src/TargetController/TargetControllerConsole.hpp @@ -3,6 +3,8 @@ #include #include +#include "TargetControllerState.hpp" + #include "src/EventManager/EventListener.hpp" #include "src/EventManager/EventManager.hpp" @@ -35,6 +37,10 @@ namespace Bloom this->defaultTimeout = timeout; } + TargetControllerState getTargetControllerState(); + + bool isTargetControllerInService() noexcept; + /** * Requests the TargetDescriptor from the TargetController * diff --git a/src/TargetController/TargetControllerState.hpp b/src/TargetController/TargetControllerState.hpp new file mode 100644 index 00000000..ddccb4a5 --- /dev/null +++ b/src/TargetController/TargetControllerState.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace Bloom +{ + enum class TargetControllerState: int + { + ACTIVE, + SUSPENDED, + }; +} diff --git a/src/Targets/TargetDescriptor.hpp b/src/Targets/TargetDescriptor.hpp index d1598238..33939bb1 100644 --- a/src/Targets/TargetDescriptor.hpp +++ b/src/Targets/TargetDescriptor.hpp @@ -17,3 +17,4 @@ namespace Bloom::Targets }; } +Q_DECLARE_METATYPE(Bloom::Targets::TargetDescriptor)