xdebug

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 23d950c8e902562a5194fc9b7e2fa44b3bf4e1eb
Author: Brian Swetland <swetland@frotz.net>
Date:   Sun, 26 Feb 2023 19:48:59 -0800

initial checkin

Diffstat:
A.gitignore | 2++
AMakefile | 13+++++++++++++
AREADME.md | 6++++++
Asrc/arm-debug.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cmsis-dap-protocol.h | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usb.c | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usb.h | 20++++++++++++++++++++
Asrc/xdebug.c | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 603 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +.*.swp +bin/ diff --git a/Makefile b/Makefile @@ -0,0 +1,13 @@ + +all: bin/xdebug + +SRCS := src/xdebug.c src/usb.c +DEPS := src/arm-debug.h src/cmsis-dap-protocol.h src/usb.h + +bin/xdebug: $(SRCS) $(DEPS) + @mkdir -p $(dir $@) + gcc -o $@ -Wall -g -O1 $(SRCS) -lusb-1.0 + +clean: + rm -f bin/ + diff --git a/README.md b/README.md @@ -0,0 +1,6 @@ +# xdebug + +Rewriting mdebug to reflect ARM ADI v5.2, newer hardware, +tidier code, and focused on CMSIS-DAP instead of my own +RSWD protocol (so it will work with a wider variety of +hardware debug probes). diff --git a/src/arm-debug.h b/src/arm-debug.h @@ -0,0 +1,57 @@ +// Copyright 2023, Brian Swetland <swetland@frotz.net> +// Licensed under the Apache License, Version 2.0. + +#pragma once + +// high nybble is banksel +#define DP_DPIDR 0x00 // RO Debug Port ID +#define DP_ABORT 0x00 // WO +#define DP_CS 0x04 // RW CTRL/STAT +#define DP_DLCR 0x14 // RW Data Link Control +#define DP_TARGETID 0x24 // RO v2 +#define DP_DLPIDR 0x34 // RO v2 Data Link ID +#define DP_EVENTSTAT 0x44 // RO v2 +#define DP_SELECT 0x08 // WO +#define DP_RESEND 0x08 // RO v2 Return last AP or RDBUFF read data +#define DP_RDBUFF 0x0C // RO +#define DP_TARGETSEL 0x0C // WO v2 + +#define DP_ABORT_DAPABORT 0x01U // Abort Current AP Txn +#define DP_ABORT_STKCMPCLR 0x02U // clear CS.STICKYCMP +#define DP_ABORT_STKERRCLR 0x04U // clear CS.STICKYERR +#define DP_ABORT_WDERRCLR 0x08U // clear CS.WDATAERR +#define DP_ABORT_ORUNERRCLR 0x10U // clear CS.STICKYORUN + +#define DP_CS_ORUNDETECT 0x00000001U // RW +#define DP_CS_STICKYORUN 0x00000002U // RO/WI +#define DP_CS_MODE_MASK 0x0000000CU // RW +#define DP_CS_MODE_NORMAL 0x00000000U +#define DP_CS_MODE_PUSHED_VERIFY 0x00000004U +#define DP_CS_MODE_PUSHED_COMPARE 0x00000008U +#define DP_CS_MODE_RESERVED 0x0000000CU +#define DP_CS_STICKYCMP 0x00000010U // RO/WI for pushed ops +#define DP_CS_STICKYERR 0x00000020U // RO/WI error occurred in AP txn +#define DP_CS_READOK 0x00000040U // RO/WI last AP or RDBUFF RD was OK +#define DP_CS_WDATAERR 0x00000080U // RO/WI +#define DP_CS_MASKLANE(n) (((n) & 0xF) << 8) // RW +#define DP_CS_TRNCNT(n) (((n) & 0xFFF) << 12) +#define DP_CS_CDBGRSTREQ 0x04000000U // RW or RAZ/WI +#define DP_CS_CDBGRSTACK 0x08000000U // RO +#define DP_CS_CDBGPWRUPREQ 0x10000000U // RW +#define DP_CS_CDBGPWRUPACK 0x20000000U // RO +#define DP_CS_CSYSPWRUPREQ 0x40000000U // RW +#define DP_CS_CSYSPWRUPACK 0x80000000U // RO + +#define DP_DLCR_TURNROUND_MASK 0x00000300U +#define DP_DLCR_TURNROUND_1 0x00000000U +#define DP_DLCR_TURNROUND_2 0x00000100U +#define DP_DLCR_TURNROUND_3 0x00000200U +#define DP_DLCR_TURNROUND_4 0x00000300U +#define DP_DLCR_MUST_BE_ONE 0x00000040U + +#define DP_EVENTSTAT_EA 0x000000001U + +#define DP_SELECT_DPBANK(n) ((n) & 0xF) +#define DP_SELECT_APBANK(n) (((n) & 0xF) << 4) +#define DP_SELECT_AP(n) (((n) & 0xFF) << 24) + diff --git a/src/cmsis-dap-protocol.h b/src/cmsis-dap-protocol.h @@ -0,0 +1,132 @@ +// Copyright 2023, Brian Swetland <swetland@frotz.net> +// Licensed under the Apache License, Version 2.0. + +#pragma once + +#define DAP_Info 0x00 // BYTE(DI_*) +// Response LEN BYTES[LEN] + +#define DI_Vendor_Name 0x01 +#define DI_Product_Name 0x02 +#define DI_Serial_Number 0x03 +#define DI_Protocol_Version 0x04 +#define DI_Target_Device_Vendor 0x05 +#define DI_Target_Device_Name 0x06 +#define DI_Target_Board_Vendor 0x07 +#define DI_Target_Board_Name 0x08 +#define DI_Product_Firmware_Version 0x09 +#define DI_Capabilities 0xF0 // BYTE +#define DI_Test_Domain_Timer 0xF1 // DWORD +#define DI_UART_RX_Buffer_Size 0xFB // WORD +#define DI_UART_TX_Buffer_Size 0xFC // WORD +#define DI_SWO_Trace_Buffer_Size 0xFD // WORD +#define DI_Max_Packet_Count 0xFE // BYTE +#define DI_Max_Packet_Size 0xFF // SHORT + +// Capabilities Reply 0x00 LEN I0 I1 +#define I0_SWD 0x01 +#define I0_JTAG 0x02 +#define I0_SWO_UART 0x04 +#define I0_SWO_Manchester 0x08 +#define I0_Atomic_Commands 0x10 +#define I0_Test_Domain_Timer 0x20 +#define I0_SWO_Streaming_Trace 0x40 +#define I0_UART_Comm_Port 0x80 + +#define I1_USB_COM_Port 0x01 + + +#define DAP_HostStatus 0x01 // BYTE(Type) BYTE(ZeroOffOneOn) +// set host status LEDs +#define HS_Type_Connected 0 +#define HS_Type_Running 1 + +#define DAP_Connect 0x02 // BYTE(Port) +// Reponse BYTE(Port) + +#define PORT_DEFAULT 0 +#define PORT_SWD 1 +#define PORT_JTAG 2 + +#define DAP_Disconnect 0x03 + +#define DAP_WriteABORT 0x08 // BYTE(Index) WORD(value) +// Write an abort request to the target + +#define DAP_Delay 0x09 // SHORT(DelayMicros) + +#define DAP_ResetTarget 0x0A +// Response BYTE(Status) BYTE(Execute) +// Execute 1 = device specific reset sequence implemented + +#define DAP_SWJ_Pins 0x10 // BYTE(PinOut) BYTE(PinSel) WORD(PinWaitMicros) +// Response BYTE(PinInput) +// Modify pins (PinOut) where selected (PinSel) + +#define PIN_SWCLK 0x01 +#define PIN_TCK 0x01 +#define PIN_SWDIO 0x02 +#define PIN_TMS 0x02 +#define PIN_TDI 0x04 +#define PIN_TDO 0x08 +#define PIN_nTRST 0x20 +#define PIN_nRESET 0x80 + +#define DAP_SWD_Configure 0x13 // BYTE(Config) +#define CFG_Turnaround_1 0x00 +#define CFG_Turnaround_2 0x01 +#define CFG_Turnaround_3 0x02 +#define CFG_Turnaround_4 0x03 +#define CFG_AlwaysDataPhase 0x04 + +#define DAP_SWD_Sequence 0x1D +// First Byte is Count +// Then one Info byte (and, if output, data) per Count +// Data is LSB first, padded to byte boundary +#define SEQ_OUTPUT 0x00 +#define SEQ_INPUT 0x80 +// Response: BYTE(STATUS) DATA (LSB first) + + +#define DAP_TransferConfigure 0x04 +// BYTE(IdleCycles) SHORT(WaitRetry) SHORT(MatchRetry) +// idle cycles - number of extra idle cycles after each transfer +// wait retry - max number of retries after WAIT response +// match retry - max number of retries on reads w/ value match + +#define DAP_Transfer 0x05 +// BYTE(Index) BYTE(Count) followed by Count instances of +// BYTE(XferReq) WORD(Value) WORD(MatchMask) WORD(ValueMatch) +#define XFER_DebugPort 0x00 +#define XFER_AccessPort 0x01 +#define XFER_Write 0x00 +#define XFER_Read 0x02 +#define XFER_Addr_00 0x00 +#define XFER_Addr_04 0x04 +#define XFER_Addr_08 0x08 +#define XFER_Addr_0C 0x0C +#define XFER_ValueMatch 0x10 +#define XFER_MatchMask 0x20 +#define XFER_TimeStamp 0x80 + +#define RSP_ACK_MASK 0x07 +#define RSP_ACK_OK 0x01 +#define RSP_ACK_WAIT 0x02 +#define RSP_ACK_FAULT 0x04 +#define RSP_ProtocolError 0x08 +#define RSP_ValueMismatch 0x10 +// Reponse BYTE(Count) BYTE(Response) WORD(TimeStamp)? WORD(Data)* + +#define DAP_TransferBlock 0x06 +// BYTE(Index) SHORT(Count) BYTE(XferReq) WORD(Data)* +// XFER as above but not ValueMatch/MatchMask/TimeStamp +// Response SHORT(Count) BYTE(Response) WORD(Data)* + +#define DAP_ExecuteCommands 0x7F +// BYTE(Count) Count x Commands +// Response BYTE(Count) Count x Responses + +#define DAP_QueueCommands 0x7E +// as above but N packets can be sent +// first non-queue-commands packet triggers execution + diff --git a/src/usb.c b/src/usb.c @@ -0,0 +1,124 @@ +// Copyright 2014, Brian Swetland <swetland@frotz.net> +// Licensed under the Apache License, Version 2.0. + +#include <stdlib.h> +#include <stdio.h> + +#include <libusb-1.0/libusb.h> + +#include "usb.h" + +struct usb_handle { + libusb_device_handle *dev; + unsigned ei; + unsigned eo; +}; + +static libusb_context *usb_ctx = NULL; + +usb_handle *usb_open(unsigned vid, unsigned pid, unsigned ifc) { + usb_handle *usb; + int r; + + if (usb_ctx == NULL) { + if (libusb_init(&usb_ctx) < 0) { + usb_ctx = NULL; + return NULL; + } + } + + usb = malloc(sizeof(usb_handle)); + if (usb == 0) { + return NULL; + } + + /* TODO: extract from descriptors */ + switch (ifc) { + case 0: + usb->ei = 0x81; + usb->eo = 0x01; + break; + case 1: + usb->ei = 0x82; + usb->eo = 0x02; + break; + case 42: + usb->ei = 0x85; + usb->eo = 0x04; + ifc = 0; + break; + default: + goto fail; + } + + usb->dev = libusb_open_device_with_vid_pid(usb_ctx, vid, pid); + if (usb->dev == NULL) { + goto fail; + } + // This causes problems on re-attach. Maybe need for OSX? + // On Linux it's completely happy without us explicitly setting a configuration. + //r = libusb_set_configuration(usb->dev, 1); + r = libusb_claim_interface(usb->dev, ifc); + if (r < 0) { + fprintf(stderr, "failed to claim interface #%d\n", ifc); + goto close_fail; + } + +#ifdef __APPLE__ + // make sure everyone's data toggles agree + // makes things worse on Linux, but happy on OSX + libusb_clear_halt(usb->dev, usb->ei); + libusb_clear_halt(usb->dev, usb->eo); +#endif + + return usb; + +close_fail: + libusb_close(usb->dev); +fail: + free(usb); + return NULL; +} + +void usb_close(usb_handle *usb) { + libusb_close(usb->dev); + free(usb); +} + +int usb_ctrl(usb_handle *usb, void *data, + uint8_t typ, uint8_t req, uint16_t val, uint16_t idx, uint16_t len) { + int r = libusb_control_transfer(usb->dev, typ, req, val, idx, data, len, 5000); + if (r < 0) { + return -1; + } else { + return r; + } +} + +int usb_read(usb_handle *usb, void *data, int len) { + int xfer = len; + int r = libusb_bulk_transfer(usb->dev, usb->ei, data, len, &xfer, 5000); + if (r < 0) { + return -1; + } + return xfer; +} + +int usb_read_forever(usb_handle *usb, void *data, int len) { + int xfer = len; + int r = libusb_bulk_transfer(usb->dev, usb->ei, data, len, &xfer, 0); + if (r < 0) { + return -1; + } + return xfer; +} + +int usb_write(usb_handle *usb, const void *data, int len) { + int xfer = len; + int r = libusb_bulk_transfer(usb->dev, usb->eo, (void*) data, len, &xfer, 5000); + if (r < 0) { + return -1; + } + return xfer; +} + diff --git a/src/usb.h b/src/usb.h @@ -0,0 +1,20 @@ +// Copyright 2014, Brian Swetland <swetland@frotz.net> +// Licensed under the Apache License, Version 2.0. + +#ifndef _USB_H_ +#define _USB_H_ + +#include <stdint.h> + +typedef struct usb_handle usb_handle; + +/* simple usb api for devices with bulk in+out interfaces */ + +usb_handle *usb_open(unsigned vid, unsigned pid, unsigned ifc); +void usb_close(usb_handle *usb); +int usb_read(usb_handle *usb, void *data, int len); +int usb_read_forever(usb_handle *usb, void *data, int len); +int usb_write(usb_handle *usb, const void *data, int len); +int usb_ctrl(usb_handle *usb, void *data, + uint8_t typ, uint8_t req, uint16_t val, uint16_t idx, uint16_t len); +#endif diff --git a/src/xdebug.c b/src/xdebug.c @@ -0,0 +1,249 @@ +// Copyright 2023, Brian Swetland <swetland@frotz.net> +// Licensed under the Apache License, Version 2.0. + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "usb.h" +#include "cmsis-dap-protocol.h" + +struct debug_context { + usb_handle* usb; + unsigned connected; + + // last dp select written + uint32_t dp_select; + + uint32_t max_packet_count; + uint32_t max_packet_size; +}; + + +typedef struct debug_context DC; + + +int dap_get_info(DC* dc, unsigned di, void *out, unsigned minlen, unsigned maxlen) { + uint8_t buf[256 + 2]; + buf[0] = DAP_Info; + buf[1] = di; + if (usb_write(dc->usb, buf, 2) != 2) { + return -1; + } + int sz = usb_read(dc->usb, buf, 256 + 2); + if ((sz < 2) || (buf[0] != DAP_Info)) { + return -1; + } + //printf("0x%02x > 0x%02x 0x%02x\n", di, buf[0], buf[1]); + if ((buf[1] < minlen) || (buf[1] > maxlen)) { + return -1; + } + memcpy(out, buf + 2, buf[1]); + return buf[1]; +} + +void dump(const char* str, const void* ptr, unsigned len) { + const uint8_t* x = ptr; + fprintf(stderr, "%s", str); + while (len > 0) { + fprintf(stderr, " %02x", *x++); + len--; + } + fprintf(stderr, "\n"); +} + +int dap_cmd(DC* dc, const void* tx, unsigned txlen, void* rx, unsigned rxlen) { + uint8_t cmd = ((const uint8_t*) tx)[0]; + dump("TX>", tx, txlen); + if (usb_write(dc->usb, tx, txlen) != txlen) { + fprintf(stderr, "dap_cmd(0x%02x): usb write error\n", cmd); + return -1; + } + int sz = usb_read(dc->usb, rx, rxlen); + if (sz < 1) { + fprintf(stderr, "dap_cmd(0x%02x): usb read error\n", cmd); + return -1; + } + dump("RX>", rx, rxlen); + if (((uint8_t*) rx)[0] != cmd) { + fprintf(stderr, "dap_cmd(0x%02x): unsupported (0x%02x)\n", + cmd, ((uint8_t*) rx)[0]); + return -1; + } + fprintf(stderr, "dap_cmd(0x%02x): sz %u\n", cmd, sz); + return sz; +} + +int dap_cmd_std(DC* dc, const char* name, uint8_t* io, + unsigned txlen, unsigned rxlen) { + if (dap_cmd(dc, io, txlen, io, rxlen) < 0) { + return -1; + } + if (io[1] != 0) { + fprintf(stderr, "%s status 0x%02x\n", name, io[1]); + return -1; + } + return 0; +} + +int dap_connect(DC* dc) { + uint8_t io[2] = { DAP_Connect, PORT_SWD }; + return dap_cmd_std(dc, "dap_connect()", io, 2, 2); +} + +int dap_swd_configure(DC* dc, unsigned cfg) { + uint8_t io[2] = { DAP_SWD_Configure, cfg }; + return dap_cmd_std(dc, "dap_swd_configure()", io, 2, 2); +} + +int dap_transfer_configure(DC* dc, unsigned idle, unsigned wait, unsigned match) { + uint8_t io[6] = { DAP_TransferConfigure, idle, wait, wait >> 8, match, match >> 8}; + return dap_cmd_std(dc, "dap_transfer_configure()", io, 6, 2); +} + +int dap_xfer_wr1(DC* dc, unsigned cfg, uint32_t val) { + uint8_t io[8] = { + DAP_Transfer, 0, 1, cfg & 0x0D, + val, val >> 8, val >> 16, val >> 24 }; + if (dap_cmd(dc, io, 8, io, 3) < 0) { + return -1; + } + if (io[1] != 0) { + fprintf(stderr, "dap_xfer_wr1() invalid count %u\n", io[1]); + return -1; + } + if (io[2] == RSP_ACK_OK) { + return 0; + } + fprintf(stderr, "dap_xfer_wr1() status 0x%02x\n", io[2]); + return -1; +} + +int dap_xfer_rd1(DC* dc, unsigned cfg, uint32_t* val) { + uint8_t io[8] = { + DAP_Transfer, 0, 1, XFER_Read | (cfg & 0x0D) }; + if (dap_cmd(dc, io, 4, io, 7) < 0) { + return -1; + } + if (io[1] != 1) { + fprintf(stderr, "dap_xfer_rd1() invalid count %u\n", io[1]); + return -1; + } + if (io[2] == RSP_ACK_OK) { + memcpy(val, io + 3, 4); + return 0; + } + fprintf(stderr, "dap_xfer_rd1() status 0x%02x\n", io[2]); + return -1; +} + +int swd_init(DC* dc) { + uint8_t io[23] = { + DAP_SWD_Sequence, 3, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 64 1s + 0x10, 0x9E, 0xE7, // JTAG to SWD magic sequence + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, // 60 1s, 4 0s + }; + if (dap_cmd(dc, io, 23, io, 2) < 0) { + return -1; + } + if (io[1] != 0) { + fprintf(stderr, "swd_init() failure 0x%02x\n", io[1]); + return -1; + } + return 0; +} + +int dc_init(DC* dc, usb_handle* usb) { + uint8_t buf[256 + 2]; + uint32_t n32; + uint16_t n16; + uint8_t n8; + + memset(dc, 0, sizeof(DC)); + dc->usb = usb; + dc->max_packet_count = 1; + dc->max_packet_size = 64; + + buf[0] = DAP_Info; + for (unsigned n = 0; n < 10; n++) { + int sz = dap_get_info(dc, n, buf, 0, 255); + if (sz > 0) { + buf[sz] = 0; + printf("0x%02x: '%s'\n", n, (char*) buf); + } + } + + buf[0] = 0; buf[1] = 0; + if (dap_get_info(dc, DI_Capabilities, buf, 1, 2) > 0) { + printf("Capabilities: 0x%02x 0x%02x\n", buf[0], buf[1]); + printf("Capabilities:"); + if (buf[0] & I0_SWD) printf(" SWD"); + if (buf[0] & I0_JTAG) printf(" JTAG"); + if (buf[0] & I0_SWO_UART) printf(" SWO(UART)"); + if (buf[0] & I0_SWO_Manchester) printf(" SWO(Manchester)"); + if (buf[0] & I0_Atomic_Commands) printf(" ATOMIC"); + if (buf[0] & I0_Test_Domain_Timer) printf(" TIMER"); + if (buf[0] & I0_SWO_Streaming_Trace) printf(" SWO(Streaming)"); + if (buf[0] & I0_UART_Comm_Port) printf(" UART"); + if (buf[1] & I1_USB_COM_Port) printf(" USBCOM"); + printf("\n"); + } + if (dap_get_info(dc, DI_UART_RX_Buffer_Size, &n32, 4, 4) == 4) { + printf("UART RX Buffer Size: %u\n", n32); + } + if (dap_get_info(dc, DI_UART_TX_Buffer_Size, &n32, 4, 4) == 4) { + printf("UART TX Buffer Size: %u\n", n32); + } + if (dap_get_info(dc, DI_SWO_Trace_Buffer_Size, &n32, 4, 4) == 4) { + printf("SWO Trace Buffer Size: %u\n", n32); + } + if (dap_get_info(dc, DI_Max_Packet_Count, &n8, 1, 1) == 1) { + printf("Max Packet Count: %u\n", n8); + dc->max_packet_count = n8; + } + if (dap_get_info(dc, DI_Max_Packet_Size, &n16, 2, 2) == 2) { + printf("Max Packet Size: %u\n", n16); + dc->max_packet_size = n16; + } + + if ((dc->max_packet_count < 1) || (dc->max_packet_size < 64)) { + fprintf(stderr, "dc_init() impossible packet configuration\n"); + return -1; + } + + return 0; +} + +int main(int argc, char **argv) { + usb_handle *usb; + uint32_t n; + + usb = usb_open(0x1fc9, 0x0143, 0); + if (usb == 0) { + usb = usb_open(0x2e8a, 0x000c, 42); + if (usb == 0) { + fprintf(stderr, "cannot find device\n"); + return -1; + } + } + + DC context; + DC* dc = &context; + dc_init(dc, usb); + + dap_connect(dc); + dap_swd_configure(dc, CFG_Turnaround_1); + dap_transfer_configure(dc, 8, 64, 0); + //dap_xfer_wr1(dc, XFER_DebugPort | XFER_Addr_04, 0x11553311); + swd_init(dc); + n = 0; + dap_xfer_rd1(dc, XFER_DebugPort, &n); + printf("IDCODE %08x\n", n); + + return 0; +} + + +