Initial commit
This commit is contained in:
54
src/DebugToolDrivers/DebugTool.hpp
Normal file
54
src/DebugToolDrivers/DebugTool.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "TargetInterfaces/Microchip/AVR/AVR8/Avr8Interface.hpp"
|
||||
|
||||
namespace Bloom
|
||||
{
|
||||
using DebugToolDrivers::TargetInterfaces::Microchip::Avr::Avr8::Avr8Interface;
|
||||
|
||||
/**
|
||||
* A debug tool can be any device that provides access to the connected target. Debug tools are usually connected
|
||||
* to the host machine via USB.
|
||||
*
|
||||
* Each debug tool must implement this interface. Note that target specific driver code should not be placed here.
|
||||
* Each target family will expect the debug tool to provide an interface for that particular group of targets.
|
||||
* For an example, see the Avr8Interface class and the DebugTool::getAvr8Interface().
|
||||
*/
|
||||
class DebugTool
|
||||
{
|
||||
private:
|
||||
bool initialised = false;
|
||||
|
||||
protected:
|
||||
void setInitialised(bool initialised) {
|
||||
this->initialised = initialised;
|
||||
}
|
||||
|
||||
public:
|
||||
bool isInitialised() const {
|
||||
return this->initialised;
|
||||
}
|
||||
|
||||
virtual void init() = 0;
|
||||
|
||||
virtual void close() = 0;
|
||||
|
||||
virtual std::string getName() = 0;
|
||||
|
||||
virtual std::string getSerialNumber() = 0;
|
||||
|
||||
/**
|
||||
* All debug tools that support AVR8 targets must provide an implementation of the Avr8Interface
|
||||
* class, via this method.
|
||||
*
|
||||
* For debug tools that do not support AVR8 targets, this method should return a nullptr.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual Avr8Interface* getAvr8Interface() {
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
virtual ~DebugTool() = default;
|
||||
};
|
||||
}
|
||||
5
src/DebugToolDrivers/DebugTools.hpp
Normal file
5
src/DebugToolDrivers/DebugTools.hpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "DebugTool.hpp"
|
||||
#include "src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp"
|
||||
#include "src/DebugToolDrivers/Microchip/PowerDebugger/PowerDebugger.hpp"
|
||||
87
src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.cpp
Normal file
87
src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include "AtmelIce.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers;
|
||||
using namespace Protocols::CmsisDap::Edbg::Avr;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void AtmelIce::init() {
|
||||
UsbDevice::init();
|
||||
|
||||
// TODO: Move away from hard-coding the CMSIS-DAP/EDBG interface number
|
||||
auto& usbHidInterface = this->getEdbgInterface().getUsbHidInterface();
|
||||
usbHidInterface.setNumber(0);
|
||||
usbHidInterface.setUSBDevice(this->getLibUsbDevice());
|
||||
usbHidInterface.setVendorId(this->getVendorId());
|
||||
usbHidInterface.setProductId(this->getProductId());
|
||||
|
||||
if (!usbHidInterface.isInitialised()) {
|
||||
usbHidInterface.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Atmel-ICE EDBG/CMSIS-DAP interface doesn't operate properly when sending commands too quickly.
|
||||
*
|
||||
* Because of this, we have to enforce a minimum time gap between commands. See comment
|
||||
* in CmsisDapInterface class declaration for more info.
|
||||
*/
|
||||
this->getEdbgInterface().setMinimumCommandTimeGap(35);
|
||||
|
||||
// We don't need to claim the CMSISDAP interface here as the HIDAPI will have already done so.
|
||||
if (!this->sessionStarted) {
|
||||
this->startSession();
|
||||
}
|
||||
|
||||
this->edbgAvr8Interface = std::make_unique<EdbgAvr8Interface>(this->edbgInterface);
|
||||
this->setInitialised(true);
|
||||
}
|
||||
|
||||
void AtmelIce::close() {
|
||||
if (this->sessionStarted) {
|
||||
this->endSession();
|
||||
}
|
||||
|
||||
this->getEdbgInterface().getUsbHidInterface().close();
|
||||
UsbDevice::close();
|
||||
}
|
||||
|
||||
std::string AtmelIce::getSerialNumber() {
|
||||
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
|
||||
CommandFrames::Discovery::Query(CommandFrames::Discovery::QueryContext::SERIAL_NUMBER)
|
||||
);
|
||||
|
||||
if (response.getResponseId() != CommandFrames::Discovery::ResponseId::OK) {
|
||||
throw Exception("Failed to fetch serial number from device - invalid Discovery Protocol response ID.");
|
||||
}
|
||||
|
||||
auto data = response.getPayloadData();
|
||||
return std::string(data.begin(), data.end());
|
||||
}
|
||||
|
||||
void AtmelIce::startSession() {
|
||||
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
|
||||
CommandFrames::HouseKeeping::StartSession()
|
||||
);
|
||||
|
||||
if (response.getResponseId() == CommandFrames::HouseKeeping::ResponseId::FAILED) {
|
||||
// Failed response returned!
|
||||
throw Exception("Failed to start session with Atmel-ICE!");
|
||||
}
|
||||
|
||||
this->sessionStarted = true;
|
||||
}
|
||||
|
||||
void AtmelIce::endSession() {
|
||||
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
|
||||
CommandFrames::HouseKeeping::EndSession()
|
||||
);
|
||||
|
||||
if (response.getResponseId() == CommandFrames::HouseKeeping::ResponseId::FAILED) {
|
||||
// Failed response returned!
|
||||
throw Exception("Failed to end session with Atmel-ICE!");
|
||||
}
|
||||
|
||||
this->sessionStarted = false;
|
||||
}
|
||||
104
src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp
Normal file
104
src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugToolDrivers/DebugTool.hpp"
|
||||
#include "src/DebugToolDrivers/USB/UsbDevice.hpp"
|
||||
#include "src/DebugToolDrivers/USB/HID/HidInterface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/CmsisDapInterface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/EdbgInterface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AvrCommandFrames.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers
|
||||
{
|
||||
using namespace Protocols::CmsisDap;
|
||||
using Protocols::CmsisDap::Edbg::EdbgInterface;
|
||||
using Protocols::CmsisDap::Edbg::Avr::EdbgAvr8Interface;
|
||||
|
||||
/**
|
||||
* The Atmel-ICE device is an EDBG (Embedded Debugger) device. It implements the CMSIS-DAP layer as well
|
||||
* as an Atmel Data Gateway Interface (DGI).
|
||||
*
|
||||
* Communication:
|
||||
* Using the Atmel-ICE device for AVR debugging/programming requires the AVR communication protocol, which
|
||||
* is a sub-protocol of the CMSIS-DAP (AVR communication protocol commands are wrapped in CMSIS-DAP vendor
|
||||
* commands). AVR communication protocol commands contain AVR frames, in which commands for numerous
|
||||
* sub-protocols are enveloped.
|
||||
*
|
||||
* So to summarise, issuing an AVR command to the EDBG device, involves:
|
||||
* Actual command data -> AVR sub-protocol -> AVR Frames -> CMSIS-DAP Vendor commands -> EDBG Device
|
||||
*
|
||||
* For more information on protocols and sub-protocols employed by Microchip EDBG devices, see
|
||||
* the 'Embedded Debugger-Based Tools Protocols User's Guide' document by Microchip.
|
||||
* @link http://ww1.microchip.com/downloads/en/DeviceDoc/50002630A.pdf
|
||||
*
|
||||
* USB Setup:
|
||||
* Vendor ID: 0x03eb (1003)
|
||||
* Product ID: 0x2141 (8513)
|
||||
*
|
||||
* The Atmel-ICE consists of two USB interfaces. One HID interface for CMSIS-DAP and one vendor specific
|
||||
* interface for the DGI. We only work with the CMSIS-DAP interface, for now.
|
||||
*/
|
||||
class AtmelIce: public DebugTool, public Usb::UsbDevice
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The EDBG interface implements additional functionality via vendor specific CMSIS-DAP commands.
|
||||
* In other words, all EDBG commands are just CMSIS-DAP vendor commands that allow the debug tool
|
||||
* to support additional functionality, like AVR programming and debugging.
|
||||
*
|
||||
* Any non-EDBG CMSIS-DAP commands for the Atmel-ICE can be sent through the EdbgInterface (as the
|
||||
* EdbgInterface extends the CmsisDapInterface).
|
||||
*/
|
||||
EdbgInterface edbgInterface = EdbgInterface();
|
||||
|
||||
/**
|
||||
* The Atmel-ICE employs the EDBG AVR8 Generic protocol, for debugging AVR8 targets. This protocol is
|
||||
* implemented in EdbgAvr8Interface. See the EdbgAvr8Interface class for more information.
|
||||
*/
|
||||
std::unique_ptr<EdbgAvr8Interface> edbgAvr8Interface = nullptr;
|
||||
|
||||
bool sessionStarted = false;
|
||||
|
||||
public:
|
||||
static const std::uint16_t USB_VENDOR_ID = 1003;
|
||||
static const std::uint16_t USB_PRODUCT_ID = 8513;
|
||||
|
||||
AtmelIce() : UsbDevice(AtmelIce::USB_VENDOR_ID, AtmelIce::USB_PRODUCT_ID) {}
|
||||
|
||||
void init() override;
|
||||
void close() override;
|
||||
|
||||
EdbgInterface& getEdbgInterface() {
|
||||
return this->edbgInterface;
|
||||
}
|
||||
|
||||
Avr8Interface* getAvr8Interface() override {
|
||||
return this->edbgAvr8Interface.get();
|
||||
}
|
||||
|
||||
std::string getName() override {
|
||||
return "Atmel-ICE";
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the device serial number via the Discovery Protocol.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::string getSerialNumber() override;
|
||||
|
||||
/**
|
||||
* Starts a session with the EDBG-based tool using the housekeeping protocol.
|
||||
*/
|
||||
void startSession();
|
||||
|
||||
/**
|
||||
* Ends the active session with the debug tool.
|
||||
*/
|
||||
void endSession();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include "PowerDebugger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers;
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void PowerDebugger::init() {
|
||||
UsbDevice::init();
|
||||
|
||||
// TODO: Move away from hard-coding the CMSIS-DAP/EDBG interface number
|
||||
auto& usbHidInterface = this->getEdbgInterface().getUsbHidInterface();
|
||||
usbHidInterface.setNumber(0);
|
||||
usbHidInterface.setUSBDevice(this->getLibUsbDevice());
|
||||
usbHidInterface.setVendorId(this->getVendorId());
|
||||
usbHidInterface.setProductId(this->getProductId());
|
||||
|
||||
if (!usbHidInterface.isInitialised()) {
|
||||
usbHidInterface.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Power Debugger EDBG/CMSIS-DAP interface doesn't operate properly when sending commands too quickly.
|
||||
*
|
||||
* Because of this, we have to enforce a minimum time gap between commands. See comment in
|
||||
* CmsisDapInterface class declaration for more info.
|
||||
*/
|
||||
this->getEdbgInterface().setMinimumCommandTimeGap(35);
|
||||
|
||||
// We don't need to claim the CMSISDAP interface here as the HIDAPI will have already done so.
|
||||
if (!this->sessionStarted) {
|
||||
this->startSession();
|
||||
}
|
||||
|
||||
this->edbgAvr8Interface = std::make_unique<EdbgAvr8Interface>(this->edbgInterface);
|
||||
this->setInitialised(true);
|
||||
}
|
||||
|
||||
void PowerDebugger::close() {
|
||||
if (this->sessionStarted) {
|
||||
this->endSession();
|
||||
}
|
||||
|
||||
this->getEdbgInterface().getUsbHidInterface().close();
|
||||
UsbDevice::close();
|
||||
}
|
||||
|
||||
std::string PowerDebugger::getSerialNumber() {
|
||||
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
|
||||
CommandFrames::Discovery::Query(Discovery::QueryContext::SERIAL_NUMBER)
|
||||
);
|
||||
|
||||
if (response.getResponseId() != Discovery::ResponseId::OK) {
|
||||
throw Exception("Failed to fetch serial number from device - invalid Discovery Protocol response ID.");
|
||||
}
|
||||
|
||||
auto data = response.getPayloadData();
|
||||
return std::string(data.begin(), data.end());
|
||||
}
|
||||
|
||||
void PowerDebugger::startSession() {
|
||||
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
|
||||
CommandFrames::HouseKeeping::StartSession()
|
||||
);
|
||||
|
||||
if (response.getResponseId() == HouseKeeping::ResponseId::FAILED) {
|
||||
// Failed response returned!
|
||||
throw Exception("Failed to start session with the Power Debugger - device returned failed response ID");
|
||||
}
|
||||
|
||||
this->sessionStarted = true;
|
||||
}
|
||||
|
||||
void PowerDebugger::endSession() {
|
||||
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
|
||||
CommandFrames::HouseKeeping::EndSession()
|
||||
);
|
||||
|
||||
if (response.getResponseId() == HouseKeeping::ResponseId::FAILED) {
|
||||
// Failed response returned!
|
||||
throw Exception("Failed to end session with the Power Debugger - device returned failed response ID");
|
||||
}
|
||||
|
||||
this->sessionStarted = false;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugToolDrivers/DebugTool.hpp"
|
||||
#include "src/DebugToolDrivers/USB/UsbDevice.hpp"
|
||||
#include "src/DebugToolDrivers/USB/HID/HidInterface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/CmsisDapInterface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/EdbgInterface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AvrCommandFrames.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers
|
||||
{
|
||||
using namespace Protocols::CmsisDap;
|
||||
using namespace Protocols::CmsisDap::Edbg::Avr::CommandFrames;
|
||||
using namespace Protocols::CmsisDap::Edbg::Avr::CommandFrames::Discovery;
|
||||
using namespace Protocols::CmsisDap::Edbg::Avr::CommandFrames::HouseKeeping;
|
||||
using Protocols::CmsisDap::Edbg::EdbgInterface;
|
||||
using Protocols::CmsisDap::Edbg::Avr::EdbgAvr8Interface;
|
||||
|
||||
/**
|
||||
* The Power Debugger device is very similar to the Atmel-ICE. It implements the CMSIS-DAP layer as well
|
||||
* as an Atmel Data Gateway Interface (DGI).
|
||||
*
|
||||
* Communication:
|
||||
* Like the Atmel-ICE, using the Power Debugger device for AVR debugging/programming requires the AVR
|
||||
* communication protocol, which is a sub-protocol of the CMSIS-DAP.
|
||||
*
|
||||
* For more information on the communication protocol, see the DebugToolDrivers::AtmelIce class.
|
||||
*
|
||||
* USB Setup:
|
||||
* Vendor ID: 0x03eb (1003)
|
||||
* Product ID: 0x2141 (8513)
|
||||
*/
|
||||
class PowerDebugger: public DebugTool, public Usb::UsbDevice
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The EDBG interface implements additional functionality via vendor specific CMSIS-DAP commands.
|
||||
* In other words, all EDBG commands are just CMSIS-DAP vendor commands that allow the debug tool
|
||||
* to support additional functionality, like AVR programming and debugging.
|
||||
*
|
||||
* Any non-EDBG CMSIS-DAP commands for the Power Debugger can be sent through the EDBGInterface (as the
|
||||
* EdbgInterface extends the CmsisDapInterface).
|
||||
*/
|
||||
EdbgInterface edbgInterface = EdbgInterface();
|
||||
|
||||
/**
|
||||
* The Power Debugger employs the EDBG AVR8Generic protocol for interfacing with AVR8 targets.
|
||||
*/
|
||||
std::unique_ptr<EdbgAvr8Interface> edbgAvr8Interface;
|
||||
|
||||
bool sessionStarted = false;
|
||||
|
||||
public:
|
||||
static const std::uint16_t USB_VENDOR_ID = 1003;
|
||||
static const std::uint16_t USB_PRODUCT_ID = 8516;
|
||||
|
||||
PowerDebugger() : UsbDevice(PowerDebugger::USB_VENDOR_ID, PowerDebugger::USB_PRODUCT_ID) {}
|
||||
|
||||
void init() override;
|
||||
void close() override;
|
||||
|
||||
EdbgInterface& getEdbgInterface() {
|
||||
return this->edbgInterface;
|
||||
}
|
||||
|
||||
Avr8Interface* getAvr8Interface() override {
|
||||
return this->edbgAvr8Interface.get();
|
||||
}
|
||||
|
||||
std::string getName() override {
|
||||
return "Power Debugger";
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the device serial number via the Discovery Protocol.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::string getSerialNumber() override;
|
||||
|
||||
/**
|
||||
* Starts a session with the EDBG-based tool using the housekeeping protocol.
|
||||
*/
|
||||
void startSession();
|
||||
|
||||
/**
|
||||
* Ends the active session with the debug tool.
|
||||
*/
|
||||
void endSession();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "CmsisDapInterface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void CmsisDapInterface::sendCommand(const Command& cmsisDapCommand) {
|
||||
if (this->msSendCommandDelay > 0) {
|
||||
using namespace std::chrono;
|
||||
long now = duration_cast<milliseconds>(high_resolution_clock::now().time_since_epoch()).count();
|
||||
long difference = (now - this->lastCommandSentTimeStamp);
|
||||
|
||||
if (difference < this->msSendCommandDelay) {
|
||||
std::this_thread::sleep_for(milliseconds(this->msSendCommandDelay - difference));
|
||||
}
|
||||
|
||||
this->lastCommandSentTimeStamp = now;
|
||||
}
|
||||
|
||||
this->getUsbHidInterface().write(static_cast<std::vector<unsigned char>>(cmsisDapCommand));
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> CmsisDapInterface::getResponse() {
|
||||
auto rawResponse = this->getUsbHidInterface().read(5000);
|
||||
|
||||
if (rawResponse.size() == 0) {
|
||||
throw Exception("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 Exception("Unexpected response to CMSIS-DAP command.");
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
#include "src/DebugToolDrivers/USB/HID/HidInterface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrCommand.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers;
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
|
||||
{
|
||||
/**
|
||||
* The CmsisDapInterface class implements the CMSIS-DAP protocol.
|
||||
*
|
||||
* See https://www.keil.com/support/man/docs/dapdebug/dapdebug_introduction.htm for more on the CMSIS-DAP protocol.
|
||||
*/
|
||||
class CmsisDapInterface
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* All CMSIS-DAP devices employ the USB HID interface for communication.
|
||||
*
|
||||
* For many CMSIS-DAP devices, the USB HID interface parameters (interface number, endpoint config, etc) vary
|
||||
* amongst devices, so we'll need to be able to preActivationConfigure the CMSISDAPInterface from a
|
||||
* higher level. For an example, see the constructor of the AtmelIce device class.
|
||||
*/
|
||||
Usb::HidInterface usbHidInterface = Usb::HidInterface();
|
||||
|
||||
/**
|
||||
* Some CMSIS-DAP debug tools fail to operate properly when we send commands too quickly. Even if we've
|
||||
* received a response from every previous command.
|
||||
*
|
||||
* Because of this, we may need to enforce a minimum time gap between sending CMSIS commands.
|
||||
* Setting msSendCommandDelay to any value above 0 will enforce an x millisecond gap between each command
|
||||
* being sent, where x is the value of msSendCommandDelay.
|
||||
*/
|
||||
int msSendCommandDelay = 0;
|
||||
long lastCommandSentTimeStamp;
|
||||
|
||||
public:
|
||||
explicit CmsisDapInterface() = default;
|
||||
|
||||
Usb::HidInterface& getUsbHidInterface() {
|
||||
return this->usbHidInterface;
|
||||
}
|
||||
|
||||
void setUsbHidInterface(Usb::HidInterface& usbHidInterface) {
|
||||
this->usbHidInterface = usbHidInterface;
|
||||
}
|
||||
|
||||
size_t getUsbHidInputReportSize() {
|
||||
return this->usbHidInterface.getInputReportSize();
|
||||
}
|
||||
|
||||
void setMinimumCommandTimeGap(int millisecondTimeGap) {
|
||||
this->msSendCommandDelay = millisecondTimeGap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a CMSIS-DAP command to the device.
|
||||
*
|
||||
* @param cmsisDapCommand
|
||||
*/
|
||||
virtual void sendCommand(const Protocols::CmsisDap::Command& cmsisDapCommand);
|
||||
|
||||
/**
|
||||
* Listens for a CMSIS-DAP response from the device.
|
||||
*
|
||||
* @TODO: There is a hard-coded timeout in this method. Review.
|
||||
*
|
||||
* @return
|
||||
* The parsed response.
|
||||
*/
|
||||
virtual std::unique_ptr<Protocols::CmsisDap::Response> getResponse();
|
||||
|
||||
/**
|
||||
* Sends a CMSIS-DAP command and waits for a response.
|
||||
*
|
||||
* @param cmsisDapCommand
|
||||
*
|
||||
* @return
|
||||
* The parsed response.
|
||||
*/
|
||||
virtual std::unique_ptr<Protocols::CmsisDap::Response> sendCommandAndWaitForResponse(
|
||||
const Protocols::CmsisDap::Command& cmsisDapCommand
|
||||
);
|
||||
};
|
||||
}
|
||||
12
src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.cpp
Normal file
12
src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "Command.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap;
|
||||
|
||||
Command::operator std::vector<unsigned char> () const
|
||||
{
|
||||
auto rawCommand = std::vector<unsigned char>(1, this->getCommandId());
|
||||
auto commandData = this->getData();
|
||||
rawCommand.insert(rawCommand.end(), commandData.begin(), commandData.end());
|
||||
|
||||
return rawCommand;
|
||||
}
|
||||
52
src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp
Normal file
52
src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
|
||||
{
|
||||
class Command
|
||||
{
|
||||
private:
|
||||
unsigned char commandId = 0x00;
|
||||
std::vector<unsigned char> data;
|
||||
|
||||
public:
|
||||
unsigned char getCommandId() const {
|
||||
return this->commandId;
|
||||
}
|
||||
|
||||
void setCommandId(unsigned char commandId) {
|
||||
this->commandId = commandId;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getData() const {
|
||||
return this->data;
|
||||
}
|
||||
|
||||
void setData(const std::vector<unsigned char>& data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
[[nodiscard]] int getCommandSize() const {
|
||||
// +1 for the command ID
|
||||
return (int) (1 + this->getData().size());
|
||||
}
|
||||
|
||||
std::uint16_t getDataSize() const {
|
||||
return (std::uint16_t) this->getData().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts instance of a CMSIS Command to a vector of unsigned char (buffer), for sending
|
||||
* to the debug tool.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
explicit virtual operator std::vector<unsigned char>() const;
|
||||
|
||||
virtual ~Command() = default;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
24
src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.cpp
Normal file
24
src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "Response.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap;
|
||||
|
||||
void Response::init(unsigned char* response, std::size_t length)
|
||||
{
|
||||
if (length == 0) {
|
||||
throw Exceptions::Exception("Failed to process CMSIS-DAP response - invalid response");
|
||||
}
|
||||
|
||||
this->setResponseId(response[0]);
|
||||
std::vector<unsigned char> data = this->getData();
|
||||
|
||||
// TODO: use insert with iterators here
|
||||
for (std::size_t i = 1; i < length; i++) {
|
||||
data.push_back(response[i]);
|
||||
}
|
||||
|
||||
this->setData(data);
|
||||
}
|
||||
|
||||
41
src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp
Normal file
41
src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
|
||||
{
|
||||
class Response
|
||||
{
|
||||
private:
|
||||
unsigned char responseId = 0x00;
|
||||
|
||||
std::vector<unsigned char> data;
|
||||
|
||||
protected:
|
||||
void setResponseId(unsigned char commandId) {
|
||||
this->responseId = commandId;
|
||||
}
|
||||
|
||||
void setData(const std::vector<unsigned char>& data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
public:
|
||||
Response() = default;
|
||||
virtual void init(unsigned char* response, std::size_t length);
|
||||
virtual void init(std::vector<unsigned char> response) {
|
||||
this->init(response.data(), response.size());
|
||||
}
|
||||
|
||||
unsigned char getResponseId() const {
|
||||
return this->responseId;
|
||||
}
|
||||
|
||||
virtual const std::vector<unsigned char>& getData() const {
|
||||
return this->data;
|
||||
}
|
||||
|
||||
virtual ~Response() = default;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
|
||||
{
|
||||
struct Avr8EdbgParameter
|
||||
{
|
||||
unsigned char context = 0x00;
|
||||
unsigned char id = 0x00;
|
||||
|
||||
constexpr Avr8EdbgParameter() = default;
|
||||
constexpr Avr8EdbgParameter(unsigned char context, unsigned char id)
|
||||
: context(context), id(id) {};
|
||||
};
|
||||
|
||||
struct Avr8EdbgParameters
|
||||
{
|
||||
constexpr static Avr8EdbgParameter CONFIG_VARIANT {0x00, 0x00};
|
||||
constexpr static Avr8EdbgParameter CONFIG_FUNCTION {0x00, 0x01};
|
||||
constexpr static Avr8EdbgParameter PHYSICAL_INTERFACE {0x01, 0x00};
|
||||
constexpr static Avr8EdbgParameter DW_CLOCK_DIVISION_FACTOR {0x01, 0x10};
|
||||
constexpr static Avr8EdbgParameter XMEGA_PDI_CLOCK {0x01, 0x31};
|
||||
|
||||
// debugWire and JTAG parameters
|
||||
constexpr static Avr8EdbgParameter DEVICE_BOOT_START_ADDR {0x02, 0x0A};
|
||||
constexpr static Avr8EdbgParameter DEVICE_FLASH_BASE {0x02, 0x06};
|
||||
constexpr static Avr8EdbgParameter DEVICE_SRAM_START {0x02, 0x0E};
|
||||
constexpr static Avr8EdbgParameter DEVICE_EEPROM_SIZE {0x02, 0x10};
|
||||
constexpr static Avr8EdbgParameter DEVICE_EEPROM_PAGE_SIZE {0x02, 0x12};
|
||||
constexpr static Avr8EdbgParameter DEVICE_FLASH_PAGE_SIZE {0x02, 0x00};
|
||||
constexpr static Avr8EdbgParameter DEVICE_FLASH_SIZE {0x02, 0x02};
|
||||
constexpr static Avr8EdbgParameter DEVICE_OCD_REVISION {0x02, 0x13};
|
||||
constexpr static Avr8EdbgParameter DEVICE_OCD_DATA_REGISTER {0x02, 0x18};
|
||||
constexpr static Avr8EdbgParameter DEVICE_SPMCR_REGISTER {0x02, 0x1D};
|
||||
constexpr static Avr8EdbgParameter DEVICE_OSCCAL_ADDR {0x02, 0x1E};
|
||||
constexpr static Avr8EdbgParameter DEVICE_EEARH_ADDR {0x02, 0x19};
|
||||
constexpr static Avr8EdbgParameter DEVICE_EEARL_ADDR {0x02, 0x1A};
|
||||
constexpr static Avr8EdbgParameter DEVICE_EECR_ADDR {0x02, 0x1B};
|
||||
constexpr static Avr8EdbgParameter DEVICE_EEDR_ADDR {0x02, 0x1C};
|
||||
|
||||
// PDI/XMega device parameters
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_APPL_BASE_ADDR {0x02, 0x00};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_BOOT_BASE_ADDR {0x02, 0x04};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_EEPROM_BASE_ADDR {0x02, 0x08};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_FUSE_BASE_ADDR {0x02, 0x0C};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_LOCKBIT_BASE_ADDR {0x02, 0x10};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_USER_SIGN_BASE_ADDR {0x02, 0x14};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_PROD_SIGN_BASE_ADDR {0x02, 0x18};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_DATA_BASE_ADDR {0x02, 0x1C};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_APPLICATION_BYTES {0x02, 0x20};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_BOOT_BYTES {0x02, 0x24};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_NVM_BASE {0x02, 0x2B};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_SIGNATURE_OFFSET {0x02, 0x2D};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_FLASH_PAGE_BYTES {0x02, 0x26};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_EEPROM_SIZE {0x02, 0x28};
|
||||
constexpr static Avr8EdbgParameter DEVICE_XMEGA_EEPROM_PAGE_SIZE {0x02, 0x2A};
|
||||
|
||||
constexpr static Avr8EdbgParameter RUN_TIMERS_WHILST_STOPPED {0x03, 0x00};
|
||||
};
|
||||
|
||||
enum class Avr8ConfigVariant: unsigned char
|
||||
{
|
||||
LOOPBACK = 0x00,
|
||||
NONE = 0xff,
|
||||
DEBUG_WIRE = 0x01,
|
||||
MEGAJTAG = 0x02,
|
||||
XMEGA = 0x03,
|
||||
UPDI = 0x05,
|
||||
};
|
||||
|
||||
enum class Avr8ConfigFunction: unsigned char
|
||||
{
|
||||
NONE = 0x00,
|
||||
PROGRAMMING = 0x01,
|
||||
DEBUGGING = 0x02,
|
||||
};
|
||||
|
||||
enum class Avr8PhysicalInterface: unsigned char
|
||||
{
|
||||
NONE = 0x00,
|
||||
JTAG = 0x04,
|
||||
DEBUG_WIRE = 0x05,
|
||||
PDI = 0x06,
|
||||
PDI_1W = 0x08,
|
||||
};
|
||||
|
||||
enum class Avr8MemoryType: unsigned char
|
||||
{
|
||||
/**
|
||||
* The SRAM memory type can be used to read &write to internal memory on the target.
|
||||
*
|
||||
* It's available with all of the config variants in debugging mode.
|
||||
*/
|
||||
SRAM = 0x20,
|
||||
|
||||
/**
|
||||
* The EEPROM memory type can be used to read &write to EEPROM memory on the target.
|
||||
*
|
||||
* It's available with all of the config variants, in debugging mode.
|
||||
*/
|
||||
EEPROM = 0x22,
|
||||
|
||||
/**
|
||||
* The FLASH_PAGE memory type can be used to read &write full flash pages on the target.
|
||||
*
|
||||
* Only available with the JTAG and debugWire config variants.
|
||||
*
|
||||
* This memory type is not available with the JTAG config variant in debugging mode. Programming mode will need
|
||||
* to be enabled before it can be used with JTAG targets. With the debugWire variant, this memory type *can* be
|
||||
* used whilst in debugging mode.
|
||||
*/
|
||||
FLASH_PAGE = 0xB0,
|
||||
|
||||
/**
|
||||
* The APPL_FLASH memory type can be used to read/write flash memory on the target.
|
||||
*
|
||||
* Only available with the XMEGA (PDI) and UPDI (PDI_1W) config variants.
|
||||
*
|
||||
* When in debugging mode, only read access is permitted. Programming mode will need to be enabled before
|
||||
* any attempts of writing data.
|
||||
*/
|
||||
APPL_FLASH = 0xC0,
|
||||
|
||||
/**
|
||||
* The SPM memory type can be used to read memory from the target whilst in debugging mode.
|
||||
*
|
||||
* Only available with JTAG and debugWire config variants.
|
||||
*/
|
||||
SPM = 0xA0,
|
||||
|
||||
/**
|
||||
* The REGISTER_FILE memory type can be used to read &write to general purpose registers.
|
||||
*
|
||||
* Only available in debugging mode and with XMEGA and UPDI config variants. The SRAM memory type can be used
|
||||
* to access general purpose registers when other variants are in use.
|
||||
*/
|
||||
REGISTER_FILE = 0xB8,
|
||||
};
|
||||
|
||||
enum class Avr8ResponseId: unsigned char
|
||||
{
|
||||
OK = 0x80,
|
||||
DATA = 0x84,
|
||||
FAILED = 0xA0,
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "AvrCommand.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
|
||||
|
||||
std::vector<unsigned char> AvrCommand::getData() const
|
||||
{
|
||||
std::vector<unsigned char> data;
|
||||
auto commandPacket = this->getCommandPacket();
|
||||
std::size_t commandPacketSize = commandPacket.size();
|
||||
data.resize(3 + commandPacketSize);
|
||||
// FragmentInfo byte
|
||||
data[0] = static_cast<unsigned char>((this->getFragmentNumber() << 4) | this->getFragmentCount());
|
||||
|
||||
// Size byte
|
||||
data[1] = (unsigned char) (commandPacketSize >> 8);
|
||||
data[2] = (unsigned char) (commandPacketSize & 0xFF);
|
||||
|
||||
if (commandPacketSize > 0) {
|
||||
for (std::size_t index = 0; index <= commandPacketSize - 1; index++) {
|
||||
data[3 + index] = commandPacket[index];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
|
||||
{
|
||||
class AvrCommand: public Command
|
||||
{
|
||||
private:
|
||||
size_t fragmentNumber = 1;
|
||||
size_t fragmentCount = 1;
|
||||
|
||||
std::vector<unsigned char> commandPacket;
|
||||
|
||||
public:
|
||||
AvrCommand() {
|
||||
this->setCommandId(0x80);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs raw command data on the fly.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::vector<unsigned char> getData() const override;
|
||||
|
||||
size_t getFragmentNumber() const {
|
||||
return this->fragmentNumber;
|
||||
}
|
||||
|
||||
void setFragmentNumber(size_t fragmentNumber) {
|
||||
this->fragmentNumber = fragmentNumber;
|
||||
}
|
||||
|
||||
size_t getFragmentCount() const {
|
||||
return this->fragmentCount;
|
||||
}
|
||||
|
||||
void setFragmentCount(size_t fragmentCount) {
|
||||
this->fragmentCount = fragmentCount;
|
||||
}
|
||||
|
||||
const std::vector<unsigned char>& getCommandPacket() const {
|
||||
return this->commandPacket;
|
||||
}
|
||||
|
||||
void setCommandPacket(const std::vector<unsigned char>& commandPacket) {
|
||||
this->commandPacket = commandPacket;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include "AvrEvent.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void AvrEvent::init(unsigned char* response, size_t length)
|
||||
{
|
||||
Response::init(response, length);
|
||||
|
||||
if (this->getResponseId() != 0x82) {
|
||||
throw Exception("Failed to construct AvrEvent object - invalid response ID.");
|
||||
}
|
||||
|
||||
std::vector<unsigned char> responseData = this->getData();
|
||||
|
||||
if (responseData.size() < 2) {
|
||||
// All AVR_EVT responses should consist of at least two bytes (excluding the AVR_EVT ID)
|
||||
throw Exception("Failed to construct AvrEvent object - AVR_EVT response "
|
||||
"returned no additional data.");
|
||||
}
|
||||
|
||||
// Response size is two bytes, MSB
|
||||
size_t responsePacketSize = static_cast<size_t>((responseData[0] << 8) | responseData[1]);
|
||||
|
||||
if (responseData.size() < 2) {
|
||||
// All AVR_EVT responses should consist of at least two bytes (excluding the AVR_EVT ID)
|
||||
throw Exception("Failed to construct AvrEvent object - AVR_EVT response "
|
||||
"contained invalid event data size.");
|
||||
}
|
||||
|
||||
std::vector<unsigned char> eventData;
|
||||
|
||||
/*
|
||||
* Ignore the SOF, protocol version &handler id and sequence ID (with all make up 5 bytes in total, 7 when
|
||||
* you include the two size bytes)
|
||||
*/
|
||||
eventData.insert(
|
||||
eventData.end(),
|
||||
responseData.begin() + 7,
|
||||
responseData.begin() + 7 + static_cast<long>(responsePacketSize)
|
||||
);
|
||||
|
||||
this->setEventData(eventData);
|
||||
|
||||
if (eventData.size() >= 1) {
|
||||
this->eventId = eventData[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/Edbg.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
|
||||
{
|
||||
using Edbg::ProtocolHandlerId;
|
||||
|
||||
enum class AvrEventId : unsigned char
|
||||
{
|
||||
AVR8_BREAK_EVENT = 0x40,
|
||||
};
|
||||
|
||||
inline bool operator==(unsigned char rawId, AvrEventId id) {
|
||||
return static_cast<unsigned char>(id) == rawId;
|
||||
}
|
||||
|
||||
inline bool operator==(AvrEventId id, unsigned char rawId) {
|
||||
return rawId == id;
|
||||
}
|
||||
|
||||
class AvrEvent: public Response
|
||||
{
|
||||
private:
|
||||
unsigned char eventId;
|
||||
|
||||
std::vector<unsigned char> eventData;
|
||||
|
||||
protected:
|
||||
void setEventData(const std::vector<unsigned char>& eventData) {
|
||||
this->eventData = eventData;
|
||||
}
|
||||
|
||||
public:
|
||||
AvrEvent() = default;
|
||||
|
||||
/**
|
||||
* 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.data(), rawData.size());
|
||||
}
|
||||
|
||||
void init(unsigned char* response, size_t length) override;
|
||||
|
||||
const std::vector<unsigned char>& getEventData() const {
|
||||
return this->eventData;
|
||||
}
|
||||
|
||||
size_t getEventDataSize() const {
|
||||
return this->eventData.size();
|
||||
}
|
||||
|
||||
AvrEventId getEventId() {
|
||||
return static_cast<AvrEventId>(this->eventId);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
|
||||
{
|
||||
class AvrEventCommand: public Command
|
||||
{
|
||||
public:
|
||||
AvrEventCommand() {
|
||||
this->setCommandId(0x82);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "AvrResponse.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void AvrResponse::init(unsigned char* response, std::size_t length)
|
||||
{
|
||||
Response::init(response, length);
|
||||
|
||||
if (this->getResponseId() != 0x81) {
|
||||
throw Exception("Failed to construct AvrResponse object - invalid response ID.");
|
||||
}
|
||||
|
||||
std::vector<unsigned char> responseData = this->getData();
|
||||
|
||||
if (responseData.empty()) {
|
||||
// All AVR responses should contain at least one byte (the fragment info byte)
|
||||
throw Exception("Failed to construct AvrResponse object - AVR_RSP response "
|
||||
"returned no additional data");
|
||||
}
|
||||
|
||||
if (responseData[0] == 0x00) {
|
||||
// This AVR Response contains no data (the device had no data to send), so we can stop here.
|
||||
return;
|
||||
}
|
||||
|
||||
this->setFragmentCount(static_cast<std::uint8_t>(responseData[0] & 0x0Fu));
|
||||
this->setFragmentNumber(static_cast<std::uint8_t>(responseData[0] >> 4));
|
||||
|
||||
// Response size is two bytes, MSB
|
||||
std::size_t responsePacketSize = static_cast<std::size_t>((responseData[1] << 8u) + responseData[2]);
|
||||
std::vector<unsigned char> responsePacket;
|
||||
responsePacket.resize(responsePacketSize);
|
||||
|
||||
for (std::size_t i = 0; i < responsePacketSize; i++) {
|
||||
responsePacket[i] = responseData[i + 3];
|
||||
}
|
||||
|
||||
this->setResponsePacket(responsePacket);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
|
||||
{
|
||||
class AvrResponse: public Response
|
||||
{
|
||||
private:
|
||||
std::uint8_t fragmentNumber = 0;
|
||||
std::uint8_t fragmentCount = 0;
|
||||
|
||||
std::vector<unsigned char> responsePacket;
|
||||
|
||||
protected:
|
||||
void setFragmentNumber(std::uint8_t fragmentNumber) {
|
||||
this->fragmentNumber = fragmentNumber;
|
||||
}
|
||||
|
||||
void setFragmentCount(std::uint8_t fragmentCount) {
|
||||
this->fragmentCount = fragmentCount;
|
||||
}
|
||||
|
||||
void setResponsePacket(const std::vector<unsigned char>& responsePacket) {
|
||||
this->responsePacket = responsePacket;
|
||||
}
|
||||
|
||||
public:
|
||||
AvrResponse() = default;
|
||||
|
||||
/**
|
||||
* 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.data(), rawData.size());
|
||||
}
|
||||
|
||||
void init(unsigned char* response, std::size_t length) override;
|
||||
|
||||
std::uint8_t getFragmentNumber() const {
|
||||
return this->fragmentNumber;
|
||||
}
|
||||
|
||||
std::uint8_t getFragmentCount() const {
|
||||
return this->fragmentCount;
|
||||
}
|
||||
|
||||
const std::vector<unsigned char>& getResponsePacket() const {
|
||||
return this->responsePacket;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
|
||||
{
|
||||
/**
|
||||
* All AVR commands result in an automatic response, but that is just a response to confirm
|
||||
* receipt of the AVR Command. It is *not* a response to the AVR command.
|
||||
*
|
||||
* 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
|
||||
* 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'
|
||||
* document, by Microchip. Link provided in the AtmelICE device class declaration.
|
||||
*
|
||||
* An AvrResponseCommand is very simple - it consists of a command ID and nothing more.
|
||||
*/
|
||||
class AvrResponseCommand: public Command
|
||||
{
|
||||
public:
|
||||
AvrResponseCommand() {
|
||||
this->setCommandId(0x81);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class ActivatePhysical: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
bool reset = false;
|
||||
|
||||
public:
|
||||
ActivatePhysical() = default;
|
||||
ActivatePhysical(bool reset) : reset(reset) {};
|
||||
|
||||
void setReset(bool reset) {
|
||||
this->reset = reset;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The activate physical command consists of 3 bytes:
|
||||
* 1. Command ID (0x10)
|
||||
* 2. Version (0x00)
|
||||
* 3. Reset flag (to apply external reset)
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(3, 0x00);
|
||||
output[0] = 0x10;
|
||||
output[1] = 0x00;
|
||||
output[2] = static_cast<unsigned char>(this->reset);
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class Attach: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
bool breakAfterAttach = false;
|
||||
|
||||
public:
|
||||
Attach() = default;
|
||||
Attach(bool breakAfterAttach) : breakAfterAttach(breakAfterAttach) {};
|
||||
|
||||
void setBreadAfterAttach(bool breakAfterAttach) {
|
||||
this->breakAfterAttach = breakAfterAttach;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The attach command consists of 3 bytes:
|
||||
* 1. Command ID (0x13)
|
||||
* 2. Version (0x00)
|
||||
* 3. Break (stop) after attach flag
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(3, 0x00);
|
||||
output[0] = 0x13;
|
||||
output[1] = 0x00;
|
||||
output[2] = static_cast<unsigned char>(this->breakAfterAttach);
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Avr8Generic.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AvrCommandFrame.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/ResponseFrames/AVR8Generic/Avr8GenericResponseFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
using namespace DebugToolDrivers::Protocols::CmsisDap;
|
||||
using namespace DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
|
||||
|
||||
class Avr8GenericCommandFrame: public AvrCommandFrame
|
||||
{
|
||||
public:
|
||||
using ResponseFrameType = ResponseFrames::Avr8Generic::Avr8GenericResponseFrame;
|
||||
|
||||
Avr8GenericCommandFrame() {
|
||||
this->setProtocolHandlerId(ProtocolHandlerId::Avr8Generic);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class ClearAllSoftwareBreakpoints: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
void init() {
|
||||
/*
|
||||
* The clear all software breakpoints command consists of 2 bytes:
|
||||
* 1. Command ID (0x45)
|
||||
* 2. Version (0x00)
|
||||
*/
|
||||
auto payload = std::vector<unsigned char>(2);
|
||||
payload[0] = 0x45;
|
||||
payload[1] = 0x00;
|
||||
this->setPayload(payload);
|
||||
}
|
||||
|
||||
public:
|
||||
ClearAllSoftwareBreakpoints() {
|
||||
init();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class ClearSoftwareBreakpoints: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
std::vector<std::uint32_t> addresses;
|
||||
|
||||
public:
|
||||
ClearSoftwareBreakpoints() = default;
|
||||
|
||||
ClearSoftwareBreakpoints(const std::vector<std::uint32_t>& addresses) : addresses(addresses) {}
|
||||
|
||||
void setAddresses(const std::vector<std::uint32_t>& addresses) {
|
||||
this->addresses = addresses;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The clear software breakpoints command consists of 2 bytes + 4*n bytes, where n is the number
|
||||
* of breakpoints to clear:
|
||||
*
|
||||
* 1. Command ID (0x44)
|
||||
* 2. Version (0x00)
|
||||
* ... addresses
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(2, 0x00);
|
||||
output[0] = 0x44;
|
||||
output[1] = 0x00;
|
||||
|
||||
for (const auto& address : this->addresses) {
|
||||
output.push_back(static_cast<unsigned char>(address));
|
||||
output.push_back(static_cast<unsigned char>(address >> 8));
|
||||
output.push_back(static_cast<unsigned char>(address >> 16));
|
||||
output.push_back(static_cast<unsigned char>(address >> 24));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class DeactivatePhysical: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
void init() {
|
||||
/*
|
||||
* The deactivate physical command consists of 2 bytes:
|
||||
* 1. Command ID (0x11)
|
||||
* 2. Version (0x00)
|
||||
*/
|
||||
auto payload = std::vector<unsigned char>(2);
|
||||
payload[0] = 0x11;
|
||||
payload[1] = 0x00;
|
||||
this->setPayload(payload);
|
||||
}
|
||||
|
||||
public:
|
||||
DeactivatePhysical() {
|
||||
init();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class Detach: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
void init() {
|
||||
/*
|
||||
* The detach command consists of 2 bytes:
|
||||
* 1. Command ID (0x11)
|
||||
* 2. Version (0x00)
|
||||
*/
|
||||
auto payload = std::vector<unsigned char>(2);
|
||||
payload[0] = 0x11;
|
||||
payload[1] = 0x00;
|
||||
this->setPayload(payload);
|
||||
}
|
||||
|
||||
public:
|
||||
Detach() {
|
||||
init();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class DisableDebugWire: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
void init() {
|
||||
/*
|
||||
* The disable debugWire command consists of 2 bytes:
|
||||
* 1. Command ID (0x17)
|
||||
* 2. Version (0x00)
|
||||
*/
|
||||
auto payload = std::vector<unsigned char>(2);
|
||||
payload[0] = 0x17;
|
||||
payload[1] = 0x00;
|
||||
this->setPayload(payload);
|
||||
}
|
||||
|
||||
public:
|
||||
DisableDebugWire() {
|
||||
init();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
#include "../../ResponseFrames/AVR8Generic/GetDeviceId.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class GetDeviceId: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
void init() {
|
||||
/*
|
||||
* The get device ID command consists of 2 bytes:
|
||||
* 1. Command ID (0x12)
|
||||
* 2. Version (0x00)
|
||||
*/
|
||||
auto payload = std::vector<unsigned char>(2);
|
||||
payload[0] = 0x12;
|
||||
payload[1] = 0x00;
|
||||
this->setPayload(payload);
|
||||
}
|
||||
|
||||
public:
|
||||
using ResponseFrameType = ResponseFrames::Avr8Generic::GetDeviceId;
|
||||
|
||||
GetDeviceId() {
|
||||
init();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class GetParameter: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
Avr8EdbgParameter parameter;
|
||||
std::uint8_t size;
|
||||
|
||||
public:
|
||||
GetParameter() = default;
|
||||
|
||||
GetParameter(const Avr8EdbgParameter& parameter) {
|
||||
this->setParameter(parameter);
|
||||
}
|
||||
|
||||
GetParameter(const Avr8EdbgParameter& parameter, std::uint8_t size) : GetParameter(parameter) {
|
||||
this->setSize(size);
|
||||
}
|
||||
|
||||
void setParameter(const Avr8EdbgParameter& parameter) {
|
||||
this->parameter = parameter;
|
||||
}
|
||||
|
||||
void setSize(std::uint8_t size) {
|
||||
this->size = size;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The get param command consists of 5 bytes:
|
||||
* 1. Command ID (0x02)
|
||||
* 2. Version (0x00)
|
||||
* 3. Param context (Avr8Parameter::context)
|
||||
* 4. Param ID (Avr8Parameter::id)
|
||||
* 5. Param value length (this->size)
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(5, 0x00);
|
||||
output[0] = 0x02;
|
||||
output[1] = 0x00;
|
||||
output[2] = static_cast<unsigned char>(this->parameter.context);
|
||||
output[3] = static_cast<unsigned char>(this->parameter.id);
|
||||
output[4] = static_cast<unsigned char>(this->size);
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
#include "../../ResponseFrames/AVR8Generic/GetProgramCounter.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class GetProgramCounter: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
void init() {
|
||||
/*
|
||||
* The PC Read command consists of 2 bytes:
|
||||
* 1. Command ID (0x35)
|
||||
* 2. Version (0x00)
|
||||
*/
|
||||
auto payload = std::vector<unsigned char>(2);
|
||||
payload[0] = 0x35;
|
||||
payload[1] = 0x00;
|
||||
this->setPayload(payload);
|
||||
}
|
||||
|
||||
public:
|
||||
using ResponseFrameType = ResponseFrames::Avr8Generic::GetProgramCounter;
|
||||
|
||||
GetProgramCounter() {
|
||||
init();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
#include "../../ResponseFrames/AVR8Generic/ReadMemory.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
using namespace Exceptions;
|
||||
|
||||
class ReadMemory: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
Avr8MemoryType type;
|
||||
std::uint32_t address = 0;
|
||||
std::uint32_t bytes = 0;
|
||||
|
||||
public:
|
||||
using ResponseFrameType = ResponseFrames::Avr8Generic::ReadMemory;
|
||||
|
||||
ReadMemory() = default;
|
||||
|
||||
void setType(const Avr8MemoryType& type) {
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
void setAddress(std::uint32_t address) {
|
||||
this->address = address;
|
||||
}
|
||||
|
||||
void setBytes(std::uint32_t bytes) {
|
||||
this->bytes = bytes;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The read memory command consists of 11 bytes:
|
||||
* 1. Command ID (0x21)
|
||||
* 2. Version (0x00)
|
||||
* 3. Memory type (Avr8MemoryType)
|
||||
* 4. Start address (4 bytes)
|
||||
* 5. Number of bytes to read (4 bytes)
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(11, 0x00);
|
||||
output[0] = 0x21;
|
||||
output[1] = 0x00;
|
||||
output[2] = static_cast<unsigned char>(this->type);
|
||||
output[3] = static_cast<unsigned char>(this->address);
|
||||
output[4] = static_cast<unsigned char>(this->address >> 8);
|
||||
output[5] = static_cast<unsigned char>(this->address >> 16);
|
||||
output[6] = static_cast<unsigned char>(this->address >> 24);
|
||||
output[7] = static_cast<unsigned char>(this->bytes);
|
||||
output[8] = static_cast<unsigned char>(this->bytes >> 8);
|
||||
output[9] = static_cast<unsigned char>(this->bytes >> 16);
|
||||
output[10] = static_cast<unsigned char>(this->bytes >> 24);
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class Reset: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
bool stopAtMainAddress = false;
|
||||
|
||||
public:
|
||||
Reset() = default;
|
||||
Reset(bool stopAtMainAddress) : stopAtMainAddress(stopAtMainAddress) {};
|
||||
|
||||
void setStopAtMainAddress(bool stopAtMainAddress) {
|
||||
this->stopAtMainAddress = stopAtMainAddress;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The reset command consists of 3 bytes:
|
||||
* 1. Command ID (0x30)
|
||||
* 2. Version (0x00)
|
||||
* 3. "Level" (0x01 to stop at boot reset vector or 0x02 to stop at main address)
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(3, 0x00);
|
||||
output[0] = 0x30;
|
||||
output[1] = 0x00;
|
||||
output[2] = this->stopAtMainAddress ? 0x02 : 0x01;
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class Run: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
void init() {
|
||||
/*
|
||||
* The run command consists of 2 bytes:
|
||||
* 1. Command ID (0x32)
|
||||
* 2. Version (0x00)
|
||||
*/
|
||||
auto payload = std::vector<unsigned char>(2);
|
||||
payload[0] = 0x32;
|
||||
payload[1] = 0x00;
|
||||
this->setPayload(payload);
|
||||
}
|
||||
|
||||
public:
|
||||
Run() {
|
||||
init();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class RunTo: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
std::uint32_t address;
|
||||
|
||||
public:
|
||||
RunTo() = default;
|
||||
|
||||
RunTo(const std::uint32_t& address) : address(address) {}
|
||||
|
||||
void setAddress(const std::uint32_t& address) {
|
||||
this->address = address;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The run-to command consists of 6 bytes:
|
||||
*
|
||||
* 1. Command ID (0x33)
|
||||
* 2. Version (0x00)
|
||||
* 3. Address to run to (4 bytes)
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(6, 0x00);
|
||||
output[0] = 0x33;
|
||||
output[1] = 0x00;
|
||||
|
||||
// The address to run to needs to be the 16-bit word address, not the byte address.
|
||||
auto wordAddress = this->address / 2;
|
||||
output[2] = (static_cast<unsigned char>(wordAddress));
|
||||
output[3] = (static_cast<unsigned char>(wordAddress >> 8));
|
||||
output[4] = (static_cast<unsigned char>(wordAddress >> 16));
|
||||
output[5] = (static_cast<unsigned char>(wordAddress >> 24));
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
using namespace Exceptions;
|
||||
|
||||
class SetParameter: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
Avr8EdbgParameter parameter;
|
||||
std::vector<unsigned char> value;
|
||||
|
||||
public:
|
||||
SetParameter() = default;
|
||||
|
||||
SetParameter(const Avr8EdbgParameter& parameter) {
|
||||
this->setParameter(parameter);
|
||||
}
|
||||
|
||||
SetParameter(const Avr8EdbgParameter& parameter, const std::vector<unsigned char>& value) : SetParameter(parameter) {
|
||||
this->setValue(value);
|
||||
}
|
||||
|
||||
SetParameter(const Avr8EdbgParameter& parameter, unsigned char value) : SetParameter(parameter) {
|
||||
this->setValue(value);
|
||||
}
|
||||
|
||||
void setParameter(const Avr8EdbgParameter& parameter) {
|
||||
this->parameter = parameter;
|
||||
}
|
||||
|
||||
void setValue(const std::vector<unsigned char>& value) {
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
void setValue(unsigned char value) {
|
||||
this->value.resize(1, value);
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The set param command consists of this->value.size() + 5 bytes. The first five bytes consist of:
|
||||
* 1. Command ID (0x01)
|
||||
* 2. Version (0x00)
|
||||
* 3. Param context (Avr8Parameter::context)
|
||||
* 4. Param ID (Avr8Parameter::id)
|
||||
* 5. Param value length (this->value.size()) - this is only one byte in size, so its value should
|
||||
* never exceed 255.
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(this->value.size() + 5, 0x00);
|
||||
output[0] = 0x01;
|
||||
output[1] = 0x00;
|
||||
output[2] = static_cast<unsigned char>(this->parameter.context);
|
||||
output[3] = static_cast<unsigned char>(this->parameter.id);
|
||||
output[4] = static_cast<unsigned char>(this->value.size());
|
||||
output.insert(output.begin() + 5, this->value.begin(), this->value.end());
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
using namespace Exceptions;
|
||||
|
||||
class SetProgramCounter: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
std::uint32_t programCounter = 0;
|
||||
|
||||
public:
|
||||
SetProgramCounter(std::uint32_t programCounter) : programCounter(programCounter) {}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The PC write command consists of 6 bytes:
|
||||
* 1. Command ID (0x01)
|
||||
* 2. Version (0x00)
|
||||
* 3. PC (4 bytes, LSB)
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(6, 0x00);
|
||||
output[0] = 0x36;
|
||||
output[1] = 0x00;
|
||||
output[2] = static_cast<unsigned char>(this->programCounter);
|
||||
output[3] = static_cast<unsigned char>(this->programCounter >> 8);
|
||||
output[4] = static_cast<unsigned char>(this->programCounter >> 16);
|
||||
output[5] = static_cast<unsigned char>(this->programCounter >> 24);
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class SetSoftwareBreakpoints: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
std::vector<std::uint32_t> addresses;
|
||||
|
||||
public:
|
||||
SetSoftwareBreakpoints() = default;
|
||||
|
||||
SetSoftwareBreakpoints(const std::vector<std::uint32_t>& addresses) : addresses(addresses) {}
|
||||
|
||||
void setAddresses(const std::vector<std::uint32_t>& addresses) {
|
||||
this->addresses = addresses;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The set software breakpoint command consists of 2 bytes + 4*n bytes, where n is the number
|
||||
* of breakpoints to set:
|
||||
*
|
||||
* 1. Command ID (0x43)
|
||||
* 2. Version (0x00)
|
||||
* ... addresses
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(2, 0x00);
|
||||
output[0] = 0x43;
|
||||
output[1] = 0x00;
|
||||
|
||||
for (const auto& address : this->addresses) {
|
||||
output.push_back(static_cast<unsigned char>(address));
|
||||
output.push_back(static_cast<unsigned char>(address >> 8));
|
||||
output.push_back(static_cast<unsigned char>(address >> 16));
|
||||
output.push_back(static_cast<unsigned char>(address >> 24));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class SetXmegaSoftwareBreakpoint: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
std::uint32_t address;
|
||||
|
||||
public:
|
||||
SetXmegaSoftwareBreakpoint() = default;
|
||||
|
||||
SetXmegaSoftwareBreakpoint(std::uint32_t address) : address(address) {}
|
||||
|
||||
void setAddress(std::uint32_t address) {
|
||||
this->address = address;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The set software breakpoint command consists of 6 bytes bytes
|
||||
*
|
||||
* 1. Command ID (0x42)
|
||||
* 2. Version (0x00)
|
||||
* 3. Address (4 bytes)
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(15, 0x00);
|
||||
output[0] = 0x42;
|
||||
output[1] = 0x00;
|
||||
output[2] = static_cast<unsigned char>(address);
|
||||
output[3] = static_cast<unsigned char>(address >> 8);
|
||||
output[4] = static_cast<unsigned char>(address >> 16);
|
||||
output[5] = static_cast<unsigned char>(address >> 24);
|
||||
output[13] = 0x01; // One breakpoint
|
||||
output[14] = 0x00;
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class Step: public Avr8GenericCommandFrame
|
||||
{
|
||||
public:
|
||||
Step() = default;
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The step command consists of 4 bytes:
|
||||
* 1. Command ID (0x34)
|
||||
* 2. Version (0x00)
|
||||
* 3. Level (0x01 for instruction level step, 0x02 for statement level step)
|
||||
* 4. Mode (0x00 for step over, 0x01 for step into, 0x02 for step out)
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(4, 0x00);
|
||||
output[0] = 0x34;
|
||||
output[1] = 0x00;
|
||||
output[2] = 0x01;
|
||||
output[3] = 0x01;
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
class Stop: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
bool stopImmediately = true;
|
||||
|
||||
public:
|
||||
Stop() = default;
|
||||
Stop(bool stopImmediately) : stopImmediately(stopImmediately) {};
|
||||
|
||||
void setStopImmediately(bool stopImmediately) {
|
||||
this->stopImmediately = stopImmediately;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The stop command consists of 3 bytes:
|
||||
* 1. Command ID (0x31)
|
||||
* 2. Version (0x00)
|
||||
* 3. Stop immediately (0x01 to stop immediately or 0x02 to stop at the next symbol)
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(3, 0x00);
|
||||
output[0] = 0x31;
|
||||
output[1] = 0x00;
|
||||
output[2] = this->stopImmediately ? 0x01 : 0x02;
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Avr8GenericCommandFrame.hpp"
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
|
||||
{
|
||||
using namespace Exceptions;
|
||||
using Bloom::Targets::TargetMemoryBuffer;
|
||||
|
||||
class WriteMemory: public Avr8GenericCommandFrame
|
||||
{
|
||||
private:
|
||||
Avr8MemoryType type;
|
||||
std::uint32_t address = 0;
|
||||
TargetMemoryBuffer buffer;
|
||||
|
||||
public:
|
||||
WriteMemory() = default;
|
||||
|
||||
void setType(const Avr8MemoryType& type) {
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
void setAddress(std::uint32_t address) {
|
||||
this->address = address;
|
||||
}
|
||||
|
||||
void setBuffer(const TargetMemoryBuffer& buffer) {
|
||||
this->buffer = buffer;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The write memory command consists of 12 bytes + the buffer size:
|
||||
* 1. Command ID (0x23)
|
||||
* 2. Version (0x00)
|
||||
* 3. Memory type (Avr8MemoryType)
|
||||
* 4. Start address (4 bytes)
|
||||
* 5. Number of bytes to write (4 bytes)
|
||||
* 6. Asynchronous flag (0x00 for "write first, then reply" and 0x01 for "reply first, then write")
|
||||
* 7. Buffer
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(12, 0x00);
|
||||
output[0] = 0x23;
|
||||
output[1] = 0x00;
|
||||
output[2] = static_cast<unsigned char>(this->type);
|
||||
output[3] = static_cast<unsigned char>(this->address);
|
||||
output[4] = static_cast<unsigned char>(this->address >> 8);
|
||||
output[5] = static_cast<unsigned char>(this->address >> 16);
|
||||
output[6] = static_cast<unsigned char>(this->address >> 24);
|
||||
|
||||
auto bytesToWrite = static_cast<std::uint32_t>(this->buffer.size());
|
||||
output[7] = static_cast<unsigned char>(bytesToWrite);
|
||||
output[8] = static_cast<unsigned char>(bytesToWrite >> 8);
|
||||
output[9] = static_cast<unsigned char>(bytesToWrite >> 16);
|
||||
output[10] = static_cast<unsigned char>(bytesToWrite >> 24);
|
||||
|
||||
// We always set the async flag to 0x00 ("write first, then reply")
|
||||
output[11] = 0x00;
|
||||
|
||||
output.insert(output.end(), this->buffer.begin(), this->buffer.end());
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "AvrCommandFrame.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
|
||||
|
||||
std::vector<AvrCommand> AvrCommandFrame::generateAvrCommands(std::size_t maximumCommandPacketSize) const {
|
||||
auto rawCommandFrame = static_cast<std::vector<unsigned char>>(*this);
|
||||
std::size_t commandFrameSize = rawCommandFrame.size();
|
||||
std::size_t commandsRequired = static_cast<std::size_t>(ceil(static_cast<float>(commandFrameSize) / static_cast<float>(maximumCommandPacketSize)));
|
||||
|
||||
std::vector<AvrCommand> avrCommands;
|
||||
std::size_t copiedPacketSize = 0;
|
||||
for (std::size_t i = 0; i < commandsRequired; i++) {
|
||||
AvrCommand avrCommand;
|
||||
avrCommand.setFragmentCount(commandsRequired);
|
||||
avrCommand.setFragmentNumber(i + 1);
|
||||
auto commandPacket = avrCommand.getCommandPacket();
|
||||
|
||||
// If we're on the last packet, the packet size will be what ever is left of the AvrCommandFrame
|
||||
std::size_t commandPacketSize = ((i + 1) != commandsRequired) ? maximumCommandPacketSize
|
||||
: (commandFrameSize - (maximumCommandPacketSize * i));
|
||||
|
||||
commandPacket.insert(
|
||||
commandPacket.end(),
|
||||
rawCommandFrame.begin() + static_cast<long>(copiedPacketSize),
|
||||
rawCommandFrame.begin() + static_cast<long>(copiedPacketSize + commandPacketSize)
|
||||
);
|
||||
|
||||
avrCommand.setCommandPacket(commandPacket);
|
||||
avrCommands.push_back(avrCommand);
|
||||
copiedPacketSize += commandPacketSize;
|
||||
}
|
||||
|
||||
return avrCommands;
|
||||
}
|
||||
|
||||
AvrCommandFrame::operator std::vector<unsigned char> () const {
|
||||
auto data = this->getPayload();
|
||||
auto dataSize = data.size();
|
||||
|
||||
auto rawCommand = std::vector<unsigned char>(5);
|
||||
|
||||
rawCommand[0] = this->SOF;
|
||||
rawCommand[1] = this->getProtocolVersion();
|
||||
|
||||
rawCommand[2] = static_cast<unsigned char>(this->getSequenceId());
|
||||
rawCommand[3] = static_cast<unsigned char>(this->getSequenceId() >> 8);
|
||||
|
||||
rawCommand[4] = static_cast<unsigned char>(this->getProtocolHandlerId());
|
||||
|
||||
if (dataSize > 0) {
|
||||
rawCommand.insert(
|
||||
rawCommand.end(),
|
||||
data.begin(),
|
||||
data.end()
|
||||
);
|
||||
}
|
||||
|
||||
return rawCommand;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/Edbg.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrCommand.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/ResponseFrames/AvrResponseFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
|
||||
{
|
||||
using namespace Edbg;
|
||||
using namespace DebugToolDrivers::Protocols::CmsisDap;
|
||||
|
||||
class AvrCommandFrame
|
||||
{
|
||||
private:
|
||||
unsigned char SOF = 0x0E;
|
||||
|
||||
unsigned char protocolVersion = 0x00;
|
||||
|
||||
/**
|
||||
* Incrementing from 0x00
|
||||
*/
|
||||
std::uint16_t sequenceId = 0;
|
||||
inline static std::uint16_t lastSequenceId = 0;
|
||||
|
||||
/**
|
||||
* Destination sub-protocol handler ID
|
||||
*/
|
||||
ProtocolHandlerId protocolHandlerID;
|
||||
|
||||
std::vector<unsigned char> payload;
|
||||
|
||||
public:
|
||||
using ResponseFrameType = AvrResponseFrame;
|
||||
|
||||
AvrCommandFrame() {
|
||||
if (this->lastSequenceId < std::numeric_limits<decltype(this->lastSequenceId)>::max()) {
|
||||
this->sequenceId = ++(this->lastSequenceId);
|
||||
|
||||
} else {
|
||||
this->sequenceId = 0;
|
||||
this->lastSequenceId = 0;
|
||||
}
|
||||
};
|
||||
|
||||
unsigned char getProtocolVersion() const {
|
||||
return this->protocolVersion;
|
||||
}
|
||||
|
||||
void setProtocolVersion(unsigned char protocolVersion) {
|
||||
this->protocolVersion = protocolVersion;
|
||||
}
|
||||
|
||||
std::uint16_t getSequenceId() const {
|
||||
return this->sequenceId;
|
||||
}
|
||||
|
||||
ProtocolHandlerId getProtocolHandlerId() const {
|
||||
return this->protocolHandlerID;
|
||||
}
|
||||
|
||||
void setProtocolHandlerId(ProtocolHandlerId protocolHandlerId) {
|
||||
this->protocolHandlerID = protocolHandlerId;
|
||||
}
|
||||
|
||||
void setProtocolHandlerId(unsigned char protocolHandlerId) {
|
||||
this->protocolHandlerID = static_cast<ProtocolHandlerId>(protocolHandlerId);
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const {
|
||||
return this->payload;
|
||||
}
|
||||
|
||||
void setPayload(const std::vector<unsigned char>& payload) {
|
||||
this->payload = payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* AvrCommandFrames are sent to the device via AVRCommands (CMSIS-DAP vendor commands).
|
||||
*
|
||||
* If the size of an AvrCommandFrame exceeds the maximum packet size of an AVRCommand, it will need to
|
||||
* be split into multiple AVRCommands before being sent to the device.
|
||||
*
|
||||
* This methods generates AVR commands from an AvrCommandFrame. The number of AVRCommands generated depends
|
||||
* on the size of the AvrCommandFrame and the passed maximumCommandPacketSize.
|
||||
*
|
||||
* @param maximumCommandPacketSize
|
||||
* The maximum size of an AVRCommand command packet. This is usually the REPORT_SIZE of the HID
|
||||
* endpoint, minus a few bytes to account for other AVRCommand fields. The maximumCommandPacketSize is used to
|
||||
* determine the number of AVRCommands to be generated.
|
||||
*
|
||||
* @return
|
||||
* A vector of sequenced AVRCommands, each containing a segment of the AvrCommandFrame.
|
||||
*/
|
||||
std::vector<AvrCommand> generateAvrCommands(std::size_t maximumCommandPacketSize) const;
|
||||
|
||||
/**
|
||||
* Converts instance of a CMSIS Command to an unsigned char, for sending to the Atmel ICE device.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
explicit virtual operator std::vector<unsigned char> () const;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "AvrCommandFrame.hpp"
|
||||
|
||||
#include "Discovery/DiscoveryCommandFrame.hpp"
|
||||
#include "Discovery/Query.hpp"
|
||||
|
||||
#include "HouseKeeping/HouseKeepingCommandFrame.hpp"
|
||||
#include "HouseKeeping/StartSession.hpp"
|
||||
#include "HouseKeeping/EndSession.hpp"
|
||||
|
||||
#include "AVR8Generic/Avr8GenericCommandFrame.hpp"
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/ResponseFrames/DiscoveryResponseFrame.hpp>
|
||||
#include "../AvrCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Discovery
|
||||
{
|
||||
using namespace DebugToolDrivers::Protocols::CmsisDap;
|
||||
/**
|
||||
* Discovery commands can only return two responses; A LIST response and a failure.
|
||||
*/
|
||||
enum class ResponseId : unsigned char
|
||||
{
|
||||
/*
|
||||
* According to the protocol docs, response ID 0x81 is for a LIST response, but this doesn't seem to be
|
||||
* well defined. So just going to use a generic name.
|
||||
*/
|
||||
OK = 0x81,
|
||||
FAILED = 0xA0,
|
||||
};
|
||||
|
||||
inline bool operator==(unsigned char rawId, ResponseId id) {
|
||||
return static_cast<unsigned char>(id) == rawId;
|
||||
}
|
||||
|
||||
inline bool operator==(ResponseId id, unsigned char rawId) {
|
||||
return static_cast<unsigned char>(id) == rawId;
|
||||
}
|
||||
|
||||
inline bool operator!=(unsigned char rawId, ResponseId id) {
|
||||
return static_cast<unsigned char>(id) != rawId;
|
||||
}
|
||||
|
||||
inline bool operator!=(ResponseId id, unsigned char rawId) {
|
||||
return static_cast<unsigned char>(id) != rawId;
|
||||
}
|
||||
|
||||
class DiscoveryCommandFrame: public AvrCommandFrame
|
||||
{
|
||||
public:
|
||||
using ResponseFrameType = ResponseFrames::DiscoveryResponseFrame;
|
||||
|
||||
DiscoveryCommandFrame() {
|
||||
this->setProtocolHandlerId(ProtocolHandlerId::Discovery);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "DiscoveryCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Discovery
|
||||
{
|
||||
/**
|
||||
* The query context is the type of query to execute.
|
||||
*/
|
||||
enum class QueryContext : unsigned char
|
||||
{
|
||||
COMMAND_HANDLERS = 0x00,
|
||||
TOOL_NAME = 0x80,
|
||||
SERIAL_NUMBER = 0x81,
|
||||
MANUFACTURE_DATE = 0x82,
|
||||
};
|
||||
|
||||
/**
|
||||
* The Discovery protocol handler only supports one command; the Query command. This command is used to
|
||||
* query information from the device, such as device capabilities, manufacture date, serial number, etc.
|
||||
*/
|
||||
class Query: public DiscoveryCommandFrame
|
||||
{
|
||||
private:
|
||||
QueryContext context;
|
||||
|
||||
public:
|
||||
Query() : DiscoveryCommandFrame() {}
|
||||
|
||||
Query(QueryContext context) : DiscoveryCommandFrame() {
|
||||
this->setContext(context);
|
||||
}
|
||||
|
||||
void setContext(QueryContext context) {
|
||||
this->context = context;
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayload() const override {
|
||||
/*
|
||||
* The payload for the Query command consists of three bytes. A command ID (0x00), version (0x00) and a
|
||||
* query context.
|
||||
*/
|
||||
auto output = std::vector<unsigned char>(3, 0x00);
|
||||
output[2] = static_cast<unsigned char>(this->context);
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "HouseKeepingCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::HouseKeeping
|
||||
{
|
||||
/**
|
||||
* The End Session command ends the active session with the tool.
|
||||
*/
|
||||
class EndSession: public HouseKeepingCommandFrame
|
||||
{
|
||||
private:
|
||||
void init() {
|
||||
/*
|
||||
* The payload for the End Session command consists of three bytes. A command ID byte (0x11), a
|
||||
* version byte (0x00) and a reset flag (0x00 for no reset, 0x01 for tool reset).
|
||||
*/
|
||||
auto payload = std::vector<unsigned char>(3);
|
||||
payload[0] = 0x11;
|
||||
payload[1] = 0x00;
|
||||
payload[2] = 0x00;
|
||||
this->setPayload(payload);
|
||||
}
|
||||
|
||||
public:
|
||||
EndSession() : HouseKeepingCommandFrame() {
|
||||
this->init();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "../AvrCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::HouseKeeping
|
||||
{
|
||||
enum class ResponseId : unsigned char
|
||||
{
|
||||
OK = 0x80,
|
||||
LIST = 0x81,
|
||||
DATA = 0x84,
|
||||
FAILED = 0xA0,
|
||||
FAILED_WITH_DATA = 0xA1
|
||||
};
|
||||
|
||||
inline bool operator==(unsigned char rawId, ResponseId id) {
|
||||
return static_cast<unsigned char>(id) == rawId;
|
||||
}
|
||||
|
||||
inline bool operator==(ResponseId id, unsigned char rawId) {
|
||||
return rawId == id;
|
||||
}
|
||||
|
||||
class HouseKeepingCommandFrame: public AvrCommandFrame
|
||||
{
|
||||
public:
|
||||
HouseKeepingCommandFrame() {
|
||||
this->setProtocolHandlerId(ProtocolHandlerId::HouseKeeping);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "HouseKeepingCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::HouseKeeping
|
||||
{
|
||||
/**
|
||||
* The Start Session command begins a session with the tool.
|
||||
*/
|
||||
class StartSession: public HouseKeepingCommandFrame
|
||||
{
|
||||
private:
|
||||
void init() {
|
||||
/*
|
||||
* The payload for the Start Session command consists of two bytes. A command ID byte (0x10) and a
|
||||
* version byte (0x00).
|
||||
*/
|
||||
auto payload = std::vector<unsigned char>(2);
|
||||
payload[0] = 0x10;
|
||||
payload[1] = 0x00;
|
||||
this->setPayload(payload);
|
||||
}
|
||||
|
||||
public:
|
||||
StartSession() : HouseKeepingCommandFrame() {
|
||||
this->init();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,919 @@
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
#include <math.h>
|
||||
|
||||
#include "EdbgAvr8Interface.hpp"
|
||||
#include "src/Exceptions/InvalidConfig.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Exceptions/Avr8CommandFailure.hpp"
|
||||
|
||||
// Command frames
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/SetParameter.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/GetParameter.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/ActivatePhysical.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/DeactivatePhysical.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/Attach.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/Detach.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/Stop.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/Step.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/Run.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/RunTo.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/GetDeviceId.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/Reset.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/ReadMemory.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/WriteMemory.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/GetProgramCounter.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/SetProgramCounter.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/DisableDebugWire.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/SetSoftwareBreakpoints.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/SetXmegaSoftwareBreakpoint.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/ClearAllSoftwareBreakpoints.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AVR8Generic/ClearSoftwareBreakpoints.hpp"
|
||||
|
||||
// AVR events
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Events/AVR8Generic/BreakEvent.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
|
||||
using namespace Bloom::Exceptions;
|
||||
using Bloom::Targets::TargetState;
|
||||
using Bloom::Targets::TargetRegister;
|
||||
using Bloom::Targets::TargetRegisters;
|
||||
using Bloom::Targets::TargetRegisterType;
|
||||
|
||||
void EdbgAvr8Interface::setParameter(const Avr8EdbgParameter& parameter, const std::vector<unsigned char>& value) {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::SetParameter(parameter, value);
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Failed to set parameter on device!", response);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned char> EdbgAvr8Interface::getParameter(const Avr8EdbgParameter& parameter, std::uint8_t size) {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::GetParameter(parameter, size);
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Failed to get parameter from device!", response);
|
||||
}
|
||||
|
||||
return response.getPayloadData();
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::configure(const TargetConfig& targetConfig) {
|
||||
auto physicalInterface = targetConfig.jsonObject.find("physicalInterface")->toString().toLower().toStdString();
|
||||
|
||||
auto availablePhysicalInterfaces = EdbgAvr8Interface::physicalInterfacesByName;
|
||||
auto availableConfigVariants = EdbgAvr8Interface::configVariantsByPhysicalInterface;
|
||||
|
||||
if (physicalInterface.empty()
|
||||
|| availablePhysicalInterfaces.find(physicalInterface) == availablePhysicalInterfaces.end()
|
||||
) {
|
||||
throw InvalidConfig("Invalid physical interface config parameter for AVR8 target.");
|
||||
}
|
||||
|
||||
auto selectedPhysicalInterface = availablePhysicalInterfaces.find(physicalInterface)->second;
|
||||
|
||||
if (availableConfigVariants.find(selectedPhysicalInterface) == availableConfigVariants.end()) {
|
||||
throw InvalidConfig("Invalid config variant deduced from physical interface.");
|
||||
}
|
||||
|
||||
if (selectedPhysicalInterface == Avr8PhysicalInterface::DEBUG_WIRE) {
|
||||
Logger::warning("AVR8 debugWire interface selected - the DWEN fuse will need to be enabled");
|
||||
}
|
||||
|
||||
this->physicalInterface = selectedPhysicalInterface;
|
||||
this->configVariant = availableConfigVariants.find(selectedPhysicalInterface)->second;
|
||||
|
||||
if (this->configVariant == Avr8ConfigVariant::XMEGA) {
|
||||
// Default PDI clock to 4MHz
|
||||
this->setParameter(Avr8EdbgParameters::XMEGA_PDI_CLOCK, static_cast<std::uint16_t>(0x0FA0));
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::setTargetParameters(const Avr8Bit::TargetParameters& config) {
|
||||
this->targetParameters = config;
|
||||
|
||||
if (!config.stackPointerRegisterStartAddress.has_value()) {
|
||||
throw Exception("Failed to find stack pointer register start address");
|
||||
}
|
||||
|
||||
if (!config.stackPointerRegisterSize.has_value()) {
|
||||
throw Exception("Failed to find stack pointer register size");
|
||||
}
|
||||
|
||||
if (!config.statusRegisterStartAddress.has_value()) {
|
||||
throw Exception("Failed to find status register start address");
|
||||
}
|
||||
|
||||
if (!config.statusRegisterSize.has_value()) {
|
||||
throw Exception("Failed to find status register size");
|
||||
}
|
||||
|
||||
if (this->configVariant == Avr8ConfigVariant::XMEGA) {
|
||||
if (!config.appSectionPdiOffset.has_value()) {
|
||||
throw Exception("Missing required parameter: APPL_BASE_ADDR");
|
||||
}
|
||||
|
||||
if (!config.bootSectionPdiOffset.has_value()) {
|
||||
throw Exception("Missing required parameter: BOOT_BASE_ADDR");
|
||||
}
|
||||
|
||||
if (!config.bootSectionSize.has_value()) {
|
||||
throw Exception("Missing required parameter: BOOT_BYTES");
|
||||
}
|
||||
|
||||
if (!config.flashSize.has_value()) {
|
||||
throw Exception("Missing required parameter: APPLICATION_BYTES");
|
||||
}
|
||||
|
||||
if (!config.eepromPdiOffset.has_value()) {
|
||||
throw Exception("Missing required parameter: EEPROM_BASE_ADDR");
|
||||
}
|
||||
|
||||
if (!config.fuseRegistersPdiOffset.has_value()) {
|
||||
throw Exception("Missing required parameter: FUSE_BASE_ADDR");
|
||||
}
|
||||
|
||||
if (!config.lockRegistersPdiOffset.has_value()) {
|
||||
throw Exception("Missing required parameter: LOCKBIT_BASE_ADDR");
|
||||
}
|
||||
|
||||
if (!config.userSignaturesPdiOffset.has_value()) {
|
||||
throw Exception("Missing required parameter: USER_SIGN_BASE_ADDR");
|
||||
}
|
||||
|
||||
if (!config.productSignaturesPdiOffset.has_value()) {
|
||||
throw Exception("Missing required parameter: PROD_SIGN_BASE_ADDR");
|
||||
}
|
||||
|
||||
if (!config.ramPdiOffset.has_value()) {
|
||||
throw Exception("Missing required parameter: DATA_BASE_ADDR");
|
||||
}
|
||||
|
||||
if (!config.flashPageSize.has_value()) {
|
||||
throw Exception("Missing required parameter: FLASH_PAGE_BYTES");
|
||||
}
|
||||
|
||||
if (!config.eepromSize.has_value()) {
|
||||
throw Exception("Missing required parameter: EEPROM_SIZE");
|
||||
}
|
||||
|
||||
if (!config.eepromPageSize.has_value()) {
|
||||
throw Exception("Missing required parameter: EEPROM_PAGE_SIZE");
|
||||
}
|
||||
|
||||
Logger::debug("Setting APPL_BASE_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_APPL_BASE_ADDR, config.appSectionPdiOffset.value());
|
||||
|
||||
Logger::debug("Setting BOOT_BASE_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_BOOT_BASE_ADDR, config.bootSectionPdiOffset.value());
|
||||
|
||||
Logger::debug("Setting EEPROM_BASE_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_EEPROM_BASE_ADDR, config.eepromPdiOffset.value());
|
||||
|
||||
Logger::debug("Setting FUSE_BASE_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_FUSE_BASE_ADDR, config.fuseRegistersPdiOffset.value());
|
||||
|
||||
Logger::debug("Setting LOCKBIT_BASE_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_LOCKBIT_BASE_ADDR, config.lockRegistersPdiOffset.value());
|
||||
|
||||
Logger::debug("Setting USER_SIGN_BASE_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_USER_SIGN_BASE_ADDR, config.userSignaturesPdiOffset.value());
|
||||
|
||||
Logger::debug("Setting PROD_SIGN_BASE_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_PROD_SIGN_BASE_ADDR, config.productSignaturesPdiOffset.value());
|
||||
|
||||
Logger::debug("Setting DATA_BASE_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_DATA_BASE_ADDR, config.ramPdiOffset.value());
|
||||
|
||||
Logger::debug("Setting APPLICATION_BYTES AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_APPLICATION_BYTES, config.flashSize.value());
|
||||
|
||||
Logger::debug("Setting BOOT_BYTES AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_BOOT_BYTES, config.bootSectionSize.value());
|
||||
|
||||
Logger::debug("Setting FLASH_PAGE_BYTES AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_FLASH_PAGE_BYTES, config.flashPageSize.value());
|
||||
|
||||
Logger::debug("Setting EEPROM_SIZE AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_EEPROM_SIZE, config.eepromSize.value());
|
||||
|
||||
Logger::debug("Setting EEPROM_PAGE_SIZE AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_EEPROM_PAGE_SIZE, static_cast<unsigned char>(config.eepromPageSize.value()));
|
||||
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_NVM_BASE, static_cast<std::uint16_t>(0x01c0));
|
||||
// this->setParameter(Avr8EdbgParameters::DEVICE_XMEGA_SIGNATURE_OFFSET, static_cast<std::uint16_t>(0x0090));
|
||||
|
||||
} else {
|
||||
if (config.flashPageSize.has_value()) {
|
||||
Logger::debug("Setting DEVICE_FLASH_PAGE_SIZE AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_FLASH_PAGE_SIZE, config.flashPageSize.value());
|
||||
}
|
||||
|
||||
if (config.flashSize.has_value()) {
|
||||
Logger::debug("Setting DEVICE_FLASH_SIZE AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_FLASH_SIZE, config.flashSize.value());
|
||||
}
|
||||
|
||||
if (config.flashStartAddress.has_value()) {
|
||||
Logger::debug("Setting DEVICE_FLASH_BASE AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_FLASH_BASE, config.flashStartAddress.value());
|
||||
}
|
||||
|
||||
if (config.ramStartAddress.has_value()) {
|
||||
Logger::debug("Setting DEVICE_SRAM_START AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_SRAM_START, config.ramStartAddress.value());
|
||||
}
|
||||
|
||||
if (config.eepromSize.has_value()) {
|
||||
Logger::debug("Setting DEVICE_EEPROM_SIZE AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_EEPROM_SIZE, config.eepromSize.value());
|
||||
}
|
||||
|
||||
if (config.eepromPageSize.has_value()) {
|
||||
Logger::debug("Setting DEVICE_EEPROM_PAGE_SIZE AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_EEPROM_PAGE_SIZE, config.eepromPageSize.value());
|
||||
}
|
||||
|
||||
if (config.ocdRevision.has_value()) {
|
||||
Logger::debug("Setting DEVICE_OCD_REVISION AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_OCD_REVISION, config.ocdRevision.value());
|
||||
}
|
||||
|
||||
if (config.ocdDataRegister.has_value()) {
|
||||
Logger::debug("Setting DEVICE_OCD_DATA_REGISTER AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_OCD_DATA_REGISTER, config.ocdDataRegister.value());
|
||||
}
|
||||
|
||||
if (config.spmcsRegisterStartAddress.has_value()) {
|
||||
Logger::debug("Setting DEVICE_SPMCR_REGISTER AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_SPMCR_REGISTER, config.spmcsRegisterStartAddress.value());
|
||||
}
|
||||
|
||||
if (config.osccalAddress.has_value()) {
|
||||
Logger::debug("Setting DEVICE_OSCCAL_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_OSCCAL_ADDR, config.osccalAddress.value());
|
||||
}
|
||||
|
||||
if (config.eepromAddressRegisterLow.has_value()) {
|
||||
Logger::debug("Setting DEVICE_EEARL_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_EEARL_ADDR, config.eepromAddressRegisterLow.value());
|
||||
}
|
||||
|
||||
if (config.eepromAddressRegisterHigh.has_value()) {
|
||||
Logger::debug("Setting DEVICE_EEARH_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_EEARH_ADDR, config.eepromAddressRegisterHigh.value());
|
||||
}
|
||||
|
||||
if (config.eepromControlRegisterAddress.has_value()) {
|
||||
Logger::debug("Setting DEVICE_EECR_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_EECR_ADDR, config.eepromControlRegisterAddress.value());
|
||||
}
|
||||
|
||||
if (config.eepromDataRegisterAddress.has_value()) {
|
||||
Logger::debug("Setting DEVICE_EEDR_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_EEDR_ADDR, config.eepromDataRegisterAddress.value());
|
||||
}
|
||||
|
||||
if (config.bootSectionStartAddress.has_value()) {
|
||||
Logger::debug("Setting DEVICE_BOOT_START_ADDR AVR8 parameter");
|
||||
this->setParameter(Avr8EdbgParameters::DEVICE_BOOT_START_ADDR, config.bootSectionStartAddress.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::init() {
|
||||
this->setParameter(
|
||||
Avr8EdbgParameters::CONFIG_VARIANT,
|
||||
static_cast<unsigned char>(this->configVariant)
|
||||
);
|
||||
|
||||
this->setParameter(
|
||||
Avr8EdbgParameters::CONFIG_FUNCTION,
|
||||
static_cast<unsigned char>(this->configFunction)
|
||||
);
|
||||
|
||||
this->setParameter(
|
||||
Avr8EdbgParameters::PHYSICAL_INTERFACE,
|
||||
static_cast<unsigned char>(this->physicalInterface)
|
||||
);
|
||||
}
|
||||
|
||||
std::unique_ptr<AvrEvent> EdbgAvr8Interface::getAvrEvent() {
|
||||
auto event = this->edbgInterface.requestAvrEvent();
|
||||
|
||||
if (!event.has_value()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
switch (event->getEventId()) {
|
||||
case AvrEventId::AVR8_BREAK_EVENT: {
|
||||
// Break event
|
||||
return std::make_unique<BreakEvent>(event.value());
|
||||
}
|
||||
default: {
|
||||
/*
|
||||
* TODO: This isn't very nice as we're performing an unnecessary copy. Maybe requestAvrEvents should
|
||||
* return a unique_ptr instead?
|
||||
*/
|
||||
return std::make_unique<AvrEvent>(event.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::clearEvents() {
|
||||
while (this->getAvrEvent() != nullptr) {}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::activatePhysical(bool applyExternalReset) {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::ActivatePhysical(applyExternalReset);
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
if (!applyExternalReset) {
|
||||
// Try again with external reset applied
|
||||
Logger::debug("Failed to activate physical interface on AVR8 target - retrying with external reset applied.");
|
||||
return this->activatePhysical(true);
|
||||
} else {
|
||||
throw Exception("Activate physical interface command failed");
|
||||
}
|
||||
}
|
||||
|
||||
this->physicalInterfaceActivated = true;
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::deactivatePhysical() {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::DeactivatePhysical();
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("deactivate physical interface on AVR8 target command failed", response);
|
||||
}
|
||||
|
||||
this->physicalInterfaceActivated = false;
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::attach() {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::Attach(true);
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Attach AVR8 target command failed", response);
|
||||
}
|
||||
|
||||
this->targetAttached = true;
|
||||
|
||||
// Wait for stopped event
|
||||
this->waitForStoppedEvent();
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::detach() {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::Detach();
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Detach AVR8 target command failed", response);
|
||||
}
|
||||
|
||||
this->targetAttached = false;
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::activate() {
|
||||
if (!this->physicalInterfaceActivated) {
|
||||
this->activatePhysical();
|
||||
}
|
||||
|
||||
if (!this->targetAttached) {
|
||||
this->attach();
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::deactivate() {
|
||||
if (this->targetAttached) {
|
||||
if (this->physicalInterface == Avr8PhysicalInterface::DEBUG_WIRE && this->disableDebugWireOnDeactivate) {
|
||||
try {
|
||||
this->disableDebugWire();
|
||||
Logger::warning("Successfully disabled debugWire on the AVR8 target - this is only temporary - "
|
||||
"the debugWire module has lost control of the RESET pin. Bloom will no longer be able to interface "
|
||||
"with the target until the next power cycle.");
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
// Failing to disable debugWire should never prevent us from proceeding with target deactivation.
|
||||
Logger::error(exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
this->stop();
|
||||
this->clearAllBreakpoints();
|
||||
this->run();
|
||||
|
||||
this->detach();
|
||||
}
|
||||
|
||||
if (this->physicalInterfaceActivated) {
|
||||
this->deactivatePhysical();
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::reset() {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::Reset();
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Reset AVR8 target command failed", response);
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::stop() {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::Stop();
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Stop AVR8 target command failed", response);
|
||||
}
|
||||
|
||||
if (this->getTargetState() == TargetState::RUNNING) {
|
||||
this->waitForStoppedEvent();
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::step() {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::Step();
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Step AVR8 target command failed", response);
|
||||
}
|
||||
|
||||
this->targetState = TargetState::RUNNING;
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::waitForStoppedEvent() {
|
||||
auto breakEvent = this->waitForAvrEvent<BreakEvent>();
|
||||
|
||||
if (breakEvent == nullptr) {
|
||||
throw Exception("Failed to receive break event for AVR8 target.");
|
||||
}
|
||||
|
||||
this->targetState = TargetState::STOPPED;
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::disableDebugWire() {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::DisableDebugWire();
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Disable debugWire AVR8 target command failed", response);
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::run() {
|
||||
this->clearEvents();
|
||||
auto commandFrame = CommandFrames::Avr8Generic::Run();
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Run AVR8 command failed", response);
|
||||
}
|
||||
|
||||
this->targetState = TargetState::RUNNING;
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::runTo(std::uint32_t address) {
|
||||
this->clearEvents();
|
||||
Logger::debug("Running to address: " + std::to_string(address));
|
||||
auto commandFrame = CommandFrames::Avr8Generic::RunTo(address);
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Run-to AVR8 command failed", response);
|
||||
}
|
||||
|
||||
this->targetState = TargetState::RUNNING;
|
||||
}
|
||||
|
||||
std::uint32_t EdbgAvr8Interface::getProgramCounter() {
|
||||
if (this->targetState != TargetState::STOPPED) {
|
||||
this->stop();
|
||||
}
|
||||
|
||||
auto commandFrame = CommandFrames::Avr8Generic::GetProgramCounter();
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Get AVR8 program counter command failed", response);
|
||||
}
|
||||
|
||||
return response.extractProgramCounter();
|
||||
}
|
||||
|
||||
TargetRegister EdbgAvr8Interface::getStackPointerRegister() {
|
||||
return TargetRegister(TargetRegisterType::STACK_POINTER, this->readMemory(
|
||||
Avr8MemoryType::SRAM,
|
||||
this->targetParameters.stackPointerRegisterStartAddress.value(),
|
||||
this->targetParameters.stackPointerRegisterSize.value()
|
||||
));
|
||||
}
|
||||
|
||||
TargetRegister EdbgAvr8Interface::getStatusRegister() {
|
||||
return TargetRegister(TargetRegisterType::STATUS_REGISTER, this->readMemory(
|
||||
Avr8MemoryType::SRAM,
|
||||
this->targetParameters.statusRegisterStartAddress.value(),
|
||||
this->targetParameters.statusRegisterSize.value()
|
||||
));
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::setStackPointerRegister(const TargetRegister& stackPointerRegister) {
|
||||
auto maximumStackPointerRegisterSize = this->targetParameters.stackPointerRegisterSize.value();
|
||||
auto registerValue = stackPointerRegister.value;
|
||||
|
||||
if (registerValue.size() > maximumStackPointerRegisterSize) {
|
||||
throw Exception("Provided stack pointer register value exceeds maximum size.");
|
||||
|
||||
} else if (registerValue.size() < maximumStackPointerRegisterSize) {
|
||||
// Fill the missing most-significant bytes with 0x00
|
||||
registerValue.insert(registerValue.begin(), maximumStackPointerRegisterSize - registerValue.size(), 0x00);
|
||||
}
|
||||
|
||||
this->writeMemory(
|
||||
Avr8MemoryType::SRAM,
|
||||
this->targetParameters.stackPointerRegisterStartAddress.value(),
|
||||
registerValue
|
||||
);
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::setStatusRegister(const TargetRegister& statusRegister) {
|
||||
auto maximumStatusRegisterSize = this->targetParameters.statusRegisterSize.value();
|
||||
auto registerValue = statusRegister.value;
|
||||
|
||||
if (registerValue.size() > maximumStatusRegisterSize) {
|
||||
throw Exception("Provided status register value exceeds maximum size.");
|
||||
|
||||
} else if (registerValue.size() < maximumStatusRegisterSize) {
|
||||
// Fill the missing most-significant bytes with 0x00
|
||||
registerValue.insert(registerValue.begin(), maximumStatusRegisterSize - registerValue.size(), 0x00);
|
||||
}
|
||||
|
||||
this->writeMemory(
|
||||
Avr8MemoryType::SRAM,
|
||||
this->targetParameters.statusRegisterStartAddress.value(),
|
||||
registerValue
|
||||
);
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::setProgramCounter(std::uint32_t programCounter) {
|
||||
if (this->targetState != TargetState::STOPPED) {
|
||||
this->stop();
|
||||
}
|
||||
|
||||
/*
|
||||
* The program counter will be given in byte address form, but the EDBG tool will be expecting it in word
|
||||
* address (16-bit) form. This is why we divide it by 2.
|
||||
*/
|
||||
auto commandFrame = CommandFrames::Avr8Generic::SetProgramCounter(programCounter / 2);
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Set program counter command failed", response);
|
||||
}
|
||||
}
|
||||
|
||||
TargetSignature EdbgAvr8Interface::getDeviceId() {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::GetDeviceId();
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Get device ID command failed", response);
|
||||
}
|
||||
|
||||
return response.extractSignature(this->physicalInterface);
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::setBreakpoint(std::uint32_t address) {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::SetSoftwareBreakpoints({address});
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Set software breakpoint command failed", response);
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::clearBreakpoint(std::uint32_t address) {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::ClearSoftwareBreakpoints({address});
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Clear AVR8 software breakpoint command failed", response);
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::clearAllBreakpoints() {
|
||||
auto commandFrame = CommandFrames::Avr8Generic::ClearAllSoftwareBreakpoints();
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Clear all AVR8 software breakpoints command failed", response);
|
||||
}
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::refreshTargetState() {
|
||||
auto avrEvent = this->getAvrEvent();
|
||||
|
||||
if (avrEvent != nullptr && avrEvent->getEventId() == AvrEventId::AVR8_BREAK_EVENT) {
|
||||
auto breakEvent = dynamic_cast<BreakEvent*>(avrEvent.get());
|
||||
|
||||
if (breakEvent == nullptr) {
|
||||
throw Exception("Failed to process AVR8 break event");
|
||||
}
|
||||
|
||||
this->targetState = TargetState::STOPPED;
|
||||
return;
|
||||
}
|
||||
|
||||
this->targetState = TargetState::RUNNING;
|
||||
}
|
||||
|
||||
TargetState EdbgAvr8Interface::getTargetState() {
|
||||
/*
|
||||
* We are not informed when a target goes from a stopped state to a running state, so there is no need
|
||||
* to query the tool when we already know the target has stopped.
|
||||
*
|
||||
* This means we have to rely on the assumption that the target cannot enter a running state without
|
||||
* our instruction.
|
||||
*/
|
||||
if (this->targetState != TargetState::STOPPED) {
|
||||
this->refreshTargetState();
|
||||
}
|
||||
|
||||
return this->targetState;
|
||||
}
|
||||
|
||||
TargetMemoryBuffer EdbgAvr8Interface::readMemory(Avr8MemoryType type, std::uint32_t address, std::uint32_t bytes) {
|
||||
if (type == Avr8MemoryType::FLASH_PAGE && this->targetParameters.flashPageSize.value_or(0) > 0) {
|
||||
// Flash reads must be done in pages
|
||||
auto pageSize = this->targetParameters.flashPageSize.value();
|
||||
|
||||
if ((bytes % pageSize) != 0 || (address % pageSize) != 0) {
|
||||
/*
|
||||
* The number of bytes to read and/or the start address are not aligned.
|
||||
*
|
||||
* Align both and call this function again.
|
||||
*/
|
||||
auto alignedAddress = address;
|
||||
auto alignedBytesToRead = bytes;
|
||||
|
||||
if ((bytes % pageSize) != 0) {
|
||||
auto pagesRequired = static_cast<std::uint32_t>(std::ceil(
|
||||
static_cast<float>(bytes) / static_cast<float>(pageSize)
|
||||
));
|
||||
|
||||
alignedBytesToRead = (pagesRequired * pageSize);
|
||||
}
|
||||
|
||||
if ((address % pageSize) != 0) {
|
||||
alignedAddress = static_cast<std::uint32_t>(std::floor(
|
||||
static_cast<float>(address) / static_cast<float>(pageSize)
|
||||
) * pageSize);
|
||||
|
||||
/*
|
||||
* Given that we've pushed the start address back, this must be accounted for in the number of
|
||||
* bytes to read. We'll need to include the difference as those are the bytes we're actually
|
||||
* interested in.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* Given: page size = 4
|
||||
* Given: dummy memory (each character represents one byte, with first byte at 0x00) = aaaabbbbccccdddd
|
||||
* Given: requested start address = 0x05
|
||||
* Given: requested bytes to read = 4
|
||||
*
|
||||
* The start address (0x05) would be:
|
||||
* aaaabbbbccccdddd
|
||||
* ^
|
||||
* Because only 4 bytes were requested, starting at address 0x05, we're only interested in the bytes
|
||||
* at addresses 0x05, 0x06, 0x07 and 0x08 (that's bytes bbbc).
|
||||
*
|
||||
* But the start address isn't aligned, so we need to align it by pushing it back to the beginning
|
||||
* of the page (so we'd set it to 0x04, for this example), which is what we do above, when setting
|
||||
* alignedAddress:
|
||||
* aaaabbbbccccdddd
|
||||
* ^
|
||||
* But now we'll only be reading 4 bytes from start address 0x04, meaning we won't be reading
|
||||
* that 4th byte (0x08). So we need to account for this by adding the difference of the requested start
|
||||
* address and the aligned start address to the number of bytes to read, to ensure that we're reading
|
||||
* all of the bytes that we're interested in. But this will throw off the aligned bytes!! So we need
|
||||
* to also account for this by aligning the additional bytes before adding them to alignedBytesToRead.
|
||||
*
|
||||
* However, we could simply get away with just adding the bytes without aligning
|
||||
* them (alignedBytesToRead += (address - alignedAddress);), as the subsequent recursive call will
|
||||
* align them for us, but it will result in an unnecessary recursion, so we'll just align the
|
||||
* additional bytes here.
|
||||
*/
|
||||
if ((address - alignedAddress) > (alignedBytesToRead - bytes)) {
|
||||
alignedBytesToRead += static_cast<std::uint32_t>(std::ceil(
|
||||
static_cast<float>(address - alignedAddress) / static_cast<float>(pageSize)
|
||||
)) * pageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that the start address and bytes to read have been aligned, we can simply invoke this function
|
||||
* for a second time, with the aligned values. Then, return the requested data and discard the rest.
|
||||
*/
|
||||
auto memoryBuffer = this->readMemory(type, alignedAddress, alignedBytesToRead);
|
||||
return TargetMemoryBuffer(
|
||||
memoryBuffer.begin() + (address - alignedAddress),
|
||||
memoryBuffer.begin() + (address - alignedAddress) + bytes
|
||||
);
|
||||
}
|
||||
|
||||
// We can only read one flash page at a time.
|
||||
if (bytes > pageSize) {
|
||||
// bytes should always be a multiple of pageSize (given the code above)
|
||||
assert(bytes % pageSize == 0);
|
||||
int pagesRequired = static_cast<int>(bytes / pageSize);
|
||||
TargetMemoryBuffer memoryBuffer;
|
||||
|
||||
for (auto i = 1; i <= pagesRequired; i++) {
|
||||
auto pageBuffer = this->readMemory(type, address + (pageSize * i), pageSize);
|
||||
memoryBuffer.insert(memoryBuffer.end(), pageBuffer.begin(), pageBuffer.end());
|
||||
}
|
||||
|
||||
return memoryBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* EDBG AVR8 debug tools behave in a really weird way when responding with more than two packets
|
||||
* for a single read memory command. The data they return in this case appears to be of little use.
|
||||
*
|
||||
* To address this, we make sure we only issue read memory commands that will result in no more than two
|
||||
* response packets. For calls that require more than this, we simply split them into numerous calls.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The subtraction of 20 bytes here is just to account for any other bytes included in the response
|
||||
* that isn't actually the memory data (like the command ID, version bytes, etc). I could have sought the
|
||||
* actual value but who has the time. It won't exceed 20 bytes. Bite me.
|
||||
*/
|
||||
auto singlePacketSize = static_cast<std::uint32_t>(this->edbgInterface.getUsbHidInputReportSize() - 20);
|
||||
auto totalResponsePackets = std::ceil(static_cast<float>(bytes) / static_cast<float>(singlePacketSize));
|
||||
auto totalReadsRequired = std::ceil(static_cast<float>(totalResponsePackets) / 2);
|
||||
|
||||
if (totalResponsePackets > 2) {
|
||||
/*
|
||||
* This call to readMemory() will result in more than two response packets, so split it into multiple calls
|
||||
* that will result in no more than two response packets per call.
|
||||
*/
|
||||
auto output = std::vector<unsigned char>();
|
||||
|
||||
for (float i = 1; i <= totalReadsRequired; i++) {
|
||||
auto bytesToRead = static_cast<std::uint32_t>((bytes - output.size()) > (singlePacketSize * 2) ?
|
||||
(singlePacketSize * 2) : bytes - output.size());
|
||||
auto data = this->readMemory(type, static_cast<std::uint32_t>(address + output.size()), bytesToRead);
|
||||
output.insert(output.end(), data.begin(), data.end());
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
auto commandFrame = CommandFrames::Avr8Generic::ReadMemory();
|
||||
commandFrame.setType(type);
|
||||
commandFrame.setAddress(address);
|
||||
commandFrame.setBytes(bytes);
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Read memory AVR8 from target command failed", response);
|
||||
}
|
||||
|
||||
return response.getMemoryBuffer();
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::writeMemory(Avr8MemoryType type, std::uint32_t address, TargetMemoryBuffer buffer) {
|
||||
if (type == Avr8MemoryType::FLASH_PAGE) {
|
||||
// TODO: Implement support for writing to flash
|
||||
throw Exception("Cannot write to flash");
|
||||
}
|
||||
|
||||
auto commandFrame = CommandFrames::Avr8Generic::WriteMemory();
|
||||
commandFrame.setType(type);
|
||||
commandFrame.setAddress(address);
|
||||
commandFrame.setBuffer(buffer);
|
||||
|
||||
auto response = this->edbgInterface.sendAvrCommandFrameAndWaitForResponseFrame(commandFrame);
|
||||
if (response.getResponseId() == Avr8ResponseId::FAILED) {
|
||||
throw Avr8CommandFailure("Write memory AVR8 from target command failed", response);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TargetRegisters EdbgAvr8Interface::readGeneralPurposeRegisters(std::set<std::size_t> registerIds) {
|
||||
auto output = TargetRegisters();
|
||||
|
||||
auto registers = this->readMemory(
|
||||
this->targetParameters.family == Family::XMEGA ? Avr8MemoryType::REGISTER_FILE : Avr8MemoryType::SRAM,
|
||||
this->targetParameters.gpRegisterStartAddress.value_or(0x00),
|
||||
this->targetParameters.gpRegisterSize.value_or(32)
|
||||
);
|
||||
|
||||
for (std::size_t registerIndex = 0; registerIndex < registers.size(); registerIndex++) {
|
||||
if (!registerIds.empty() && registerIds.find(registerIndex) == registerIds.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
output.push_back(TargetRegister(registerIndex, {registers[registerIndex]}));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::writeGeneralPurposeRegisters(const TargetRegisters& registers) {
|
||||
auto gpRegisterSize = this->targetParameters.gpRegisterSize.value_or(32);
|
||||
auto gpStartAddress = this->targetParameters.gpRegisterStartAddress.value_or(0x00);
|
||||
|
||||
for (const auto& gpRegister : registers) {
|
||||
auto descriptor = gpRegister.descriptor;
|
||||
if (gpRegister.descriptor.type != TargetRegisterType::GENERAL_PURPOSE_REGISTER) {
|
||||
// We are only to update GP registers here
|
||||
throw Exception("Cannot write non GP register");
|
||||
}
|
||||
|
||||
if (!descriptor.id.has_value()) {
|
||||
throw Exception("Missing GP register ID");
|
||||
|
||||
} else if ((descriptor.id.value() + 1) > gpRegisterSize || (descriptor.id.value() + 1) < 0) {
|
||||
throw Exception("Invalid GP register ID: " + std::to_string(descriptor.id.value()));
|
||||
}
|
||||
|
||||
if (gpRegister.value.size() != 1) {
|
||||
throw Exception("Invalid GP register value size");
|
||||
}
|
||||
|
||||
// TODO: This can be inefficient when updating many registers, maybe do something a little smarter here.
|
||||
this->writeMemory(
|
||||
this->configVariant == Avr8ConfigVariant::XMEGA ? Avr8MemoryType::REGISTER_FILE : Avr8MemoryType::SRAM,
|
||||
static_cast<std::uint32_t>(gpStartAddress + descriptor.id.value()),
|
||||
gpRegister.value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TargetMemoryBuffer EdbgAvr8Interface::readMemory(TargetMemoryType memoryType, std::uint32_t startAddress, std::uint32_t bytes) {
|
||||
auto avr8MemoryType = Avr8MemoryType::SRAM;
|
||||
|
||||
switch (memoryType) {
|
||||
case TargetMemoryType::RAM: {
|
||||
avr8MemoryType = Avr8MemoryType::SRAM;
|
||||
break;
|
||||
}
|
||||
case TargetMemoryType::FLASH: {
|
||||
if (this->configVariant == Avr8ConfigVariant::DEBUG_WIRE) {
|
||||
avr8MemoryType = Avr8MemoryType::FLASH_PAGE;
|
||||
|
||||
} else if (this->configVariant == Avr8ConfigVariant::XMEGA || this->configVariant == Avr8ConfigVariant::UPDI) {
|
||||
avr8MemoryType = Avr8MemoryType::APPL_FLASH;
|
||||
|
||||
} else {
|
||||
avr8MemoryType = Avr8MemoryType::SPM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TargetMemoryType::EEPROM: {
|
||||
avr8MemoryType = Avr8MemoryType::EEPROM;
|
||||
}
|
||||
}
|
||||
|
||||
return this->readMemory(avr8MemoryType, startAddress, bytes);
|
||||
}
|
||||
|
||||
void EdbgAvr8Interface::writeMemory(TargetMemoryType memoryType, std::uint32_t startAddress, const TargetMemoryBuffer& buffer) {
|
||||
auto avr8MemoryType = Avr8MemoryType::SRAM;
|
||||
|
||||
switch (memoryType) {
|
||||
case TargetMemoryType::RAM: {
|
||||
avr8MemoryType = Avr8MemoryType::SRAM;
|
||||
break;
|
||||
}
|
||||
case TargetMemoryType::FLASH: {
|
||||
if (this->configVariant == Avr8ConfigVariant::DEBUG_WIRE) {
|
||||
avr8MemoryType = Avr8MemoryType::FLASH_PAGE;
|
||||
|
||||
} else if (this->configVariant == Avr8ConfigVariant::MEGAJTAG) {
|
||||
avr8MemoryType = Avr8MemoryType::FLASH_PAGE;
|
||||
// TODO: Enable programming mode
|
||||
|
||||
} else if (this->configVariant == Avr8ConfigVariant::XMEGA || this->configVariant == Avr8ConfigVariant::UPDI) {
|
||||
avr8MemoryType = Avr8MemoryType::APPL_FLASH;
|
||||
|
||||
} else {
|
||||
avr8MemoryType = Avr8MemoryType::SPM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TargetMemoryType::EEPROM: {
|
||||
avr8MemoryType = Avr8MemoryType::EEPROM;
|
||||
}
|
||||
}
|
||||
|
||||
return this->writeMemory(avr8MemoryType, startAddress, buffer);
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <cassert>
|
||||
|
||||
#include "src/DebugToolDrivers/TargetInterfaces/Microchip/AVR/AVR8/Avr8Interface.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Avr8Generic.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/EdbgInterface.hpp"
|
||||
#include "src/Targets/Microchip/AVR/Target.hpp"
|
||||
#include "src/Targets/Microchip/AVR/AVR8/Family.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
|
||||
{
|
||||
using namespace DebugToolDrivers;
|
||||
using namespace Targets;
|
||||
using namespace Targets::Microchip::Avr;
|
||||
using namespace Events;
|
||||
|
||||
using Protocols::CmsisDap::Edbg::EdbgInterface;
|
||||
using Targets::Microchip::Avr::Avr8Bit::Family;
|
||||
using Targets::TargetRegister;
|
||||
using Targets::TargetRegisterMap;
|
||||
using Targets::TargetMemoryBuffer;
|
||||
|
||||
inline bool operator==(unsigned char rawId, Avr8ResponseId id) {
|
||||
return static_cast<unsigned char>(id) == rawId;
|
||||
}
|
||||
|
||||
inline bool operator==(Avr8ResponseId id, unsigned char rawId) {
|
||||
return rawId == id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The EdbgAvr8Interface implements the AVR8 Generic EDBG/CMSIS-DAP protocol, as an Avr8Interface.
|
||||
*
|
||||
* See the "AVR8 Generic Protocol" section in the DS50002630A document by Microchip, for more information on the
|
||||
* protocol.
|
||||
*
|
||||
* This implementation should work with any Microchip EDBG based CMSIS-DAP debug tool (such as the Atmel-ICE,
|
||||
* Power Debugger and the MPLAB SNAP debugger (in "AVR mode")).
|
||||
*/
|
||||
class EdbgAvr8Interface: public Avr8Interface
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The AVR8 Generic protocol is a sub-protocol of the EDBG AVR protocol, which is served via CMSIS-DAP vendor
|
||||
* commands.
|
||||
*
|
||||
* Every EDBG based debug tool that utilises this implementation must provide access to its EDBG interface.
|
||||
*/
|
||||
EdbgInterface& edbgInterface;
|
||||
|
||||
/**
|
||||
* The AVR8 Generic protocol provides two functions: Debugging and programming. The desired function must be
|
||||
* configured via the setting of the "AVR8_CONFIG_FUNCTION" parameter.
|
||||
*/
|
||||
Avr8ConfigFunction configFunction = Avr8ConfigFunction::DEBUGGING;
|
||||
|
||||
/**
|
||||
* Configuring of the AVR8 Generic protocol depends on some characteristics of the target.
|
||||
* The "AVR8_CONFIG_VARIANT" parameter allows us to determine which target parameters are required by the
|
||||
* debug tool.
|
||||
*/
|
||||
Avr8ConfigVariant configVariant;
|
||||
|
||||
/**
|
||||
* Currently, the AVR8 Generic protocol supports 4 physical interfaces: debugWire, JTAG, PDI and UPDI.
|
||||
* The desired physical interface must be selected by setting the "AVR8_PHY_PHYSICAL" parameter.
|
||||
*/
|
||||
Avr8PhysicalInterface physicalInterface;
|
||||
|
||||
/**
|
||||
* EDBG-based debug tools require target specific parameters such as memory locations, page sizes and
|
||||
* register addresses. It is the AVR8 target's responsibility to obtain the required information and pass it
|
||||
* to the Avr8Interface. See Avr8::getTargetParameters() and Avr8::postPromotionConfigure().
|
||||
*
|
||||
* For the EdbgAvr8Interface, we send the required parameters to the debug tool immediately upon receiving
|
||||
* them. See EdbgAvr8Interface::setTargetParameters().
|
||||
*/
|
||||
Avr8Bit::TargetParameters targetParameters;
|
||||
|
||||
/**
|
||||
* We keep record of the current target state for caching purposes. We'll only refresh the target state if the
|
||||
* target is running. If it has already stopped, then we assume it cannot transition to a running state without
|
||||
* an instruction from us.
|
||||
*
|
||||
* @TODO: Review this. Is the above assumption correct? Always? Explore the option of polling the target state.
|
||||
*/
|
||||
TargetState targetState = TargetState::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Upon configuration, the physical interface must be activated on the debug tool. We keep record of this to
|
||||
* assist in our decision to deactivate the physical interface, when deactivate() is called.
|
||||
*/
|
||||
bool physicalInterfaceActivated = false;
|
||||
|
||||
/**
|
||||
* As with activating the physical interface, we are required to issue the "attach" command to the debug
|
||||
* tool, in order to start a debug session in the target.
|
||||
*/
|
||||
bool targetAttached = false;
|
||||
|
||||
/**
|
||||
* Because the debugWire module requires control of the reset pin on the target, enabling this module will
|
||||
* effectively mean losing control of the reset pin. This means users won't be able to use other
|
||||
* interfaces that require access to the reset pin, such as ISP, until the debugWire module is disabled.
|
||||
*
|
||||
* The AVR8 Generic protocol provides a function for temporarily disabling the debugWire module on the target.
|
||||
* This doesn't change the DWEN fuse and its affect is only temporary - the debugWire module will be
|
||||
* reactivated upon the user cycling the power to the target.
|
||||
*
|
||||
* Bloom is able to temporarily disable the debugWire module, automatically, upon deactivating of the
|
||||
* target (which usually occurs after a debug session has ended). This allows users to program the target via
|
||||
* ISP, after they've finished a debug session. After programming the target, the user will need to cycle the
|
||||
* target power before Bloom can gain access for another debug session.
|
||||
*
|
||||
* This feature can be activated via the user's Bloom configuration.
|
||||
*
|
||||
* See disableDebugWire() method below.
|
||||
*/
|
||||
bool disableDebugWireOnDeactivate = false;
|
||||
|
||||
/**
|
||||
* Users are required to set their desired physical interface in their Bloom configuration. This would take
|
||||
* the form of a string, so we map the available options to the appropriate enums.
|
||||
*/
|
||||
static inline std::map<std::string, Avr8PhysicalInterface> physicalInterfacesByName = {
|
||||
{"debugwire", Avr8PhysicalInterface::DEBUG_WIRE},
|
||||
{"pdi", Avr8PhysicalInterface::PDI},
|
||||
// {"jtag", Avr8PhysicalInterface::JTAG}, // Disabled for now - will add support later
|
||||
// {"updi", Avr8PhysicalInterface::PDI_1W}, // Disabled for now - will add support later
|
||||
};
|
||||
|
||||
/**
|
||||
* Although users can supply the desired config variant via their Bloom configuration, this is not required.
|
||||
* This mapping allows us to determine which config variant to select, based on the selected physical
|
||||
* interface.
|
||||
*/
|
||||
static inline std::map<Avr8PhysicalInterface, Avr8ConfigVariant> configVariantsByPhysicalInterface = {
|
||||
{Avr8PhysicalInterface::DEBUG_WIRE, Avr8ConfigVariant::DEBUG_WIRE},
|
||||
{Avr8PhysicalInterface::PDI, Avr8ConfigVariant::XMEGA},
|
||||
{Avr8PhysicalInterface::JTAG, Avr8ConfigVariant::MEGAJTAG},
|
||||
{Avr8PhysicalInterface::PDI_1W, Avr8ConfigVariant::UPDI},
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets an AVR8 parameter on the debug tool. See the Avr8EdbgParameters class and protocol documentation
|
||||
* for more on available parameters.
|
||||
*
|
||||
* @param parameter
|
||||
* @param value
|
||||
*/
|
||||
void setParameter(const Avr8EdbgParameter& parameter, const std::vector<unsigned char>& value);
|
||||
|
||||
/**
|
||||
* Overload for setting parameters with single byte values.
|
||||
*
|
||||
* @param parameter
|
||||
* @param value
|
||||
*/
|
||||
void setParameter(const Avr8EdbgParameter& parameter, unsigned char value) {
|
||||
this->setParameter(parameter, std::vector<unsigned char>(1, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload for setting parameters with four byte integer values.
|
||||
*
|
||||
* @param parameter
|
||||
* @param value
|
||||
*/
|
||||
void setParameter(const Avr8EdbgParameter& parameter, std::uint32_t value) {
|
||||
auto paramValue = std::vector<unsigned char>(4);
|
||||
paramValue[0] = static_cast<unsigned char>(value);
|
||||
paramValue[1] = static_cast<unsigned char>(value >> 8);
|
||||
paramValue[2] = static_cast<unsigned char>(value >> 16);
|
||||
paramValue[3] = static_cast<unsigned char>(value >> 24);
|
||||
|
||||
this->setParameter(parameter, paramValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload for setting parameters with two byte integer values.
|
||||
*
|
||||
* @param parameter
|
||||
* @param value
|
||||
*/
|
||||
void setParameter(const Avr8EdbgParameter& parameter, std::uint16_t value) {
|
||||
auto paramValue = std::vector<unsigned char>(2);
|
||||
paramValue[0] = static_cast<unsigned char>(value);
|
||||
paramValue[1] = static_cast<unsigned char>(value >> 8);
|
||||
|
||||
this->setParameter(parameter, paramValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an AV8 parameter from the debug tool.
|
||||
*
|
||||
* @param parameter
|
||||
* @param size
|
||||
* @return
|
||||
*/
|
||||
std::vector<unsigned char> getParameter(const Avr8EdbgParameter& parameter, std::uint8_t size);
|
||||
|
||||
/**
|
||||
* Sends the "Activate Physical" command to the debug tool, activating the physical interface and thus enabling
|
||||
* communication between the debug tool and the target.
|
||||
*
|
||||
* @param applyExternalReset
|
||||
*/
|
||||
void activatePhysical(bool applyExternalReset = false);
|
||||
|
||||
/**
|
||||
* Sends the "Deactivate Physical" command to the debug tool, which will result in the connection between the
|
||||
* debug tool and the target being severed.
|
||||
*/
|
||||
void deactivatePhysical();
|
||||
|
||||
/**
|
||||
* Sends the "Attach" command to the debug tool, which starts a debug session on the target.
|
||||
*/
|
||||
void attach();
|
||||
|
||||
/**
|
||||
* Sends the "Detach" command to the debug tool, which terminates any active debug session on the target.
|
||||
*/
|
||||
void detach();
|
||||
|
||||
/**
|
||||
* Fetches any queued events belonging to the AVR8 Generic protocol (such as target break events).
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::unique_ptr<AvrEvent> getAvrEvent();
|
||||
|
||||
/**
|
||||
* Clears (discards) any queued AVR8 Generic protocol events on the debug tool.
|
||||
*/
|
||||
void clearEvents();
|
||||
|
||||
/**
|
||||
* Reads memory on the target.
|
||||
*
|
||||
* This method will handle any alignment requirements for the selected memory type.
|
||||
*
|
||||
* See the Avr8MemoryType enum for list of supported AVR8 memory types.
|
||||
*
|
||||
* @param type
|
||||
* The type of memory to access (Flash, EEPROM, SRAM, etc). See protocol documentation for more on this.
|
||||
*
|
||||
* @param address
|
||||
* The start address (byte address)
|
||||
*
|
||||
* @param bytes
|
||||
* Number of bytes to access.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
TargetMemoryBuffer readMemory(Avr8MemoryType type, std::uint32_t address, std::uint32_t bytes);
|
||||
|
||||
/**
|
||||
* Writes memory to the target.
|
||||
*
|
||||
* This method will handle any alignment requirements for the selected memory type.
|
||||
*
|
||||
* See the Avr8MemoryType enum for list of supported AVR8 memory types.
|
||||
*
|
||||
* @param type
|
||||
* @param address
|
||||
* @param buffer
|
||||
*/
|
||||
void writeMemory(Avr8MemoryType type, std::uint32_t address, TargetMemoryBuffer buffer);
|
||||
|
||||
/**
|
||||
* Fetches the current target state.
|
||||
*
|
||||
* This currently uses AVR BREAK events to determine if a target has stopped. The lack of any
|
||||
* queued BREAK events leads to the assumption that the target is still running.
|
||||
*/
|
||||
void refreshTargetState();
|
||||
|
||||
/**
|
||||
* Temporarily disables the debugWire module on the target. This does not affect the DWEN fuse. The module
|
||||
* will be reactivated upon the cycling of the target power.
|
||||
*/
|
||||
void disableDebugWire();
|
||||
|
||||
/**
|
||||
* Waits for an AVR event of a specific type.
|
||||
*
|
||||
* @tparam AvrEventType
|
||||
* Type of AVR event to wait for. See AvrEvent class for more.
|
||||
*
|
||||
* @param maximumAttempts
|
||||
* Maximum number of attempts to poll the debug tool for the expected event.
|
||||
*
|
||||
* @return
|
||||
* If an event is found before maximumAttempts is reached, the event will be returned. Otherwise a nullptr
|
||||
* will be returned.
|
||||
*/
|
||||
template <class AvrEventType>
|
||||
std::unique_ptr<AvrEventType> waitForAvrEvent(int maximumAttempts = 20) {
|
||||
int attemptCount = 0;
|
||||
|
||||
while (attemptCount <= maximumAttempts) {
|
||||
auto genericEvent = this->getAvrEvent();
|
||||
|
||||
if (genericEvent != nullptr) {
|
||||
// Attempt to downcast event
|
||||
auto event = std::unique_ptr<AvrEventType>(dynamic_cast<AvrEventType*>(genericEvent.release()));
|
||||
|
||||
if (event != nullptr) {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
attemptCount++;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an AVR BREAK event.
|
||||
*
|
||||
* This simply wraps a call to waitForAvrEvent<BreakEvent>(). An exception will be thrown if the call doesn't
|
||||
* return a BreakEvent.
|
||||
*
|
||||
* This should only be used when a BreakEvent is always expected.
|
||||
*/
|
||||
void waitForStoppedEvent();
|
||||
|
||||
public:
|
||||
EdbgAvr8Interface(EdbgInterface& edbgInterface)
|
||||
: edbgInterface(edbgInterface) {};
|
||||
|
||||
/*
|
||||
* The public methods below implement the interface defined by the Avr8Interface class.
|
||||
* See the comments in that class for more info on the expected behaviour of each method.
|
||||
*/
|
||||
|
||||
/**
|
||||
* As already mentioned in numerous comments above, the EdbgAvr8Interface requires some configuration from
|
||||
* the user. This is supplied via the user's Bloom configuration.
|
||||
*
|
||||
* @param targetConfig
|
||||
*/
|
||||
virtual void configure(const TargetConfig& targetConfig) override;
|
||||
|
||||
/**
|
||||
* Accepts target parameters from the AVR8 target instance and sends the necessary target parameters to the
|
||||
* debug tool.
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
virtual void setTargetParameters(const Avr8Bit::TargetParameters& config) override;
|
||||
|
||||
/**
|
||||
* Initialises the AVR8 Generic protocol interface by setting the appropriate parameters on the debug tool.
|
||||
*/
|
||||
virtual void init() override;
|
||||
|
||||
/**
|
||||
* Issues the "stop" command to the debug tool, halting target execution.
|
||||
*/
|
||||
virtual void stop() override;
|
||||
|
||||
/**
|
||||
* Issues the "run" command to the debug tool, resuming execution on the target.
|
||||
*/
|
||||
virtual void run() override;
|
||||
|
||||
/**
|
||||
* Issues the "run to" command to the debug tool, resuming execution on the target, up to a specific byte
|
||||
* address. The target will dispatch an AVR BREAK event once it reaches the specified address.
|
||||
*
|
||||
* @param address
|
||||
* The (byte) address to run to.
|
||||
*/
|
||||
virtual void runTo(std::uint32_t address) override;
|
||||
|
||||
/**
|
||||
* Issues the "step" command to the debug tool, stepping the execution on the target. The stepping can be
|
||||
* configured to step in, out or over. But currently we only support stepping in. The target will dispatch
|
||||
* an AVR BREAK event once it reaches the next instruction.
|
||||
*/
|
||||
virtual void step() override;
|
||||
|
||||
/**
|
||||
* Issues the "reset" command to the debug tool, resetting target execution.
|
||||
*/
|
||||
virtual void reset() override;
|
||||
|
||||
/**
|
||||
* Activates the physical interface and starts a debug session on the target (via attach()).
|
||||
*/
|
||||
virtual void activate() override;
|
||||
|
||||
/**
|
||||
* Terminates any active debug session on the target and severs the connection between the debug tool and
|
||||
* the target (by deactivating the physical interface).
|
||||
*/
|
||||
virtual void deactivate() override;
|
||||
|
||||
/**
|
||||
* Issues the "PC Read" command to the debug tool, to extract the current program counter.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual std::uint32_t getProgramCounter() override;
|
||||
|
||||
/**
|
||||
* Reads the stack pointer register from the target.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual TargetRegister getStackPointerRegister() override;
|
||||
|
||||
/**
|
||||
* Reads the status register from the target.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual TargetRegister getStatusRegister() override;
|
||||
|
||||
/**
|
||||
* Updates the stack pointer register on ther target.
|
||||
*
|
||||
* @param stackPointerRegister
|
||||
*/
|
||||
virtual void setStackPointerRegister(const TargetRegister& stackPointerRegister) override;
|
||||
|
||||
/**
|
||||
* Updates the status register on the target.
|
||||
*
|
||||
* @param statusRegister
|
||||
*/
|
||||
virtual void setStatusRegister(const TargetRegister& statusRegister) override;
|
||||
|
||||
/**
|
||||
* Issues the "PC Write" command to the debug tool, setting the program counter on the target.
|
||||
*
|
||||
* @param programCounter
|
||||
* The byte address to set as the program counter.
|
||||
*/
|
||||
virtual void setProgramCounter(std::uint32_t programCounter) override;
|
||||
|
||||
/**
|
||||
* Issues the "Get ID" command to the debug tool, to extract the signature from the target.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual TargetSignature getDeviceId() override;
|
||||
|
||||
/**
|
||||
* Issues the "Software Breakpoint Set" command to the debug tool, setting a software breakpoint at the given
|
||||
* byte address.
|
||||
*
|
||||
* @param address
|
||||
* The byte address to position the breakpoint.
|
||||
*/
|
||||
virtual void setBreakpoint(std::uint32_t address) override;
|
||||
|
||||
/**
|
||||
* Issues the "Software Breakpoint Clear" command to the debug tool, clearing any breakpoint at the given
|
||||
* byte address.
|
||||
*
|
||||
* @param address
|
||||
* The byte address of the breakpoint to clear.
|
||||
*/
|
||||
virtual void clearBreakpoint(std::uint32_t address) override;
|
||||
|
||||
/**
|
||||
* Issues the "Software Breakpoint Clear All" command to the debug tool, clearing all software breakpoints
|
||||
* that were set *in the current debug session*.
|
||||
*
|
||||
* If the debug session ended before any of the set breakpoints were cleared, this will *not* clear them.
|
||||
*/
|
||||
virtual void clearAllBreakpoints() override;
|
||||
|
||||
/**
|
||||
* Reads gernal purpose registers from the target.
|
||||
*
|
||||
* @param registerIds
|
||||
* @return
|
||||
*/
|
||||
virtual TargetRegisters readGeneralPurposeRegisters(std::set<std::size_t> registerIds) override;
|
||||
|
||||
/**
|
||||
* Writes general purpose registers to target.
|
||||
*
|
||||
* @param registers
|
||||
*/
|
||||
virtual void writeGeneralPurposeRegisters(const TargetRegisters& registers) override;
|
||||
|
||||
/**
|
||||
* This is an overloaded method.
|
||||
*
|
||||
* Resolves the correct Avr8MemoryType from the given TargetMemoryType and calls readMemory().
|
||||
*
|
||||
* @param memoryType
|
||||
* @param startAddress
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
virtual TargetMemoryBuffer readMemory(TargetMemoryType memoryType, std::uint32_t startAddress, std::uint32_t bytes) override;
|
||||
|
||||
/**
|
||||
* This is an overloaded method.
|
||||
*
|
||||
* Resolves the correct Avr8MemoryType from the given TargetMemoryType and calls writeMemory().
|
||||
*
|
||||
* @param memoryType
|
||||
* @param startAddress
|
||||
* @param buffer
|
||||
*/
|
||||
virtual void writeMemory(TargetMemoryType memoryType, std::uint32_t startAddress, const TargetMemoryBuffer& buffer) override;
|
||||
|
||||
/**
|
||||
* Returns the current state of the target.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual TargetState getTargetState() override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "BreakEvent.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void BreakEvent::init(const AvrEvent& event) {
|
||||
AvrEvent::init(event);
|
||||
auto& data = this->getEventData();
|
||||
|
||||
if (data.size() < 8) {
|
||||
/*
|
||||
* All BreakEvent packets must consist of at least 9 bytes:
|
||||
* 1 byte for event ID
|
||||
* 4 bytes for program counter
|
||||
* 1 byte for break cause
|
||||
* 2 bytes for extended info
|
||||
*/
|
||||
throw Exception("Failed to process BreakEvent from AvrEvent - unexpected packet size.");
|
||||
}
|
||||
|
||||
// Program counter consists of 4 bytes
|
||||
this->programCounter = static_cast<std::uint32_t>((data[4] << 24) | (data[3] << 16) | (data[2] << 8) | data[1]) * 2;
|
||||
|
||||
// Break cause is 1 byte, where 0x01 is 'program breakpoint' and 0x00 'unspecified'
|
||||
this->breakCause = data[7] == 0x01 ? TargetBreakCause::BREAKPOINT : TargetBreakCause::UNKNOWN;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../AvrEvent.hpp"
|
||||
#include "src/Targets/Microchip/AVR/Target.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
|
||||
{
|
||||
using Targets::TargetBreakCause;
|
||||
|
||||
class BreakEvent: public AvrEvent
|
||||
{
|
||||
private:
|
||||
std::uint32_t programCounter;
|
||||
TargetBreakCause breakCause;
|
||||
|
||||
void init(const AvrEvent& event);
|
||||
|
||||
public:
|
||||
BreakEvent(const AvrEvent& event) {
|
||||
this->init(event);
|
||||
}
|
||||
|
||||
std::uint32_t getProgramCounter() {
|
||||
return this->programCounter;
|
||||
}
|
||||
|
||||
TargetBreakCause getBreakCause() {
|
||||
return this->breakCause;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/ResponseFrames/AVR8Generic/Avr8GenericResponseFrame.hpp"
|
||||
|
||||
namespace Bloom::Exceptions
|
||||
{
|
||||
using Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::ResponseFrames::Avr8Generic::Avr8GenericResponseFrame;
|
||||
class Avr8CommandFailure: public Exception
|
||||
{
|
||||
private:
|
||||
static inline auto failureCodeToDescription = std::map<unsigned char, std::string>({
|
||||
{0x10, "debugWIRE physical error"},
|
||||
{0x11, "JTAGM failed to initialise"},
|
||||
{0x12, "JTAGM did something strange"},
|
||||
{0x13, "JTAG low level error"},
|
||||
{0x14, "Unsupported version of JTAGM"},
|
||||
{0x15, "JTAG master timed out"},
|
||||
{0x16, "JTAG bit banger timed out"},
|
||||
{0x17, "Parity error in received data"},
|
||||
{0x18, "Did not receive EMPTY byte"},
|
||||
{0x19, "PDI physical timed out"},
|
||||
{0x1A, "Collision on physical level"},
|
||||
{0x1B, "PDI enable failed"},
|
||||
{0x20, "Target not found"},
|
||||
{0x21, "Failure when increasing baud"},
|
||||
{0x22, "Target power not detected"},
|
||||
{0x23, "Must run attach command first"},
|
||||
{0x24, "Devices > 31"},
|
||||
{0x25, "Configured device bits do not add up to detected bits"},
|
||||
{0x31, "Physical not activated"},
|
||||
{0x32, "Illegal run / stopped state"},
|
||||
{0x33, "Invalid config for activate phy"},
|
||||
{0x34, "Not a valid memtype"},
|
||||
{0x35, "Too many or too few bytes"},
|
||||
{0x36, "Asked for a bad address"},
|
||||
{0x37, "Asked for badly aligned data"},
|
||||
{0x38, "Address not within legal range"},
|
||||
{0x39, "Illegal value given"},
|
||||
{0x3A, "Illegal target ID"},
|
||||
{0x3B, "Clock value out of range"},
|
||||
{0x3C, "A timeout occurred"},
|
||||
{0x3D, "Read an illegal OCD status"},
|
||||
{0x40, "NVM failed to be enabled"},
|
||||
{0x41, "NVM failed to be disabled"},
|
||||
{0x42, "Illegal control/status bits"},
|
||||
{0x43, "CRC mismatch"},
|
||||
{0x44, "Failed to enable OCD"},
|
||||
{0x50, "Device not under control"},
|
||||
{0x60, "Error when reading PC"},
|
||||
{0x61, "Error when reading register"},
|
||||
{0x70, "Error while reading"},
|
||||
{0x71, "Error while writing"},
|
||||
{0x72, "Timeout while reading"},
|
||||
{0x80, "Invalid breakpoint configuration"},
|
||||
{0x81, "Not enough available resources"},
|
||||
{0x90, "Feature not available"},
|
||||
{0x91, "Command has not been implemented"},
|
||||
{0xFF, "Unknown error"},
|
||||
});
|
||||
|
||||
public:
|
||||
explicit Avr8CommandFailure(const std::string& message) : Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit Avr8CommandFailure(const char* message) : Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit Avr8CommandFailure(const std::string& message, Avr8GenericResponseFrame& responseFrame)
|
||||
: Exception(message) {
|
||||
this->message = message;
|
||||
|
||||
auto responsePayload = responseFrame.getPayload();
|
||||
if (responsePayload.size() == 3 && this->failureCodeToDescription.contains(responsePayload[2])) {
|
||||
this->message += " - Failure reason: " + this->failureCodeToDescription.find(responsePayload[2])->second;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/ResponseFrames/AvrResponseFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::ResponseFrames::Avr8Generic
|
||||
{
|
||||
class Avr8GenericResponseFrame: public AvrResponseFrame
|
||||
{
|
||||
public:
|
||||
Avr8GenericResponseFrame(const std::vector<AvrResponse>& AVRResponses) : AvrResponseFrame(AVRResponses) {}
|
||||
Avr8GenericResponseFrame() {}
|
||||
|
||||
/**
|
||||
* See parent method.
|
||||
*/
|
||||
std::vector<unsigned char> getPayloadData() override {
|
||||
/*
|
||||
* AVR8 data payloads are in little endian form and include two bytes before the data (response ID and
|
||||
* version byte) as well as an additional byte after the data, known as the 'status code'.
|
||||
*/
|
||||
auto data = std::vector<unsigned char>(
|
||||
this->getPayload().begin() + 2,
|
||||
this->getPayload().end() - 1
|
||||
);
|
||||
|
||||
std::reverse(data.begin(), data.end());
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericResponseFrame.hpp"
|
||||
#include "src/Targets/Microchip/AVR/Target.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::ResponseFrames::Avr8Generic
|
||||
{
|
||||
using namespace Targets::Microchip::Avr;
|
||||
|
||||
class GetDeviceId: public Avr8GenericResponseFrame
|
||||
{
|
||||
public:
|
||||
GetDeviceId(const std::vector<AvrResponse>& AvrResponses) : Avr8GenericResponseFrame(AvrResponses) {}
|
||||
GetDeviceId() {}
|
||||
|
||||
TargetSignature extractSignature(Avr8PhysicalInterface physicalInterface) {
|
||||
auto payloadData = this->getPayloadData();
|
||||
|
||||
switch (physicalInterface) {
|
||||
case Avr8PhysicalInterface::DEBUG_WIRE: {
|
||||
/*
|
||||
* When using the DebugWire physical interface, the get device ID command will return
|
||||
* four bytes, where the first can be ignored.
|
||||
*/
|
||||
return TargetSignature(payloadData[1], payloadData[2], payloadData[3]);
|
||||
}
|
||||
case Avr8PhysicalInterface::PDI:
|
||||
case Avr8PhysicalInterface::PDI_1W: {
|
||||
/*
|
||||
* When using the PDI physical interface, the signature is returned in LSB format.
|
||||
*/
|
||||
return TargetSignature(payloadData[3], payloadData[2], payloadData[1]);
|
||||
}
|
||||
default: {
|
||||
return TargetSignature(payloadData[0], payloadData[1], payloadData[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Avr8GenericResponseFrame.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::ResponseFrames::Avr8Generic
|
||||
{
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
class GetProgramCounter: public Avr8GenericResponseFrame
|
||||
{
|
||||
public:
|
||||
GetProgramCounter(const std::vector<AvrResponse>& AVRResponses) : Avr8GenericResponseFrame(AVRResponses) {}
|
||||
GetProgramCounter() {}
|
||||
|
||||
std::uint32_t extractProgramCounter() {
|
||||
/*
|
||||
* The payload for the PC Read command should always consist of six bytes. Thr first two being the
|
||||
* command ID and version, the other four being the PC. The four PC bytes are little-endian.
|
||||
*/
|
||||
auto& payload = this->getPayload();
|
||||
if (payload.size() != 6) {
|
||||
throw Exception("Failed to extract PC from payload of PC read command response frame - unexpected payload size.");
|
||||
}
|
||||
|
||||
return static_cast<std::uint32_t>(payload[5] << 24 | payload[4] << 16 | payload[3] << 8 | payload[2]) * 2;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "Avr8GenericResponseFrame.hpp"
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::ResponseFrames::Avr8Generic
|
||||
{
|
||||
using namespace Bloom::Exceptions;
|
||||
using Bloom::Targets::TargetMemoryBuffer;
|
||||
|
||||
class ReadMemory: public Avr8GenericResponseFrame
|
||||
{
|
||||
public:
|
||||
ReadMemory(const std::vector<AvrResponse>& AVRResponses) : Avr8GenericResponseFrame(AVRResponses) {}
|
||||
ReadMemory() {}
|
||||
|
||||
TargetMemoryBuffer getMemoryBuffer() {
|
||||
/*
|
||||
* AVR8 data payloads are typically in little endian form, but this does not apply the data returned
|
||||
* from the READ MEMORY commands.
|
||||
*/
|
||||
auto data = std::vector<unsigned char>(
|
||||
this->getPayload().begin() + 2,
|
||||
this->getPayload().end() - 1
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "AvrResponseFrame.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void AvrResponseFrame::initFromAvrResponses(const std::vector<AvrResponse>& avrResponses) {
|
||||
// Build a raw frame buffer from the AVRResponse objects and just call initFromRawFrame()
|
||||
std::vector<unsigned char> rawFrame;
|
||||
|
||||
for (auto& avrResponse : avrResponses) {
|
||||
auto responsePacket = avrResponse.getResponsePacket();
|
||||
rawFrame.insert(rawFrame.end(), responsePacket.begin(), responsePacket.end());
|
||||
}
|
||||
|
||||
return this->initFromRawFrame(rawFrame);
|
||||
}
|
||||
|
||||
void AvrResponseFrame::initFromRawFrame(const std::vector<unsigned char>& rawFrame) {
|
||||
if (rawFrame.size() < 4) {
|
||||
// All AVR response frames must consist of at least four bytes (SOF, sequence ID (two bytes) and
|
||||
// a protocol handler ID)
|
||||
throw Exception("Failed to construct AvrResponseFrame - unexpected end to raw frame");
|
||||
}
|
||||
|
||||
if (rawFrame[0] != 0x0E) {
|
||||
// The SOF field must always be 0x0E
|
||||
throw Exception("Failed to construct AvrResponseFrame - unexpected SOF field value in raw frame");
|
||||
}
|
||||
|
||||
this->setSequenceId(static_cast<std::uint16_t>((rawFrame[2] << 8) + rawFrame[1]));
|
||||
this->setProtocolHandlerId(rawFrame[3]);
|
||||
|
||||
auto& payload = this->getPayload();
|
||||
payload.insert(payload.begin(), rawFrame.begin() + 4, rawFrame.end());
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/Edbg.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrResponse.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
|
||||
{
|
||||
using namespace Edbg;
|
||||
|
||||
class AvrResponseFrame
|
||||
{
|
||||
private:
|
||||
unsigned char SOF = 0x0E;
|
||||
|
||||
/**
|
||||
* Incrementing from 0x00
|
||||
*/
|
||||
std::uint16_t sequenceID = 1;
|
||||
|
||||
/**
|
||||
* Destination sub-protocol handler ID
|
||||
*/
|
||||
ProtocolHandlerId protocolHandlerID;
|
||||
|
||||
std::vector<unsigned char> payload;
|
||||
|
||||
protected:
|
||||
virtual void initFromRawFrame(const std::vector<unsigned char>& rawFrame);
|
||||
|
||||
void setSequenceId(std::uint16_t sequenceId) {
|
||||
this->sequenceID = sequenceId;
|
||||
}
|
||||
|
||||
void setProtocolHandlerId(ProtocolHandlerId protocolHandlerId) {
|
||||
this->protocolHandlerID = protocolHandlerId;
|
||||
}
|
||||
|
||||
void setProtocolHandlerId(unsigned char protocolHandlerId) {
|
||||
this->protocolHandlerID = static_cast<ProtocolHandlerId>(protocolHandlerId);
|
||||
}
|
||||
|
||||
void setPayload(const std::vector<unsigned char>& payload) {
|
||||
this->payload = payload;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit AvrResponseFrame(const std::vector<AvrResponse>& AVRResponses) {
|
||||
this->initFromAvrResponses(AVRResponses);
|
||||
}
|
||||
|
||||
explicit AvrResponseFrame() {}
|
||||
|
||||
/**
|
||||
* An AVRResponse contains a single fragment of an AvrResponseFrame.
|
||||
*
|
||||
* This method will construct an AvrResponseFrame from a vector of AVRResponses.
|
||||
*
|
||||
* @param avrResponses
|
||||
*/
|
||||
void initFromAvrResponses(const std::vector<AvrResponse>& avrResponses);
|
||||
|
||||
std::uint16_t getSequenceId() const {
|
||||
return this->sequenceID;
|
||||
}
|
||||
|
||||
ProtocolHandlerId getProtocolHandlerId() const {
|
||||
return this->protocolHandlerID;
|
||||
}
|
||||
|
||||
std::vector<unsigned char>& getPayload() {
|
||||
return this->payload;
|
||||
}
|
||||
|
||||
unsigned char getResponseId() {
|
||||
return this->payload[0];
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getPayloadData() {
|
||||
return this->getPayload();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "AvrResponseFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::ResponseFrames
|
||||
{
|
||||
class DiscoveryResponseFrame: public AvrResponseFrame
|
||||
{
|
||||
public:
|
||||
DiscoveryResponseFrame(const std::vector<AvrResponse>& AVRResponses) : AvrResponseFrame(AVRResponses) {}
|
||||
DiscoveryResponseFrame() {}
|
||||
|
||||
/**
|
||||
* See parent method.
|
||||
*/
|
||||
std::vector<unsigned char> getPayloadData() override {
|
||||
/*
|
||||
* DISCOVERY payloads include two bytes before the data (response ID and version byte).
|
||||
*/
|
||||
auto data = std::vector<unsigned char>(
|
||||
this->getPayload().begin() + 2,
|
||||
this->getPayload().end()
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg
|
||||
{
|
||||
enum class ProtocolHandlerId: unsigned char
|
||||
{
|
||||
Discovery = 0x00,
|
||||
HouseKeeping = 0x01,
|
||||
Avr8Generic = 0x12,
|
||||
Avr32Generic = 0x13,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "EdbgInterface.hpp"
|
||||
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
|
||||
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
Protocols::CmsisDap::Response EdbgInterface::sendAvrCommandFrameAndWaitForResponse(
|
||||
const Protocols::CmsisDap::Edbg::Avr::AvrCommandFrame& avrCommandFrame
|
||||
) {
|
||||
// An AVR command frame can be split into multiple CMSIS-DAP commands. Each command
|
||||
// containing a fragment of the AvrCommandFrame.
|
||||
|
||||
// Minus 3 to accommodate AVR command meta data
|
||||
std::size_t maximumCommandPacketSize = (this->getUsbHidInputReportSize() - 3);
|
||||
|
||||
auto avrCommands = avrCommandFrame.generateAvrCommands(maximumCommandPacketSize);
|
||||
|
||||
for (auto& avrCommand : avrCommands) {
|
||||
// Send command to device
|
||||
auto response = this->sendCommandAndWaitForResponse(avrCommand);
|
||||
|
||||
if (&avrCommand ==& avrCommands.back()) {
|
||||
return* response;
|
||||
}
|
||||
}
|
||||
|
||||
// This should never happen
|
||||
throw Exception("Cannot send AVR command frame - failed to generate CMSIS-DAP Vendor (AVR) commands");
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
throw Exception("Unexpected response to AvrResponseCommand from device");
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Protocols::CmsisDap::Edbg::Avr::AvrEvent> EdbgInterface::requestAvrEvent() {
|
||||
this->sendCommand(AvrEventCommand());
|
||||
auto cmsisResponse = this->getResponse();
|
||||
|
||||
if (cmsisResponse->getResponseId() == 0x82) {
|
||||
// This is an AVR_EVT response
|
||||
auto avrEvent = Protocols::CmsisDap::Edbg::Avr::AvrEvent();
|
||||
avrEvent.init(*cmsisResponse);
|
||||
return avrEvent.getEventDataSize() > 0 ? std::optional(avrEvent) : std::nullopt;
|
||||
} else {
|
||||
throw Exception("Unexpected response to AvrEventCommand from device");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Protocols::CmsisDap::Edbg::Avr::AvrResponse> EdbgInterface::requestAvrResponses() {
|
||||
std::vector<Protocols::CmsisDap::Edbg::Avr::AvrResponse> responses;
|
||||
AvrResponseCommand responseCommand;
|
||||
|
||||
this->sendCommand(responseCommand);
|
||||
auto response = this->getAvrResponse();
|
||||
responses.push_back(response);
|
||||
int fragmentCount = response.getFragmentCount();
|
||||
|
||||
while (responses.size() < fragmentCount) {
|
||||
// There are more response packets
|
||||
this->sendCommand(responseCommand);
|
||||
response = this->getAvrResponse();
|
||||
|
||||
if (response.getFragmentCount() != fragmentCount) {
|
||||
throw Exception("Failed to fetch AVRResponse objects - invalid fragment count returned.");
|
||||
}
|
||||
|
||||
if (response.getFragmentCount() == 0 && response.getFragmentNumber() == 0) {
|
||||
throw Exception("Failed to fetch AVRResponse objects - unexpected empty response");
|
||||
} else if (response.getFragmentNumber() == 0) {
|
||||
// End of response data ( &this packet can be ignored)
|
||||
break;
|
||||
}
|
||||
|
||||
responses.push_back(response);
|
||||
}
|
||||
|
||||
return responses;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "src/Exceptions/Exception.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/AvrEventCommand.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/AvrResponseCommand.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/ResponseFrames/AvrResponseFrame.hpp"
|
||||
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AvrCommandFrame.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg
|
||||
{
|
||||
using namespace Protocols::CmsisDap::Edbg::Avr;
|
||||
using namespace Exceptions;
|
||||
|
||||
/**
|
||||
* The EdbgInterface class implements the EDBG sub-protocol, which takes the form of numerous CMSIS-DAP vendor
|
||||
* commands.
|
||||
*/
|
||||
class EdbgInterface: public CmsisDapInterface
|
||||
{
|
||||
public:
|
||||
explicit EdbgInterface() = default;
|
||||
|
||||
/**
|
||||
* Send an AvrCommandFrame to the debug tool and wait for a response.
|
||||
*
|
||||
* NOTE: The response this method waits for is *not* an AvrResponseFrame, but rather, just a response from
|
||||
* the debug tool indicating successful receipt of the AvrCommandFrame.
|
||||
* See EdbgInterface::sendAvrCommandFrameAndWaitForResponseFrame().
|
||||
*
|
||||
* @param avrCommandFrame
|
||||
* @return
|
||||
*/
|
||||
virtual Protocols::CmsisDap::Response sendAvrCommandFrameAndWaitForResponse(
|
||||
const Protocols::CmsisDap::Edbg::Avr::AvrCommandFrame& avrCommandFrame
|
||||
);
|
||||
|
||||
Protocols::CmsisDap::Edbg::Avr::AvrResponse getAvrResponse();
|
||||
|
||||
virtual std::vector<Protocols::CmsisDap::Edbg::Avr::AvrResponse> requestAvrResponses();
|
||||
|
||||
virtual std::optional<Protocols::CmsisDap::Edbg::Avr::AvrEvent> requestAvrEvent();
|
||||
|
||||
|
||||
template<class CommandFrameType>
|
||||
typename CommandFrameType::ResponseFrameType sendAvrCommandFrameAndWaitForResponseFrame(
|
||||
const CommandFrameType& avrCommandFrame
|
||||
) {
|
||||
static_assert(
|
||||
std::is_base_of<AvrCommandFrame, CommandFrameType>::value,
|
||||
"AVR Command must be base of AvrCommandFrame."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
std::is_base_of<AvrResponseFrame, typename CommandFrameType::ResponseFrameType>::value,
|
||||
"AVR Command must specify a valid response frame type, derived from AvrResponseFrame."
|
||||
);
|
||||
|
||||
auto response = this->sendAvrCommandFrameAndWaitForResponse(avrCommandFrame);
|
||||
|
||||
if (response.getData()[0] != 0x01) {
|
||||
// The last response packet should always acknowledge receipt of the AvrCommandFrame
|
||||
throw Exception("Failed to send AvrCommandFrame to device - device did not acknowledge receipt.");
|
||||
}
|
||||
|
||||
auto responses = this->requestAvrResponses();
|
||||
auto responseFrame = typename CommandFrameType::ResponseFrameType();
|
||||
responseFrame.initFromAvrResponses(responses);
|
||||
return responseFrame;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
|
||||
#include "src/Targets/Microchip/AVR/TargetSignature.hpp"
|
||||
#include "src/Targets/Microchip/AVR/AVR8/TargetParameters.hpp"
|
||||
#include "src/Targets/TargetState.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
#include "src/ApplicationConfig.hpp"
|
||||
|
||||
namespace Bloom::DebugToolDrivers::TargetInterfaces::Microchip::Avr::Avr8
|
||||
{
|
||||
using namespace Bloom;
|
||||
using namespace Targets::Microchip::Avr;
|
||||
|
||||
using Targets::TargetState;
|
||||
using Targets::TargetRegisterMap;
|
||||
using Targets::TargetMemoryBuffer;
|
||||
using Targets::TargetMemoryType;
|
||||
using Targets::TargetRegister;
|
||||
using Targets::TargetRegisters;
|
||||
using Targets::Microchip::Avr::TargetSignature;
|
||||
|
||||
/**
|
||||
* Interfacing with an AVR8 target can vary significantly, depending on the debug tool being used.
|
||||
*
|
||||
* This class describes the interface required for interfacing with AVR8 targets.
|
||||
*
|
||||
* Each debug tool that supports interfacing with AVR8 targets must provide an implementation
|
||||
* of this interface class. For example, the Atmel-ICE provides the EdbgAvr8Interface implementation for
|
||||
* interfacing with AVR8 targets. See Bloom::DebugToolDrivers::AtmelIce->getAvr8Interface() and
|
||||
* Bloom::DebugToolDriver->getAvr8Interface() for more on this.
|
||||
*/
|
||||
class Avr8Interface
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Configures the interface. Any debug tool -> target interface specific configuration should take
|
||||
* place here.
|
||||
*
|
||||
* For example, the EdbgAvr8Interface implementation configures the physical interface and config
|
||||
* variant here.
|
||||
*
|
||||
* @param targetConfig
|
||||
*/
|
||||
virtual void configure(const TargetConfig& targetConfig) = 0;
|
||||
|
||||
/**
|
||||
* Should accept Avr8 target parameters for configuration of the interface.
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
virtual void setTargetParameters(const Avr8Bit::TargetParameters& config) = 0;
|
||||
|
||||
/**
|
||||
* Should initialise the interface between the debug tool and the AVR8 target.
|
||||
*/
|
||||
virtual void init() = 0;
|
||||
|
||||
/**
|
||||
* Should stop execution on that target.
|
||||
*/
|
||||
virtual void stop() = 0;
|
||||
|
||||
/**
|
||||
* Should resume execution on the AVR8 target.
|
||||
*/
|
||||
virtual void run() = 0;
|
||||
|
||||
/**
|
||||
* Continue execution up to a specific byte address.
|
||||
*/
|
||||
virtual void runTo(std::uint32_t address) = 0;
|
||||
|
||||
/**
|
||||
* Step execution on teh AVR8 target.
|
||||
*/
|
||||
virtual void step() = 0;
|
||||
|
||||
/**
|
||||
* Should reset the AVR8 target.
|
||||
*/
|
||||
virtual void reset() = 0;
|
||||
|
||||
/**
|
||||
* Should activate the physical interface between the debug tool and the AVR8 target.
|
||||
*/
|
||||
virtual void activate() = 0;
|
||||
|
||||
/**
|
||||
* Should deactivate the physical interface between the debug tool and the AVR8 target.
|
||||
*/
|
||||
virtual void deactivate() = 0;
|
||||
|
||||
/**
|
||||
* Should retrieve the AVR8 target signature of the AVR8 target.
|
||||
*
|
||||
* This method may invoke stop(), as some interfaces are known to require the target to be in a stopped
|
||||
* state before the signature can be read.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual TargetSignature getDeviceId() = 0;
|
||||
|
||||
/**
|
||||
* Should set a software breakpoint at a given address.
|
||||
*
|
||||
* @param address
|
||||
*/
|
||||
virtual void setBreakpoint(std::uint32_t address) = 0;
|
||||
|
||||
/**
|
||||
* Should remove a software breakpoint at a given address.
|
||||
*
|
||||
* @param address
|
||||
*/
|
||||
virtual void clearBreakpoint(std::uint32_t address) = 0;
|
||||
|
||||
/**
|
||||
* Should remove all software and hardware breakpoints on the target.
|
||||
*/
|
||||
virtual void clearAllBreakpoints() = 0;
|
||||
|
||||
/**
|
||||
* Should retrieve the current program counter value from the target.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual std::uint32_t getProgramCounter() = 0;
|
||||
|
||||
/**
|
||||
* Should retrieve the current stack pointer register value from the target.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual TargetRegister getStackPointerRegister() = 0;
|
||||
|
||||
/**
|
||||
* Should retrieve the current status register value from the target.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual TargetRegister getStatusRegister() = 0;
|
||||
|
||||
/**
|
||||
* Should update the program counter value on the target.
|
||||
*
|
||||
* @param programCounter
|
||||
*/
|
||||
virtual void setProgramCounter(std::uint32_t programCounter) = 0;
|
||||
|
||||
/**
|
||||
* SHould update the stack pointer register value on the target.
|
||||
*
|
||||
* @param stackPointerRegister
|
||||
*/
|
||||
virtual void setStackPointerRegister(const TargetRegister& stackPointerRegister) = 0;
|
||||
|
||||
/**
|
||||
* Should update the status register value on the target.
|
||||
*
|
||||
* @param statusRegister
|
||||
*/
|
||||
virtual void setStatusRegister(const TargetRegister& statusRegister) = 0;
|
||||
|
||||
/**
|
||||
* Should read the requested general purpose register from the target.
|
||||
*
|
||||
* @param registerIds
|
||||
* A set of register IDs: 0 -> 31
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual TargetRegisters readGeneralPurposeRegisters(std::set<size_t> registerIds) = 0;
|
||||
|
||||
/**
|
||||
* Should update the value of general purpose registers.
|
||||
*
|
||||
* @param registers
|
||||
*/
|
||||
virtual void writeGeneralPurposeRegisters(const TargetRegisters& registers) = 0;
|
||||
|
||||
/**
|
||||
* Should read memory from the target, for the given memory type.
|
||||
*
|
||||
* @param memoryType
|
||||
* @param startAddress
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
virtual TargetMemoryBuffer readMemory(TargetMemoryType memoryType, std::uint32_t startAddress, std::uint32_t bytes) = 0;
|
||||
|
||||
/**
|
||||
* Should write memory to the target, for a given memory type.
|
||||
*
|
||||
* @param memoryType
|
||||
* @param startAddress
|
||||
* @param buffer
|
||||
*/
|
||||
virtual void writeMemory(TargetMemoryType memoryType, std::uint32_t startAddress, const TargetMemoryBuffer& buffer) = 0;
|
||||
|
||||
/**
|
||||
* Should obtain the current target state.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual TargetState getTargetState() = 0;
|
||||
};
|
||||
}
|
||||
135
src/DebugToolDrivers/USB/HID/HidInterface.cpp
Normal file
135
src/DebugToolDrivers/USB/HID/HidInterface.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <src/Logger/Logger.hpp>
|
||||
|
||||
#include "hidapi.hpp"
|
||||
#include "HidInterface.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Exceptions/DeviceCommunicationFailure.hpp"
|
||||
|
||||
using namespace Bloom::Usb;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
std::string HidInterface::getDevicePathByInterfaceNumber(const std::uint16_t& interfaceNumber) {
|
||||
hid_device_info* HIDDeviceInfoList = hid_enumerate(this->getVendorId(), this->getProductId());
|
||||
|
||||
while (HIDDeviceInfoList != nullptr) {
|
||||
if (HIDDeviceInfoList->interface_number == interfaceNumber) {
|
||||
break;
|
||||
}
|
||||
|
||||
HIDDeviceInfoList = HIDDeviceInfoList->next;
|
||||
}
|
||||
|
||||
if (HIDDeviceInfoList == nullptr) {
|
||||
throw std::runtime_error("Failed to match interface number with HID interface.");
|
||||
}
|
||||
|
||||
auto path = std::string(HIDDeviceInfoList->path);
|
||||
hid_free_enumeration(HIDDeviceInfoList);
|
||||
return path;
|
||||
}
|
||||
|
||||
void HidInterface::init() {
|
||||
/*
|
||||
* Because we use the HIDAPI with libusb for our HID interfaces, we must allow the HIDAPI to own the device
|
||||
* resources (device handle and the interface). However, the HIDAPI does not provide any means to ensure that a
|
||||
* specific configuration is set against the device. This is why we first open the device via libusb (by calling
|
||||
* the generic init() method), so that we can set the correct configuration. We then close the device to allow
|
||||
* the HIDAPI to take ownership.
|
||||
*/
|
||||
Interface::init();
|
||||
Interface::detachKernelDriver();
|
||||
Interface::setConfiguration(0);
|
||||
Interface::close();
|
||||
|
||||
hid_init();
|
||||
hid_device* hidDevice;
|
||||
|
||||
std::string HIDInterfacePath = this->getDevicePathByInterfaceNumber(this->getNumber());
|
||||
Logger::debug("HID device path: " + HIDInterfacePath);
|
||||
|
||||
if ((hidDevice = hid_open_path(HIDInterfacePath.c_str())) == nullptr) {
|
||||
throw Exception("Failed to open HID device via hidapi.");
|
||||
}
|
||||
|
||||
if (hidDevice->input_ep_max_packet_size < 1) {
|
||||
throw Exception("Invalid max packet size for USB endpoint, on interface " + std::to_string(this->getNumber()));
|
||||
}
|
||||
|
||||
this->setHidDevice(hidDevice);
|
||||
this->setInputReportSize(static_cast<std::size_t>(hidDevice->input_ep_max_packet_size));
|
||||
this->setLibUsbDeviceHandle(hidDevice->device_handle);
|
||||
}
|
||||
|
||||
void HidInterface::close() {
|
||||
auto hidDevice = this->getHidDevice();
|
||||
|
||||
if (hidDevice != nullptr) {
|
||||
this->setLibUsbDeviceHandle(nullptr);
|
||||
hid_close(hidDevice);
|
||||
// hid_close() releases the interface
|
||||
Interface::setClaimed(false);
|
||||
}
|
||||
|
||||
hid_exit();
|
||||
Interface::close();
|
||||
}
|
||||
|
||||
std::size_t HidInterface::read(unsigned char* buffer, std::size_t maxLength, unsigned int timeout) {
|
||||
int transferred;
|
||||
|
||||
if ((transferred = hid_read_timeout(this->getHidDevice(), buffer, maxLength,
|
||||
timeout == 0 ? -1 : static_cast<int>(timeout))) == -1
|
||||
) {
|
||||
throw DeviceCommunicationFailure("Failed to read from HID device.");
|
||||
}
|
||||
|
||||
return static_cast<std::size_t>(transferred);
|
||||
}
|
||||
|
||||
void HidInterface::write(unsigned char* buffer, std::size_t length) {
|
||||
int transferred;
|
||||
|
||||
if ((transferred = hid_write(this->getHidDevice(), buffer, length)) != length) {
|
||||
Logger::debug("Attempted to write " + std::to_string(length)
|
||||
+ " bytes to HID interface. Bytes written: " + std::to_string(transferred));
|
||||
throw DeviceCommunicationFailure("Failed to write data to HID interface.");
|
||||
}
|
||||
}
|
||||
|
||||
void HidInterface::write(std::vector<unsigned char> buffer) {
|
||||
if (buffer.size() > this->getInputReportSize()) {
|
||||
throw Exception("Cannot send data via HID interface - data exceeds maximum packet size.");
|
||||
|
||||
} else if (buffer.size() < this->getInputReportSize()) {
|
||||
/*
|
||||
* Every report we send via the USB HID interface should be of a fixed size.
|
||||
* In the event of a report being too small, we just fill the buffer vector with 0.
|
||||
*/
|
||||
buffer.resize(this->getInputReportSize(), 0);
|
||||
}
|
||||
|
||||
this->write(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
std::vector<unsigned char> HidInterface::read(unsigned int timeout) {
|
||||
std::vector<unsigned char> output;
|
||||
auto readSize = this->getInputReportSize();
|
||||
|
||||
// Attempt to read the first HID report packet, and whatever is left after that.
|
||||
output.resize(readSize);
|
||||
auto transferredByteCount = this->read(output.data(), readSize, timeout);
|
||||
auto totalByteCount = transferredByteCount;
|
||||
|
||||
while (transferredByteCount >= readSize) {
|
||||
output.resize(totalByteCount + readSize);
|
||||
|
||||
transferredByteCount = this->read(output.data() + totalByteCount, readSize, 1);
|
||||
totalByteCount += transferredByteCount;
|
||||
}
|
||||
|
||||
output.resize(totalByteCount);
|
||||
return output;
|
||||
}
|
||||
117
src/DebugToolDrivers/USB/HID/HidInterface.hpp
Normal file
117
src/DebugToolDrivers/USB/HID/HidInterface.hpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "hidapi.hpp"
|
||||
#include "src/DebugToolDrivers/USB/Interface.hpp"
|
||||
|
||||
namespace Bloom::Usb
|
||||
{
|
||||
/**
|
||||
* The HIDInterface uses the HIDAPI library to implement communication with HID endpoints.
|
||||
*
|
||||
* Currently, this interface only supports single-report HID implementations. HID interfaces with
|
||||
* multiple reports will be supported as-and-when we need it.
|
||||
*/
|
||||
class HidInterface: public Interface
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The HIDAPI library provides a hid_device data structure to represent a USB HID interface.
|
||||
*
|
||||
* @see hidapi.hpp or the HIDAPI documentation for more on this.
|
||||
*/
|
||||
hid_device* hidDevice = nullptr;
|
||||
|
||||
/**
|
||||
* All HID reports have a fixed report length. This means that every packet
|
||||
* we send or receive to/from an HID endpoint must be equal to the report length in size.
|
||||
*
|
||||
* The default input report size is 64 bytes, but this is overridden at interface initialisation.
|
||||
* @see definition of init() for more on this.
|
||||
*/
|
||||
std::size_t inputReportSize = 64;
|
||||
|
||||
void setHidDevice(hid_device* hidDevice) {
|
||||
this->hidDevice = hidDevice;
|
||||
}
|
||||
|
||||
void setInputReportSize(const std::size_t& inputReportSize) {
|
||||
this->inputReportSize = inputReportSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a maximum of `maxLength` bytes into `buffer`, from the HID input endpoint.
|
||||
*
|
||||
* Keeping this in the private scope to enforce use of vector<unsigned char>. See read()
|
||||
* method in public scope.
|
||||
*
|
||||
* @param buffer
|
||||
* @param maxLength
|
||||
* @param timeout
|
||||
*
|
||||
* @return
|
||||
* Number of bytes read.
|
||||
*/
|
||||
std::size_t read(unsigned char* buffer, std::size_t maxLength, unsigned int timeout);
|
||||
|
||||
/**
|
||||
* Writes `length` bytes from `buffer` to HID output endpoint.
|
||||
*
|
||||
* Keeping this in the private scope to enforce use of vector<unsigned char>.
|
||||
* @see write(std::vector<unsigned char> buffer);
|
||||
*
|
||||
* @param buffer
|
||||
* @param length
|
||||
*/
|
||||
void write(unsigned char* buffer, std::size_t length);
|
||||
|
||||
protected:
|
||||
hid_device* getHidDevice() const {
|
||||
return this->hidDevice;
|
||||
}
|
||||
|
||||
public:
|
||||
std::size_t getInputReportSize() {
|
||||
return 512;
|
||||
// return this->inputReportSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Claims the USB HID interface, obtains a hid_device instance and configures
|
||||
*/
|
||||
void init() override;
|
||||
|
||||
void close() override;
|
||||
|
||||
/**
|
||||
* Wrapper for write(unsigned char *buffer, int length)
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void write(std::vector<unsigned char> buffer);
|
||||
|
||||
/**
|
||||
* Reads as much data as the device has to offer, into a vector.
|
||||
*
|
||||
* If `timeout` is set to 0, this method will block until at least one HID report
|
||||
* packet is received.
|
||||
*
|
||||
* @param timeout
|
||||
*
|
||||
* @return
|
||||
* A vector of the data received from the device.
|
||||
*/
|
||||
std::vector<unsigned char> read(unsigned int timeout = 0);
|
||||
|
||||
/**
|
||||
* Resolves a device path from a USB interface number.
|
||||
*
|
||||
* @param interfaceNumber
|
||||
* @return
|
||||
*/
|
||||
std::string getDevicePathByInterfaceNumber(const std::uint16_t& interfaceNumber);
|
||||
};
|
||||
}
|
||||
37
src/DebugToolDrivers/USB/HID/hidapi.hpp
Normal file
37
src/DebugToolDrivers/USB/HID/hidapi.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <libusb-1.0/libusb.h>
|
||||
#include <hidapi/hidapi.h>
|
||||
|
||||
struct hid_device_ {
|
||||
/* Handle to the actual device. */
|
||||
libusb_device_handle* device_handle;
|
||||
|
||||
/* Endpoint information */
|
||||
int input_endpoint;
|
||||
int output_endpoint;
|
||||
int input_ep_max_packet_size;
|
||||
|
||||
/* The interface number of the HID */
|
||||
int interface;
|
||||
|
||||
/* Indexes of Strings */
|
||||
int manufacturer_index;
|
||||
int product_index;
|
||||
int serial_index;
|
||||
|
||||
/* Whether blocking reads are used */
|
||||
int blocking; /* boolean */
|
||||
|
||||
/* Read thread objects */
|
||||
pthread_t thread;
|
||||
pthread_mutex_t mutex; /* Protects input_reports */
|
||||
pthread_cond_t condition;
|
||||
pthread_barrier_t barrier; /* Ensures correct startup sequence */
|
||||
int shutdown_thread;
|
||||
int cancelled;
|
||||
struct libusb_transfer* transfer;
|
||||
|
||||
/* List of received input reports. */
|
||||
struct input_report* input_reports;
|
||||
};
|
||||
150
src/DebugToolDrivers/USB/Interface.cpp
Normal file
150
src/DebugToolDrivers/USB/Interface.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#include <libusb-1.0/libusb.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "Interface.hpp"
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
using namespace Bloom::Usb;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
|
||||
void Interface::init() {
|
||||
libusb_device_descriptor deviceDescriptor = {};
|
||||
auto deviceHandle = this->getLibUsbDeviceHandle();
|
||||
auto libUsbDevice = this->getUSBDevice();
|
||||
int libUsbStatusCode;
|
||||
|
||||
if (libUsbDevice == nullptr) {
|
||||
throw Exception("Cannot open USB device without libusb device pointer.");
|
||||
}
|
||||
|
||||
if (deviceHandle == nullptr) {
|
||||
// Obtain a device handle from libusb
|
||||
if ((libUsbStatusCode = libusb_open(libUsbDevice, &deviceHandle)) < 0) {
|
||||
throw Exception("Failed to open USB device - error code "
|
||||
+ std::to_string(libUsbStatusCode) + " returned.");
|
||||
}
|
||||
}
|
||||
|
||||
if ((libUsbStatusCode = libusb_get_device_descriptor(libUsbDevice, &deviceDescriptor))) {
|
||||
throw Exception("Failed to obtain USB device descriptor - error code "
|
||||
+ std::to_string(libUsbStatusCode) + " returned.");
|
||||
}
|
||||
|
||||
this->setLibUsbDeviceHandle(deviceHandle);
|
||||
this->setInitialised(true);
|
||||
}
|
||||
|
||||
void Interface::setConfiguration(int configIndex) {
|
||||
libusb_config_descriptor* configDescriptor = {};
|
||||
int libUsbStatusCode;
|
||||
|
||||
if ((libUsbStatusCode = libusb_get_config_descriptor(this->getUSBDevice(), 0, &configDescriptor))) {
|
||||
throw Exception("Failed to obtain USB configuration descriptor - error code "
|
||||
+ std::to_string(libUsbStatusCode) + " returned.");
|
||||
}
|
||||
|
||||
if ((libUsbStatusCode = libusb_set_configuration(this->getLibUsbDeviceHandle(), configDescriptor->bConfigurationValue))) {
|
||||
throw Exception("Failed to set USB configuration - error code "
|
||||
+ std::to_string(libUsbStatusCode) + " returned.");
|
||||
}
|
||||
|
||||
libusb_free_config_descriptor(configDescriptor);
|
||||
}
|
||||
|
||||
void Interface::close() {
|
||||
auto deviceHandle = this->getLibUsbDeviceHandle();
|
||||
this->release();
|
||||
|
||||
if (deviceHandle != nullptr) {
|
||||
libusb_close(deviceHandle);
|
||||
this->setLibUsbDeviceHandle(nullptr);
|
||||
}
|
||||
|
||||
this->setInitialised(false);
|
||||
}
|
||||
|
||||
void Interface::claim() {
|
||||
int interfaceNumber = this->getNumber();
|
||||
int libUsbStatusCode = 0;
|
||||
|
||||
this->detachKernelDriver();
|
||||
|
||||
if (libusb_claim_interface(this->getLibUsbDeviceHandle(), interfaceNumber) != 0) {
|
||||
throw Exception("Failed to claim interface {" + std::to_string(interfaceNumber) + "} on USB device\n");
|
||||
}
|
||||
|
||||
this->setClaimed(true);
|
||||
}
|
||||
|
||||
void Interface::detachKernelDriver() {
|
||||
int interfaceNumber = this->getNumber();
|
||||
int libUsbStatusCode;
|
||||
|
||||
if ((libUsbStatusCode = libusb_kernel_driver_active(this->getLibUsbDeviceHandle(), interfaceNumber)) != 0) {
|
||||
if (libUsbStatusCode == 1) {
|
||||
// A kernel driver is active on this interface. Attempt to detach it
|
||||
if (libusb_detach_kernel_driver(this->getLibUsbDeviceHandle(), interfaceNumber) != 0) {
|
||||
throw Exception("Failed to detach kernel driver from interface " +
|
||||
std::to_string(interfaceNumber) + "\n");
|
||||
}
|
||||
} else {
|
||||
throw Exception("Failed to check for active kernel driver on USB interface.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::release() {
|
||||
if (this->isClaimed()) {
|
||||
if (libusb_release_interface(this->getLibUsbDeviceHandle(), this->getNumber()) != 0) {
|
||||
throw Exception("Failed to release interface {" + std::to_string(this->getNumber()) + "} on USB device\n");
|
||||
}
|
||||
|
||||
this->setClaimed(false);
|
||||
}
|
||||
}
|
||||
|
||||
int Interface::read(unsigned char* buffer, unsigned char endPoint, size_t length, size_t timeout) {
|
||||
int totalTransferred = 0;
|
||||
int transferred = 0;
|
||||
int libUsbStatusCode = 0;
|
||||
|
||||
while (length > totalTransferred) {
|
||||
libUsbStatusCode = libusb_interrupt_transfer(
|
||||
this->getLibUsbDeviceHandle(),
|
||||
endPoint,
|
||||
buffer,
|
||||
static_cast<int>(length),
|
||||
&transferred,
|
||||
static_cast<unsigned int>(timeout)
|
||||
);
|
||||
|
||||
if (libUsbStatusCode != 0 && libUsbStatusCode != -7) {
|
||||
throw Exception("Failed to read from USB device. Error code returned: " + std::to_string(libUsbStatusCode));
|
||||
}
|
||||
|
||||
totalTransferred += transferred;
|
||||
}
|
||||
|
||||
return transferred;
|
||||
}
|
||||
|
||||
void Interface::write(unsigned char* buffer, unsigned char endPoint, int length) {
|
||||
int transferred = 0;
|
||||
int libUsbStatusCode = 0;
|
||||
|
||||
libUsbStatusCode = libusb_interrupt_transfer(
|
||||
this->getLibUsbDeviceHandle(),
|
||||
endPoint,
|
||||
buffer,
|
||||
length,
|
||||
&transferred,
|
||||
0
|
||||
);
|
||||
|
||||
if (libUsbStatusCode != 0) {
|
||||
throw Exception("Failed to read from USB device. Error code returned: " + std::to_string(libUsbStatusCode));
|
||||
}
|
||||
}
|
||||
130
src/DebugToolDrivers/USB/Interface.hpp
Normal file
130
src/DebugToolDrivers/USB/Interface.hpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <libusb-1.0/libusb.h>
|
||||
#include <string>
|
||||
|
||||
#include "UsbDevice.hpp"
|
||||
|
||||
namespace Bloom::Usb
|
||||
{
|
||||
class Interface
|
||||
{
|
||||
private:
|
||||
libusb_device* USBDevice = nullptr;
|
||||
std::uint16_t vendorId = 0;
|
||||
std::uint16_t productId = 0;
|
||||
|
||||
/**
|
||||
* With libusb, we can only claim a single USB interface per device handle. For this reason,
|
||||
* device handles are stored against the interface. Each interface must obtain a new handle before
|
||||
* claiming.
|
||||
*/
|
||||
libusb_device_handle* libUsbDeviceHandle = nullptr;
|
||||
std::uint8_t number = 0;
|
||||
std::string name = "";
|
||||
bool initialised = false;
|
||||
bool claimed = false;
|
||||
|
||||
void setInitialised(bool initialised) {
|
||||
this->initialised = initialised;
|
||||
}
|
||||
|
||||
protected:
|
||||
void setClaimed(bool claimed) {
|
||||
this->claimed = claimed;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit Interface(const std::uint8_t& interfaceNumber = 0) {
|
||||
this->setNumber(interfaceNumber);
|
||||
}
|
||||
|
||||
libusb_device* getUSBDevice() const {
|
||||
return this->USBDevice;
|
||||
}
|
||||
|
||||
void setUSBDevice(libusb_device* USBDevice) {
|
||||
this->USBDevice = USBDevice;
|
||||
}
|
||||
|
||||
libusb_device_handle* getLibUsbDeviceHandle() const {
|
||||
return this->libUsbDeviceHandle;
|
||||
}
|
||||
|
||||
void setLibUsbDeviceHandle(libusb_device_handle* libUsbDeviceHandle) {
|
||||
this->libUsbDeviceHandle = libUsbDeviceHandle;
|
||||
}
|
||||
|
||||
std::uint8_t getNumber() const {
|
||||
return this->number;
|
||||
}
|
||||
|
||||
void setNumber(std::uint8_t number) {
|
||||
this->number = number;
|
||||
}
|
||||
|
||||
const std::string& getName() const {
|
||||
return this->name;
|
||||
}
|
||||
|
||||
void setName(const std::string& name) {
|
||||
this->name = name;
|
||||
}
|
||||
|
||||
bool isClaimed() {
|
||||
return this->claimed;
|
||||
}
|
||||
|
||||
bool isInitialised() {
|
||||
return this->initialised;
|
||||
}
|
||||
|
||||
std::uint16_t getVendorId() const {
|
||||
return this->vendorId;
|
||||
}
|
||||
|
||||
void setVendorId(std::uint16_t vendorId) {
|
||||
this->vendorId = vendorId;
|
||||
}
|
||||
|
||||
std::uint16_t getProductId() const {
|
||||
return this->productId;
|
||||
}
|
||||
|
||||
void setProductId(std::uint16_t productId) {
|
||||
this->productId = productId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to obtain a device descriptor and device handle via libusb.
|
||||
*/
|
||||
virtual void init();
|
||||
|
||||
virtual void setConfiguration(int configIndex);
|
||||
|
||||
/**
|
||||
* Releases the interface and closes the device descriptor.
|
||||
*/
|
||||
virtual void close();
|
||||
|
||||
/**
|
||||
* Attempts to claim the interface
|
||||
*/
|
||||
void claim();
|
||||
void detachKernelDriver();
|
||||
void release();
|
||||
|
||||
/**
|
||||
* @TODO Remove or refactor these (read() and write()) - they're not currently used
|
||||
*
|
||||
* @param buffer
|
||||
* @param endPoint
|
||||
* @param length
|
||||
* @param timeout
|
||||
* @return
|
||||
*/
|
||||
virtual int read(unsigned char* buffer, unsigned char endPoint, std::size_t length, std::size_t timeout);
|
||||
virtual void write(unsigned char* buffer, unsigned char endPoint, int length);
|
||||
};
|
||||
}
|
||||
77
src/DebugToolDrivers/USB/UsbDevice.cpp
Normal file
77
src/DebugToolDrivers/USB/UsbDevice.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/usbdevice_fs.h>
|
||||
#include <libusb-1.0/libusb.h>
|
||||
|
||||
#include "UsbDevice.hpp"
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
using Bloom::Usb::UsbDevice;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
std::vector<libusb_device*> UsbDevice::findMatchingDevices(
|
||||
std::optional<std::uint16_t> vendorId, std::optional<std::uint16_t> productId
|
||||
) {
|
||||
auto libUsbContext = this->libUsbContext;
|
||||
libusb_device** devices = nullptr;
|
||||
libusb_device* device;
|
||||
std::vector<libusb_device*> matchedDevices;
|
||||
ssize_t i = 0, libUsbStatusCode;
|
||||
|
||||
auto vendorIdToMatch = vendorId.value_or(this->vendorId);
|
||||
auto productIdToMatch = productId.value_or(this->productId);
|
||||
|
||||
if ((libUsbStatusCode = libusb_get_device_list(libUsbContext, &devices)) < 0) {
|
||||
throw Exception("Failed to retrieve USB devices - return code: '" + std::to_string(libUsbStatusCode)
|
||||
+ "'");
|
||||
}
|
||||
|
||||
while ((device = devices[i++]) != nullptr) {
|
||||
struct libusb_device_descriptor desc = {};
|
||||
|
||||
if ((libUsbStatusCode = libusb_get_device_descriptor(device, &desc)) < 0) {
|
||||
Logger::warning("Failed to retrieve USB device descriptor - return code: '"
|
||||
+ std::to_string(libUsbStatusCode) + "'");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (desc.idVendor == vendorIdToMatch && desc.idProduct == productIdToMatch) {
|
||||
matchedDevices.push_back(device);
|
||||
}
|
||||
}
|
||||
|
||||
libusb_free_device_list(devices, 1);
|
||||
return matchedDevices;
|
||||
}
|
||||
|
||||
void UsbDevice::init()
|
||||
{
|
||||
libusb_init(&this->libUsbContext);
|
||||
// libusb_set_option(this->libUsbContext, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_NONE);
|
||||
auto devices = this->findMatchingDevices();
|
||||
|
||||
if (devices.empty()) {
|
||||
throw Exception("Failed to find USB device with matching vendor & product ID.");
|
||||
|
||||
} else if (devices.size() > 1) {
|
||||
// TODO: implement support for multiple devices (maybe via serial number?)
|
||||
throw Exception("Multiple devices of matching vendor & product ID found.\n"
|
||||
"Yes, as a program I really am too stupid to figure out what to do "
|
||||
"here, so I'm just going to quit.\n Please ensure that only one debug tool "
|
||||
"is connected and then try again.");
|
||||
}
|
||||
|
||||
// For now, just use the first device found.
|
||||
this->setLibUsbDevice(devices.front());
|
||||
}
|
||||
|
||||
void UsbDevice::close()
|
||||
{
|
||||
if (this->libUsbContext != nullptr) {
|
||||
libusb_exit(this->libUsbContext);
|
||||
}
|
||||
}
|
||||
54
src/DebugToolDrivers/USB/UsbDevice.hpp
Normal file
54
src/DebugToolDrivers/USB/UsbDevice.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <libusb-1.0/libusb.h>
|
||||
|
||||
#include "src/DebugToolDrivers/DebugTool.hpp"
|
||||
|
||||
namespace Bloom::Usb
|
||||
{
|
||||
class UsbDevice
|
||||
{
|
||||
private:
|
||||
libusb_context* libUsbContext = nullptr;
|
||||
libusb_device* libUsbDevice = nullptr;
|
||||
std::uint16_t vendorId;
|
||||
std::uint16_t productId;
|
||||
|
||||
std::vector<libusb_device*> findMatchingDevices(
|
||||
std::optional<std::uint16_t> vendorId = std::nullopt, std::optional<std::uint16_t> productId = std::nullopt
|
||||
);
|
||||
|
||||
protected:
|
||||
void close();
|
||||
|
||||
public:
|
||||
void init();
|
||||
|
||||
UsbDevice(std::uint16_t vendorId, std::uint16_t productId) {
|
||||
this->vendorId = vendorId;
|
||||
this->productId = productId;
|
||||
};
|
||||
|
||||
~UsbDevice() = default;
|
||||
|
||||
[[nodiscard]] libusb_device* getLibUsbDevice() const {
|
||||
return this->libUsbDevice;
|
||||
}
|
||||
|
||||
void setLibUsbDevice(libusb_device* libUsbDevice) {
|
||||
this->libUsbDevice = libUsbDevice;
|
||||
}
|
||||
|
||||
std::uint16_t getVendorId() const {
|
||||
return this->vendorId;
|
||||
}
|
||||
|
||||
std::uint16_t getProductId() const {
|
||||
return this->productId;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user