Initial commit

This commit is contained in:
Nav
2021-04-04 21:04:12 +01:00
commit a29c5e1fec
549 changed files with 441216 additions and 0 deletions

View 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;
};
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "DebugTool.hpp"
#include "src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp"
#include "src/DebugToolDrivers/Microchip/PowerDebugger/PowerDebugger.hpp"

View 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;
}

View 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();
};
}

View File

@@ -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;
}

View File

@@ -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();
};
}

View File

@@ -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;
}

View File

@@ -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
);
};
}

View 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;
}

View 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;
};
}

View 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);
}

View 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;
};
}

View File

@@ -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,
};
}

View File

@@ -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;
}

View File

@@ -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;
}
};
}

View File

@@ -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];
}
}

View File

@@ -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);
}
};
}

View File

@@ -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);
}
};
}

View File

@@ -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);
}

View File

@@ -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;
}
};
}

View File

@@ -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);
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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);
}
};
}

View File

@@ -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();
};
};
}

View File

@@ -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;
}
};
}

View File

@@ -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();
};
};
}

View File

@@ -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();
};
};
}

View File

@@ -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();
};
};
}

View File

@@ -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();
};
};
}

View File

@@ -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;
}
};
}

View File

@@ -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();
};
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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();
};
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}

View File

@@ -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;
};
}

View File

@@ -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"

View File

@@ -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);
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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();
}
};
}

View File

@@ -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);
}
};
}

View File

@@ -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();
}
};
}

View File

@@ -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);
}

View File

@@ -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;
};
}

View File

@@ -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;
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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]);
}
}
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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());
}

View File

@@ -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();
}
};
}

View File

@@ -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;
}
};
}

View File

@@ -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,
};
}

View File

@@ -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;
}

View File

@@ -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;
}
};
}

View File

@@ -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;
};
}

View 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;
}

View 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);
};
}

View 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;
};

View 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));
}
}

View 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);
};
}

View 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);
}
}

View 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;
}
};
}