Refactored CMSIS-DAP Response command classes and introduced the ExpectedResponseType alias in CMSIS-DAP commands.

This commit is contained in:
Nav
2022-02-28 16:27:24 +00:00
parent 081fba5cbd
commit 5aa233eec7
16 changed files with 137 additions and 116 deletions

View File

@@ -3,8 +3,6 @@
#include <thread> #include <thread>
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp"
#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
{ {
@@ -25,28 +23,4 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
this->getUsbHidInterface().write(static_cast<std::vector<unsigned char>>(cmsisDapCommand)); this->getUsbHidInterface().write(static_cast<std::vector<unsigned char>>(cmsisDapCommand));
} }
std::unique_ptr<Response> CmsisDapInterface::getResponse() {
auto rawResponse = this->getUsbHidInterface().read(10000);
if (rawResponse.empty()) {
throw DeviceCommunicationFailure("Empty CMSIS-DAP response received");
}
auto response = std::make_unique<Response>(Response());
response->init(rawResponse);
return response;
}
std::unique_ptr<Response> CmsisDapInterface::sendCommandAndWaitForResponse(const Command& cmsisDapCommand) {
this->sendCommand(cmsisDapCommand);
auto response = this->getResponse();
if (response->getResponseId() != cmsisDapCommand.getCommandId()) {
// This response is not what we were expecting
throw DeviceCommunicationFailure("Unexpected response to CMSIS-DAP command.");
}
return response;
}
} }

View File

@@ -2,12 +2,15 @@
#include <memory> #include <memory>
#include <chrono> #include <chrono>
#include <cstdint>
#include "src/DebugToolDrivers/USB/HID/HidInterface.hpp" #include "src/DebugToolDrivers/USB/HID/HidInterface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrCommand.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrCommand.hpp"
#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
{ {
/** /**
@@ -31,7 +34,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
return this->usbHidInterface; return this->usbHidInterface;
} }
size_t getUsbHidInputReportSize() { std::size_t getUsbHidInputReportSize() {
return this->usbHidInterface.getInputReportSize(); return this->usbHidInterface.getInputReportSize();
} }
@@ -44,7 +47,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
* *
* @param cmsisDapCommand * @param cmsisDapCommand
*/ */
virtual void sendCommand(const Protocols::CmsisDap::Command& cmsisDapCommand); virtual void sendCommand(const Command& cmsisDapCommand);
/** /**
* Listens for a CMSIS-DAP response from the device. * Listens for a CMSIS-DAP response from the device.
@@ -52,9 +55,24 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
* @TODO: There is a hard-coded timeout in this method. Review. * @TODO: There is a hard-coded timeout in this method. Review.
* *
* @return * @return
* The parsed response. * An instance to ResponseType, which must be derived from the Response class. The instance is constructed via
* its raw buffer constructor: ResponseType(const std::vector<unsigned char>&).
*/ */
virtual std::unique_ptr<Protocols::CmsisDap::Response> getResponse(); template<class ResponseType>
auto getResponse() {
static_assert(
std::is_base_of<Response, ResponseType>::value,
"CMSIS Response type must be derived from the Response class."
);
const auto rawResponse = this->getUsbHidInterface().read(10000);
if (rawResponse.empty()) {
throw Exceptions::DeviceCommunicationFailure("Empty CMSIS-DAP response received");
}
return ResponseType(rawResponse);
}
/** /**
* Sends a CMSIS-DAP command and waits for a response. * Sends a CMSIS-DAP command and waits for a response.
@@ -62,12 +80,31 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
* @param cmsisDapCommand * @param cmsisDapCommand
* *
* @return * @return
* The parsed response. * An instance of the ExpectedResponseType alias defined in CommandType.
* See the CmsisDapInterface::getResponse() template function for more.
*/ */
virtual std::unique_ptr<Protocols::CmsisDap::Response> sendCommandAndWaitForResponse( template<class CommandType>
const Protocols::CmsisDap::Command& cmsisDapCommand auto sendCommandAndWaitForResponse(const CommandType& cmsisDapCommand) {
static_assert(
std::is_base_of<Command, CommandType>::value,
"CMSIS Command type must be derived from the Command class."
); );
static_assert(
std::is_base_of<Response, typename CommandType::ExpectedResponseType>::value,
"CMSIS Command type must specify a valid expected response type, derived from the Response class."
);
this->sendCommand(cmsisDapCommand);
auto response = this->getResponse<typename CommandType::ExpectedResponseType>();
if (response.getResponseId() != cmsisDapCommand.getCommandId()) {
throw Exceptions::DeviceCommunicationFailure("Unexpected response to CMSIS-DAP command.");
}
return response;
}
private: private:
/** /**
* All CMSIS-DAP devices employ the USB HID interface for communication. * All CMSIS-DAP devices employ the USB HID interface for communication.

View File

@@ -3,11 +3,52 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include "Response.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
{ {
/**
* CMSIS-DAP command.
*
* Casting an instance of this class to an std::vector<unsigned char> will result in the raw buffer of the
* CMSIS-DAP command, which can then be sent to the device. See the conversion function below.
*/
class Command class Command
{ {
public: public:
/*
* CMSIS-DAP commands always result in a response. The data structure and contents of the response depends on
* the command. Because of this, we allow the command class (and any derived classes) to specify the expected
* response type, via the ExpectedResponseType alias.
*
* This alias is used by template functions such as CmsisDapInterface::sendCommandAndWaitForResponse(), to
* determine which type to use, when constructing and returning a response object. This means we don't have
* to perform any downcasting with our response objects, and the type of our response objects is known at
* compile time.
*
* For example, consider the AvrResponseCommand - this is a CMSIS-DAP vendor command which requests an AVR
* response from the device. Upon issuing this command, we expect the device to respond with data in the
* structure described by the AvrResponse class. So, in the AvrResponseCommand class, we set the
* ExpectedResponseType alias to AvrResponse. Then, when sending an AvrResponseCommand to the device:
*
* CmsisDapInterface cmsisInterface;
* AvrResponseCommand avrResponseCommand;
* auto response = cmsisInterface.sendCommandAndWaitForResponse(avrResponseCommand);
*
* In the code above, the response object will be an instance of the AvrResponse class, because the
* CmsisDapInterface::sendCommandAndWaitForResponse() function will use the ExpectedResponseType alias from
* the command class (AvrResponseCommand::ExpectedResponseType).
*
* Effectively, the ExpectedResponseType alias allows us to map CMSIS-DAP command classes to response classes.
* This mapping is employed by the necessary functions to provide us with response objects of the correct type,
* improving type safety and type hinting.
*
* For more on this, see the implementation of the following template functions:
* CmsisDapInterface::sendCommandAndWaitForResponse()
* CmsisDapInterface::getResponse()
*/
using ExpectedResponseType = Response;
Command() = default; Command() = default;
virtual ~Command() = default; virtual ~Command() = default;

View File

@@ -4,7 +4,7 @@
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
{ {
void Response::init(const std::vector<unsigned char>& rawResponse) { Response::Response(const std::vector<unsigned char>& rawResponse) {
if (rawResponse.empty()) { if (rawResponse.empty()) {
throw Exceptions::Exception("Failed to process CMSIS-DAP response - invalid response"); throw Exceptions::Exception("Failed to process CMSIS-DAP response - invalid response");
} }

View File

@@ -7,7 +7,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
class Response class Response
{ {
public: public:
Response() = default; Response(const std::vector<unsigned char>& rawResponse);
virtual ~Response() = default; virtual ~Response() = default;
Response(const Response& other) = default; Response(const Response& other) = default;
@@ -16,8 +16,6 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
Response& operator = (const Response& other) = default; Response& operator = (const Response& other) = default;
Response& operator = (Response&& other) = default; Response& operator = (Response&& other) = default;
virtual void init(const std::vector<unsigned char>& rawResponse);
[[nodiscard]] unsigned char getResponseId() const { [[nodiscard]] unsigned char getResponseId() const {
return this->responseId; return this->responseId;
} }

View File

@@ -5,11 +5,29 @@
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
#include "AvrResponse.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{ {
/**
* AVR CMSIS-DAP vendor command.
*/
class AvrCommand: public Command class AvrCommand: public Command
{ {
public: public:
/*
* AVR CMSIS-DAP vendor commands *do not* directly result in an AvrResponse object. The device will respond
* immediately upon receiving this command, simply acknowledging receipt of the command.
*
* If a response is expected to follow upon the execution of the AVR command, it must be requested from the
* device, using the AvrResponseCommand (see that class declaration for more info).
*
* For this reason, the ExpectedResponseType for this command is just the standard Response type.
*
* For more on the purpose of this alias, see the Command class.
*/
using ExpectedResponseType = Response;
AvrCommand( AvrCommand(
std::size_t fragmentCount, std::size_t fragmentCount,
std::size_t fragmentNumber, std::size_t fragmentNumber,

View File

@@ -6,9 +6,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{ {
using namespace Bloom::Exceptions; using namespace Bloom::Exceptions;
void AvrEvent::init(const std::vector<unsigned char>& rawResponse) { AvrEvent::AvrEvent(const std::vector<unsigned char>& rawResponse): Response(rawResponse) {
Response::init(rawResponse);
if (this->getResponseId() != 0x82) { if (this->getResponseId() != 0x82) {
throw Exception("Failed to construct AvrEvent object - invalid response ID."); throw Exception("Failed to construct AvrEvent object - invalid response ID.");
} }

View File

@@ -23,20 +23,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
class AvrEvent: public Response class AvrEvent: public Response
{ {
public: public:
AvrEvent() = default; explicit AvrEvent(const std::vector<unsigned char>& rawResponse);
/**
* Construct an AVRResponse object from a Response object.
*
* @param response
*/
void init(const Response& response) {
auto rawData = response.getData();
rawData.insert(rawData.begin(), response.getResponseId());
this->init(rawData);
}
void init(const std::vector<unsigned char>& rawResponse) override;
[[nodiscard]] const std::vector<unsigned char>& getEventData() const { [[nodiscard]] const std::vector<unsigned char>& getEventData() const {
return this->eventData; return this->eventData;

View File

@@ -1,14 +1,15 @@
#pragma once #pragma once
#include <vector>
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
#include "AvrEvent.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{ {
class AvrEventCommand: public Command class AvrEventCommand: public Command
{ {
public: public:
using ExpectedResponseType = AvrEvent;
AvrEventCommand() { AvrEventCommand() {
this->setCommandId(0x82); this->setCommandId(0x82);
} }

View File

@@ -6,9 +6,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{ {
using namespace Bloom::Exceptions; using namespace Bloom::Exceptions;
void AvrResponse::init(const std::vector<unsigned char>& rawResponse) { AvrResponse::AvrResponse(const std::vector<unsigned char>& rawResponse): Response(rawResponse) {
Response::init(rawResponse);
if (this->getResponseId() != 0x81) { if (this->getResponseId() != 0x81) {
throw Exception("Failed to construct AvrResponse object - invalid response ID."); throw Exception("Failed to construct AvrResponse object - invalid response ID.");
} }

View File

@@ -10,20 +10,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
class AvrResponse: public Response class AvrResponse: public Response
{ {
public: public:
AvrResponse() = default; explicit AvrResponse(const std::vector<unsigned char>& rawResponse);
/**
* Construct an AVRResponse object from a Response object.
*
* @param response
*/
void init(const Response& response) {
auto rawData = response.getData();
rawData.insert(rawData.begin(), response.getResponseId());
this->init(rawData);
}
void init(const std::vector<unsigned char>& rawResponse) override;
[[nodiscard]] std::uint8_t getFragmentNumber() const { [[nodiscard]] std::uint8_t getFragmentNumber() const {
return this->fragmentNumber; return this->fragmentNumber;

View File

@@ -4,6 +4,8 @@
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
#include "AvrResponse.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{ {
/** /**
@@ -12,8 +14,9 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
* *
* Responses to AVR commands are not automatically sent from the device, they must be requested from the device. * Responses to AVR commands are not automatically sent from the device, they must be requested from the device.
* *
* An AvrResponseCommand is a CMSIS-DAP command, used to request a response for an AVR command. In response to an * An AvrResponseCommand is a CMSIS-DAP vendor command, used to request a response for an AVR command. In response
* AvrResponseCommand, the device will send over an AVRResponse, which will contain the response to the AVR command. * to an AvrResponseCommand, the device will send over an AvrResponse, which will contain the response to the
* AVR command.
* *
* For more information on this, see the 'Embedded Debugger-Based Tools Protocols User's Guide' * For more information on this, see the 'Embedded Debugger-Based Tools Protocols User's Guide'
* document, by Microchip. Link provided in the AtmelICE device class declaration. * document, by Microchip. Link provided in the AtmelICE device class declaration.
@@ -23,6 +26,8 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
class AvrResponseCommand: public Command class AvrResponseCommand: public Command
{ {
public: public:
using ExpectedResponseType = AvrResponse;
AvrResponseCommand() { AvrResponseCommand() {
this->setCommandId(0x81); this->setCommandId(0x81);
} }

View File

@@ -8,8 +8,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
using Bloom::Targets::TargetBreakCause; using Bloom::Targets::TargetBreakCause;
void BreakEvent::init(const AvrEvent& event) { BreakEvent::BreakEvent(const AvrEvent& event): AvrEvent(event) {
AvrEvent::init(event);
const auto& data = this->getEventData(); const auto& data = this->getEventData();
if (data.size() < 8) { if (data.size() < 8) {

View File

@@ -10,9 +10,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
class BreakEvent: public AvrEvent class BreakEvent: public AvrEvent
{ {
public: public:
explicit BreakEvent(const AvrEvent& event) { explicit BreakEvent(const AvrEvent& event);
this->init(event);
}
[[nodiscard]] std::uint32_t getProgramCounter() const { [[nodiscard]] std::uint32_t getProgramCounter() const {
return this->programCounter; return this->programCounter;

View File

@@ -16,7 +16,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg
auto response = this->sendCommandAndWaitForResponse(avrCommand); auto response = this->sendCommandAndWaitForResponse(avrCommand);
if (&avrCommand == &avrCommands.back()) { if (&avrCommand == &avrCommands.back()) {
return *response; return response;
} }
} }
@@ -26,31 +26,14 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg
); );
} }
Protocols::CmsisDap::Edbg::Avr::AvrResponse EdbgInterface::getAvrResponse() {
auto cmsisResponse = this->getResponse();
if (cmsisResponse->getResponseId() == 0x81) {
// This is an AVR_RSP response
auto avrResponse = Protocols::CmsisDap::Edbg::Avr::AvrResponse();
avrResponse.init(*cmsisResponse);
return avrResponse;
}
throw DeviceCommunicationFailure("Unexpected response to AvrResponseCommand from device");
}
std::optional<Protocols::CmsisDap::Edbg::Avr::AvrEvent> EdbgInterface::requestAvrEvent() { std::optional<Protocols::CmsisDap::Edbg::Avr::AvrEvent> EdbgInterface::requestAvrEvent() {
this->sendCommand(AvrEventCommand()); auto avrEventResponse = this->sendCommandAndWaitForResponse(Avr::AvrEventCommand());
auto cmsisResponse = this->getResponse();
if (cmsisResponse->getResponseId() == 0x82) { if (avrEventResponse.getResponseId() != 0x82) {
// This is an AVR_EVT response throw DeviceCommunicationFailure("Unexpected response to AvrEventCommand from device");
auto avrEvent = Protocols::CmsisDap::Edbg::Avr::AvrEvent();
avrEvent.init(*cmsisResponse);
return avrEvent.getEventDataSize() > 0 ? std::optional(avrEvent) : std::nullopt;
} }
throw DeviceCommunicationFailure("Unexpected response to AvrEventCommand from device"); return avrEventResponse.getEventDataSize() > 0 ? std::optional(avrEventResponse) : std::nullopt;
} }
std::vector<Protocols::CmsisDap::Edbg::Avr::AvrResponse> EdbgInterface::requestAvrResponses() { std::vector<Protocols::CmsisDap::Edbg::Avr::AvrResponse> EdbgInterface::requestAvrResponses() {
@@ -59,34 +42,32 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg
std::vector<Protocols::CmsisDap::Edbg::Avr::AvrResponse> responses; std::vector<Protocols::CmsisDap::Edbg::Avr::AvrResponse> responses;
AvrResponseCommand responseCommand; AvrResponseCommand responseCommand;
this->sendCommand(responseCommand); auto avrResponse = this->sendCommandAndWaitForResponse(responseCommand);
auto response = this->getAvrResponse(); responses.push_back(avrResponse);
responses.push_back(response); const auto fragmentCount = avrResponse.getFragmentCount();
int fragmentCount = response.getFragmentCount();
while (responses.size() < fragmentCount) { while (responses.size() < fragmentCount) {
// There are more response packets // There are more response packets
this->sendCommand(responseCommand); auto avrResponse = this->sendCommandAndWaitForResponse(responseCommand);
response = this->getAvrResponse();
if (response.getFragmentCount() != fragmentCount) { if (avrResponse.getFragmentCount() != fragmentCount) {
throw DeviceCommunicationFailure( throw DeviceCommunicationFailure(
"Failed to fetch AVRResponse objects - invalid fragment count returned." "Failed to fetch AVRResponse objects - invalid fragment count returned."
); );
} }
if (response.getFragmentCount() == 0 && response.getFragmentNumber() == 0) { if (avrResponse.getFragmentCount() == 0 && avrResponse.getFragmentNumber() == 0) {
throw DeviceCommunicationFailure( throw DeviceCommunicationFailure(
"Failed to fetch AVRResponse objects - unexpected empty response" "Failed to fetch AVRResponse objects - unexpected empty response"
); );
} }
if (response.getFragmentNumber() == 0) { if (avrResponse.getFragmentNumber() == 0) {
// End of response data ( &this packet can be ignored) // End of response data ( &this packet can be ignored)
break; break;
} }
responses.push_back(response); responses.push_back(avrResponse);
} }
return responses; return responses;

View File

@@ -4,6 +4,7 @@
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/CmsisDapInterface.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/CmsisDapInterface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrCommand.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrCommand.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrResponse.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrEventCommand.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrEventCommand.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrEvent.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrEvent.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AvrCommandFrame.hpp" #include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AvrCommandFrame.hpp"
@@ -48,8 +49,6 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg
const std::vector<Avr::AvrCommand>& avrCommands const std::vector<Avr::AvrCommand>& avrCommands
); );
Protocols::CmsisDap::Edbg::Avr::AvrResponse getAvrResponse();
template<class CommandFrameType> template<class CommandFrameType>
typename CommandFrameType::ResponseFrameType sendAvrCommandFrameAndWaitForResponseFrame( typename CommandFrameType::ResponseFrameType sendAvrCommandFrameAndWaitForResponseFrame(
const CommandFrameType& avrCommandFrame const CommandFrameType& avrCommandFrame