diff --git a/src/command.c b/src/command.c index 52a60c6c721..c1b84191cf1 100644 --- a/src/command.c +++ b/src/command.c @@ -56,6 +56,9 @@ static bool cmd_help(target_s *target, int argc, const char **argv); static bool cmd_jtag_scan(target_s *target, int argc, const char **argv); static bool cmd_swd_scan(target_s *target, int argc, const char **argv); +#ifdef PLATFORM_HAS_RVSWD +static bool cmd_rvswd_scan(target_s *target, int argc, const char **argv); +#endif static bool cmd_auto_scan(target_s *target, int argc, const char **argv); static bool cmd_frequency(target_s *target, int argc, const char **argv); static bool cmd_targets(target_s *target, int argc, const char **argv); @@ -94,6 +97,9 @@ const command_s cmd_list[] = { {"jtag_scan", cmd_jtag_scan, "Scan JTAG chain for devices"}, {"swd_scan", cmd_swd_scan, "Scan SWD interface for devices: [TARGET_ID]"}, {"swdp_scan", cmd_swd_scan, "Deprecated: use swd_scan instead"}, +#ifdef PLATFORM_HAS_RVSWD + {"rvswd_scan", cmd_rvswd_scan, "Scan RVSWD for devices"}, +#endif {"auto_scan", cmd_auto_scan, "Automatically scan all chain types for devices"}, {"frequency", cmd_frequency, "set minimum high and low times: [FREQ]"}, {"targets", cmd_targets, "Display list of available targets"}, @@ -316,6 +322,52 @@ bool cmd_swd_scan(target_s *target, int argc, const char **argv) return true; } +#ifdef PLATFORM_HAS_RVSWD +bool cmd_rvswd_scan(target_s *target, int argc, const char **argv) +{ + (void)target; + (void)argc; + (void)argv; + + if (platform_target_voltage()) + gdb_outf("Target voltage: %s\n", platform_target_voltage()); + + if (connect_assert_nrst) + platform_nrst_set_val(true); /* will be deasserted after attach */ + + bool scan_result = false; + TRY (EXCEPTION_ALL) { +#if CONFIG_BMDA == 1 + scan_result = bmda_rvswd_scan(); +#else + scan_result = rvswd_scan(); +#endif + } + CATCH () { + case EXCEPTION_TIMEOUT: + gdb_outf("Timeout during scan. Is target stuck in WFI?\n"); + break; + case EXCEPTION_ERROR: + gdb_outf("Exception: %s\n", exception_frame.msg); + break; + default: + break; + } + + if (!scan_result) { + platform_target_clk_output_enable(false); + platform_nrst_set_val(false); + gdb_out("RVSWD scan failed!\n"); + return false; + } + + cmd_targets(NULL, 0, NULL); + platform_target_clk_output_enable(false); + morse(NULL, false); + return true; +} +#endif + bool cmd_auto_scan(target_s *target, int argc, const char **argv) { (void)target; @@ -342,8 +394,18 @@ bool cmd_auto_scan(target_s *target, int argc, const char **argv) #else scan_result = adiv5_swd_scan(0); #endif - if (!scan_result) + if (!scan_result) { gdb_out("SWD scan found no devices.\n"); +#ifdef PLATFORM_HAS_RVSWD +#if CONFIG_BMDA == 1 + scan_result = bmda_rvswd_scan(); +#else + scan_result = rvswd_scan(); +#endif + if (!scan_result) + gdb_out("RVSWD scan found no devices.\n"); +#endif + } } } CATCH () { diff --git a/src/gdb_packet.c b/src/gdb_packet.c index acc0051be3a..89994ee63ef 100644 --- a/src/gdb_packet.c +++ b/src/gdb_packet.c @@ -81,6 +81,10 @@ void gdb_set_noackmode(bool enable) } #ifndef DEBUG_GDB_IS_NOOP +/* + * To see what packets GDB is seeing you can enable remote protocol debugging with: + * `(gdb) set debug remote 1` + */ static void gdb_packet_debug(const char *const func, const gdb_packet_s *const packet) { /* Log packet for debugging */ diff --git a/src/include/buffer_utils.h b/src/include/buffer_utils.h index 0f05a25e4f3..0340699a12e 100644 --- a/src/include/buffer_utils.h +++ b/src/include/buffer_utils.h @@ -84,4 +84,11 @@ static inline uint64_t read_be8(const uint8_t *const buffer, const size_t offset ((uint64_t)buffer[offset + 6] << 8U) | buffer[offset + 7]; } +static inline size_t write_char(char *const buffer, const size_t buffer_size, const size_t offset, const char c) +{ + if (buffer && offset < buffer_size) + buffer[offset] = c; + return offset + 1U; +} + #endif /*INCLUDE_BUFFER_UTILS_H*/ diff --git a/src/include/platform_support.h b/src/include/platform_support.h index 5e04ceb332b..1c34eb97583 100644 --- a/src/include/platform_support.h +++ b/src/include/platform_support.h @@ -50,6 +50,9 @@ void platform_timeout_set(platform_timeout_s *target, uint32_t ms); bool platform_timeout_is_expired(const platform_timeout_s *target); void platform_delay(uint32_t ms); +void platform_critical_enter(void); +void platform_critical_exit(void); + #define POWER_CONFLICT_THRESHOLD 5U /* in 0.1V, so 5 stands for 0.5V */ extern bool connect_assert_nrst; diff --git a/src/include/rvswd.h b/src/include/rvswd.h new file mode 100644 index 00000000000..125377053d6 --- /dev/null +++ b/src/include/rvswd.h @@ -0,0 +1,44 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2011 Black Sphere Technologies Ltd. + * Written by Gareth McMullin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef INCLUDE_RVSWD_H +#define INCLUDE_RVSWD_H + +#include +#include +#include + +/* Functions interface talking RVSWD */ +typedef struct rvswd_proc { + /* Perform a start condition */ + void (*start)(void); + /* Perform a start condition */ + void (*stop)(void); + /* Perform a clock_cycles read */ + uint32_t (*seq_in)(size_t clock_cycles); + /* Perform a clock_cycles write with the provided data */ + void (*seq_out)(uint32_t dio_states, size_t clock_cycles); +} rvswd_proc_s; + +extern rvswd_proc_s rvswd_proc; + +void rvswd_init(void); + +#endif /* INCLUDE_RVSWD_H */ diff --git a/src/include/target.h b/src/include/target.h index 5b4c9a15346..2ca65276791 100644 --- a/src/include/target.h +++ b/src/include/target.h @@ -45,9 +45,13 @@ typedef struct target_controller target_controller_s; #if CONFIG_BMDA == 1 bool bmda_swd_scan(uint32_t targetid); bool bmda_jtag_scan(void); +bool bmda_rvswd_scan(void); #endif bool adiv5_swd_scan(uint32_t targetid); bool jtag_scan(void); +// #ifdef PLATFORM_HAS_RVSWD +bool rvswd_scan(void); +// #endif size_t target_foreach(void (*callback)(size_t index, target_s *target, void *context), void *context); void target_list_free(void); diff --git a/src/platforms/common/meson.build b/src/platforms/common/meson.build index 4c2ce75a98b..ced755b32ea 100644 --- a/src/platforms/common/meson.build +++ b/src/platforms/common/meson.build @@ -34,6 +34,7 @@ platform_common_sources = files( 'aux_serial.c', 'jtagtap.c', 'swdptap.c', + 'rvswd.c', 'usb.c', 'usb_dfu_stub.c', 'usb_serial.c', diff --git a/src/platforms/common/rvswd.c b/src/platforms/common/rvswd.c new file mode 100644 index 00000000000..e686882285d --- /dev/null +++ b/src/platforms/common/rvswd.c @@ -0,0 +1,251 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2025 1BitSquared + * Written by Rafael Silva + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file implements the RVSWD interface + * + * It's mostly the same routines as the SWD interface, with some RVSWD specifics + */ + +#include "general.h" +#include "platform.h" +#include "timing.h" +#include "rvswd.h" +#include "maths_utils.h" + +/** + * RVSWD I/O is shared with SWD + */ +#define RVSWD_DIO_PORT SWDIO_PORT +#define RVSWD_DIO_PIN SWDIO_PIN + +#define RVSWD_CLK_PORT SWCLK_PORT +#define RVSWD_CLK_PIN SWCLK_PIN + +#define RVSWD_DIO_MODE_FLOAT SWDIO_MODE_FLOAT +#define RVSWD_DIO_MODE_DRIVE SWDIO_MODE_DRIVE + +typedef enum rvswd_direction_t { + RVSWD_DIRECTION_INPUT, + RVSWD_DIRECTION_OUTPUT +} rvswd_direction_t; + +rvswd_proc_s rvswd_proc; + +static void rvswd_start(void) __attribute__((optimize(3))); +static void rvswd_stop(void) __attribute__((optimize(3))); + +static uint32_t rvswd_seq_in(size_t clock_cycles) __attribute__((optimize(3))); +static uint32_t rvswd_seq_in_clk_delay(size_t clock_cycles) __attribute__((optimize(3))); +static uint32_t rvswd_seq_in_no_delay(size_t clock_cycles) __attribute__((optimize(3))); + +static void rvswd_seq_out(uint32_t dio_states, size_t clock_cycles) __attribute__((optimize(3))); +static void rvswd_seq_out_clk_delay(uint32_t dio_states, size_t clock_cycles) __attribute__((optimize(3))); +static void rvswd_seq_out_no_delay(uint32_t dio_states, size_t clock_cycles) __attribute__((optimize(3))); + +static inline void __attribute__((always_inline)) rvswd_hold_period(void) +{ + /* Hold for a period */ + for (volatile uint32_t counter = target_clk_divider + 1U; counter > 0U; --counter) + continue; +} + +void rvswd_init(void) +{ + rvswd_proc.start = rvswd_start; + rvswd_proc.stop = rvswd_stop; + rvswd_proc.seq_in = rvswd_seq_in; + rvswd_proc.seq_out = rvswd_seq_out; +} + +static void rvswd_set_dio_direction(rvswd_direction_t direction) +{ + /* Do nothing if the direction is already set */ + /* FIXME: this internal state may become invalid if the IO is modified elsewhere (e.g. SWD) */ + static rvswd_direction_t current_direction = RVSWD_DIRECTION_INPUT; + if (direction == current_direction) + return; + + /* Change the direction */ + if (direction == RVSWD_DIRECTION_OUTPUT) { + RVSWD_DIO_MODE_DRIVE(); + } else { + RVSWD_DIO_MODE_FLOAT(); + } + current_direction = direction; +} + +static void rvswd_start(void) +{ + /* + * DIO falling edge while CLK is idle high marks a START condition + */ + + /* Setup for the start sequence by setting the bus to the idle state */ + rvswd_set_dio_direction(RVSWD_DIRECTION_OUTPUT); + gpio_set(RVSWD_DIO_PORT, RVSWD_DIO_PIN); + gpio_set(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + + /* Ensure the bus is idle for a period */ + rvswd_hold_period(); + + /* Generate the start condition */ + gpio_clear(RVSWD_DIO_PORT, RVSWD_DIO_PIN); + rvswd_hold_period(); +} + +static void rvswd_stop(void) +{ + /* + * DIO rising edge while CLK is idle high marks a STOP condition + */ + + /* Setup for the stop condition by driving the CLK and DIO low */ + gpio_clear(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + rvswd_set_dio_direction(RVSWD_DIRECTION_OUTPUT); + gpio_clear(RVSWD_DIO_PORT, RVSWD_DIO_PIN); + + /* Ensure setup for a period */ + rvswd_hold_period(); + + /* Generate the stop condition */ + gpio_set(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + rvswd_hold_period(); + gpio_set(RVSWD_DIO_PORT, RVSWD_DIO_PIN); +} + +static uint32_t rvswd_seq_in_clk_delay(const size_t clock_cycles) +{ + uint32_t value = 0; /* Return value */ + + /* Shift clock_cycles bits in */ + for (size_t cycle = clock_cycles; cycle > 0; --cycle) { + /* Drive the CLK low and hold for a period */ + gpio_clear(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + rvswd_hold_period(); + + /* Sample the DIO line and Raise the CLK, then hold for a period */ + value |= gpio_get(RVSWD_DIO_PORT, RVSWD_DIO_PIN) ? (1U << (cycle - 1U)) : 0U; + gpio_set(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + rvswd_hold_period(); + } + + // /* Leave the CLK low and return the value */ + // gpio_clear(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + + /* Leave the CLK high and return the value */ + return value; +} + +static uint32_t rvswd_seq_in_no_delay(const size_t clock_cycles) +{ + uint32_t value = 0U; /* Return value */ + + /* Shift clock_cycles bits in */ + for (size_t cycle = clock_cycles; cycle > 0; --cycle) { + /* Drive the CLK low */ + gpio_clear(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + + /* Sample the DIO line and raise the CLK */ + value |= gpio_get(RVSWD_DIO_PORT, RVSWD_DIO_PIN) ? (1U << (cycle - 1U)) : 0U; + gpio_set(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + } + + // /* Leave the CLK low and return the value */ + // gpio_clear(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + + /* Leave the CLK high and return the value */ + return value; +} + +static uint32_t rvswd_seq_in(size_t clock_cycles) +{ + /* Set the DIO line to float to give control to the target */ + rvswd_set_dio_direction(RVSWD_DIRECTION_INPUT); + + /* Delegate to the appropriate sequence in routine depending on the clock divider */ + if (target_clk_divider != UINT32_MAX) + return rvswd_seq_in_clk_delay(clock_cycles); + else + return rvswd_seq_in_no_delay(clock_cycles); +} + +static void rvswd_seq_out_clk_delay(const uint32_t dio_states, const size_t clock_cycles) +{ + /* Shift clock_cycles bits out */ + for (size_t cycle = clock_cycles; cycle > 0; --cycle) { + /* Drive the CLK low and setup the DIO line, then hold for a period */ + gpio_clear(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + gpio_set_val(SWDIO_PORT, SWDIO_PIN, dio_states & (1U << (cycle - 1U))); + rvswd_hold_period(); + + /* Raise the CLK and hold for a period */ + gpio_set(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + rvswd_hold_period(); + } + + // /* Leave the CLK low */ + // gpio_clear(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + + /* Leave the CLK high and return */ +} + +static void rvswd_seq_out_no_delay(const uint32_t dio_states, const size_t clock_cycles) +{ + /* Shift clock_cycles bits out */ + for (size_t cycle = clock_cycles; cycle > 0; --cycle) { + /* Drive the CLK low and setup the DIO line */ + gpio_clear(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + gpio_set_val(SWDIO_PORT, SWDIO_PIN, dio_states & (1U << (cycle - 1U))); + + /* Raise the CLK */ + gpio_set(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + } + + // /* Leave the CLK low */ + // gpio_clear(RVSWD_CLK_PORT, RVSWD_CLK_PIN); + + /* Leave the CLK high and return */ +} + +static void rvswd_seq_out(const uint32_t dio_states, const size_t clock_cycles) +{ + /* Set the DIO line to drive to give us control */ + rvswd_set_dio_direction(RVSWD_DIRECTION_OUTPUT); + + /* Delegate to the appropriate sequence in routine depending on the clock divider */ + if (target_clk_divider != UINT32_MAX) + rvswd_seq_out_clk_delay(dio_states, clock_cycles); + else + rvswd_seq_out_no_delay(dio_states, clock_cycles); +} diff --git a/src/platforms/common/stm32/critical.c b/src/platforms/common/stm32/critical.c new file mode 100644 index 00000000000..1159b0a45b7 --- /dev/null +++ b/src/platforms/common/stm32/critical.c @@ -0,0 +1,62 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2025 1BitSquared + * Written by Rafael Silva + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file implements the RVSWD interface + * + * It's mostly the same routines as the SWD interface, with some RVSWD specifics + */ + +#include "general.h" +#include "platform.h" + +#include + +static uint32_t nested_critical = 0; +static uint32_t primask = 0; + +void platform_critical_enter(void) +{ + if (nested_critical == 0) { + primask = cm_mask_interrupts(1U); + } + nested_critical++; +} + +void platform_critical_exit(void) +{ + nested_critical--; + if (nested_critical == 0) { + cm_mask_interrupts(primask); + } +} diff --git a/src/platforms/common/stm32/meson.build b/src/platforms/common/stm32/meson.build index cb31141ce27..754c3317a44 100644 --- a/src/platforms/common/stm32/meson.build +++ b/src/platforms/common/stm32/meson.build @@ -31,6 +31,7 @@ platform_stm32_includes = include_directories('.') platform_stm32_sources = files( + 'critical.c', 'serialno.c', 'timing_stm32.c', ) diff --git a/src/platforms/hosted/bmp_libusb.c b/src/platforms/hosted/bmp_libusb.c index c0d060cbb13..9b5237d9e5f 100644 --- a/src/platforms/hosted/bmp_libusb.c +++ b/src/platforms/hosted/bmp_libusb.c @@ -76,6 +76,7 @@ static const debugger_device_s debugger_devices[] = { {VENDOR_ID_STLINK, PRODUCT_ID_STLINKV3, PROBE_TYPE_STLINK_V2, NULL, "ST-Link v3"}, {VENDOR_ID_STLINK, PRODUCT_ID_STLINKV3E, PROBE_TYPE_STLINK_V2, NULL, "ST-Link v3E"}, {VENDOR_ID_SEGGER, PRODUCT_ID_ANY, PROBE_TYPE_JLINK, NULL, "Segger J-Link"}, + {VENDOR_ID_WCH, PRODUCT_ID_WCHLINK_RV, PROBE_TYPE_WCHLINK, NULL, "WCH-Link"}, {VENDOR_ID_FTDI, PRODUCT_ID_FTDI_FT2232, PROBE_TYPE_FTDI, NULL, "FTDI FT2232"}, {VENDOR_ID_FTDI, PRODUCT_ID_FTDI_FT4232, PROBE_TYPE_FTDI, NULL, "FTDI FT4232"}, {VENDOR_ID_FTDI, PRODUCT_ID_FTDI_FT232, PROBE_TYPE_FTDI, NULL, "FTDI FT232"}, @@ -118,7 +119,7 @@ const debugger_device_s *get_debugger_device_from_vid_pid(const uint16_t probe_v void bmp_ident(bmda_probe_s *info) { DEBUG_INFO("Black Magic Debug App " FIRMWARE_VERSION "\n for Black Magic Probe, ST-Link v2 and v3, CMSIS-DAP, " - "J-Link and FTDI (MPSSE)\n"); + "J-Link, FTDI (MPSSE) and WCH-Link\n"); if (info && info->vid && info->pid) { DEBUG_INFO("Using %04x:%04x %s %s\n %s %s\n", info->vid, info->pid, (info->serial[0]) ? info->serial : NO_SERIAL_NUMBER, info->manufacturer, info->product, info->version); diff --git a/src/platforms/hosted/meson.build b/src/platforms/hosted/meson.build index 4b679c2c217..13c0d511113 100644 --- a/src/platforms/hosted/meson.build +++ b/src/platforms/hosted/meson.build @@ -54,6 +54,9 @@ bmda_sources = files( 'jlink.c', 'jlink_jtag.c', 'jlink_swd.c', + 'wchlink.c', + 'wchlink_rvswd.c', + 'wchlink_riscv_dtm.c', ) subdir('remote') diff --git a/src/platforms/hosted/platform.c b/src/platforms/hosted/platform.c index f3258110f58..fed894b4a6e 100644 --- a/src/platforms/hosted/platform.c +++ b/src/platforms/hosted/platform.c @@ -31,6 +31,7 @@ #include "platform.h" #include "jtagtap.h" #include "swd.h" +#include "rvswd.h" #include "target.h" #include "target_internal.h" #include "adiv5.h" @@ -51,6 +52,7 @@ #include "stlinkv2.h" #include "ftdi_bmp.h" #include "jlink.h" +#include "wchlink.h" #include "cmsis_dap.h" #endif @@ -63,6 +65,7 @@ bmda_probe_s bmda_probe_info; #ifndef ENABLE_GPIOD jtag_proc_s jtag_proc; swd_proc_s swd_proc; +rvswd_proc_s rvswd_proc; #endif static uint32_t max_frequency = 4000000U; @@ -163,6 +166,11 @@ void platform_init(int argc, char **argv) if (!jlink_init()) exit(1); break; + + case PROBE_TYPE_WCHLINK: + if (!wchlink_init()) + exit(-1); + break; #endif #ifdef ENABLE_GPIOD @@ -190,6 +198,16 @@ void platform_init(int argc, char **argv) } } +void platform_critical_enter(void) +{ + /* Nothing to do */ +} + +void platform_critical_exit(void) +{ + /* Nothing to do */ +} + bool bmda_swd_scan(const uint32_t targetid) { bmda_probe_info.is_jtag = false; @@ -309,6 +327,21 @@ bool bmda_jtag_init(void) } } +bool bmda_rvswd_scan() +{ + bmda_probe_info.is_jtag = false; + + switch (bmda_probe_info.type) { +#if HOSTED_BMP_ONLY == 0 + case PROBE_TYPE_WCHLINK: + return wchlink_rvswd_scan(); +#endif + + default: + return false; + } +} + void bmda_adiv5_dp_init(adiv5_debug_port_s *const dp) { switch (bmda_probe_info.type) { @@ -411,6 +444,9 @@ char *bmda_adaptor_ident(void) case PROBE_TYPE_JLINK: return "J-Link"; + case PROBE_TYPE_WCHLINK: + return "WCH-Link"; + case PROBE_TYPE_GPIOD: return "GPIOD"; diff --git a/src/platforms/hosted/platform.h b/src/platforms/hosted/platform.h index 3747ef1169f..e220166dffb 100644 --- a/src/platforms/hosted/platform.h +++ b/src/platforms/hosted/platform.h @@ -48,6 +48,7 @@ void platform_buffer_flush(void); do { \ } while (0) #define PLATFORM_HAS_POWER_SWITCH +#define PLATFORM_HAS_RVSWD #define PRODUCT_ID_ANY 0xffffU @@ -77,6 +78,11 @@ void platform_buffer_flush(void); #define VENDOR_ID_ORBCODE 0x1209U #define PRODUCT_ID_ORBTRACE 0x3443U +#define VENDOR_ID_WCH 0x1a86U +#define PRODUCT_ID_WCHLINK_RV 0x8010U /* WCH-Link and WCH-LinkE in mode RV */ +#define PRODUCT_ID_WCHLINK_DAP 0x8011U /* WCH-Link in mode DAP */ +#define PRODUCT_ID_WCHLINKE_DAP 0x8012U /* WCH-LinkE in mode DAP */ + typedef enum probe_type { PROBE_TYPE_NONE = 0, PROBE_TYPE_BMP, @@ -84,6 +90,7 @@ typedef enum probe_type { PROBE_TYPE_FTDI, PROBE_TYPE_CMSIS_DAP, PROBE_TYPE_JLINK, + PROBE_TYPE_WCHLINK, PROBE_TYPE_GPIOD, } probe_type_e; diff --git a/src/platforms/hosted/wchlink.c b/src/platforms/hosted/wchlink.c new file mode 100644 index 00000000000..78c58c9126d --- /dev/null +++ b/src/platforms/hosted/wchlink.c @@ -0,0 +1,387 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rafael Silva + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "wchlink.h" +#include "wchlink_protocol.h" +#include "buffer_utils.h" + +typedef struct wchlink { + struct wchlink_fw_version { + uint8_t major; + uint8_t minor; + } fw_version; /* Firmware version */ + + uint8_t hw_type; /* Hardware type */ + + uint8_t riscvchip; /* The attached RISC-V chip code */ +} wchlink_s; + +static wchlink_s wchlink; + +/* WCH-Link USB protocol functions */ + +static char *wchlink_command_error(const uint8_t command, const uint8_t subcommand, const uint8_t error) +{ + /* Only this error is formally known, so we hack it's identification a bit for now */ + if (command == WCH_CMD_CONTROL && subcommand == WCH_CONTROL_SUBCMD_ATTACH && error == WCH_ERR_ATTACH) + return "Failed to attach to target"; + return "Unknown"; +} + +/* + * Send a command to the WCH-Link. + * + * ┌─────────────┬────────┬─────────┬──────────────┬──────────────────────────────┐ + * │ Byte │ 0 │ 1 │ 2 │ 3:End │ + * ├─────────────┼────────┼─────────┼──────────────┼──────────────────────────────┤ + * │ Description │ Header │ Command │ Payload Size │ Payload (Sub-command + Data) │ + * └─────────────┴────────┴─────────┴──────────────┴──────────────────────────────┘ + * See wchlink_protocol.h for more information. + * + * Returns true for success, false for failure. + */ +bool wchlink_command_send_recv(const uint8_t command, const uint8_t subcommand, const void *const payload, + const size_t payload_length, void *const response, const size_t response_length) +{ + /* + * Total request size is packet header + command + payload size + payload (for which we always add the subcommand byte) + * Total response size is packet header + command/error + payload size + payload + */ + const size_t request_size = 4U + payload_length; + const size_t response_size = 3U + response_length; + + /* Stack buffer for the transfer, this is much larger than we need */ + uint8_t buffer[256U] = {0}; + if (request_size > sizeof(buffer) || response_size > sizeof(buffer)) + return false; + + /* Prepare the command packet */ + buffer[WCH_CMD_PACKET_HEADER_OFFSET] = WCH_CMD_PACKET_HEADER_OUT; /* Command packet header */ + buffer[WCH_CMD_PACKET_CMD_ERROR_OFFSET] = command; /* Command */ + buffer[WCH_CMD_PACKET_SIZE_OFFSET] = payload_length + 1U; /* Payload size */ + buffer[WCH_CMD_PACKET_PAYLOAD_OFFSET] = subcommand; /* Subcommand as the first byte of the payload */ + + /* Copy in the payload if any */ + if (payload_length && payload) + memcpy(buffer + WCH_CMD_PACKET_PAYLOAD_OFFSET + 1U, payload, payload_length); + + /* Send the command and receive the response */ + if (bmda_usb_transfer(bmda_probe_info.usb_link, buffer, request_size, buffer, response_size, WCH_USB_TIMEOUT) < 0) + return false; + + /* Check the response */ + if (buffer[WCH_CMD_PACKET_HEADER_OFFSET] != WCH_CMD_PACKET_HEADER_IN) { + DEBUG_ERROR("wchlink protocol error: malformed response\n"); + return false; + } + if (buffer[WCH_CMD_PACKET_CMD_ERROR_OFFSET] != command) { + DEBUG_ERROR("wchlink protocol error: 0x%02x - %s\n", buffer[WCH_CMD_PACKET_CMD_ERROR_OFFSET], + wchlink_command_error(command, subcommand, buffer[WCH_CMD_PACKET_CMD_ERROR_OFFSET])); + return false; + } + if (buffer[WCH_CMD_PACKET_SIZE_OFFSET] != response_length) { + DEBUG_ERROR("wchlink protocol error: response payload size mismatch\n"); + return false; + } + + /* Copy the response payload if requested */ + if (response_length && response) + memcpy(response, buffer + WCH_CMD_PACKET_PAYLOAD_OFFSET, response_length); + + return true; +} + +/* + * Do a DMI transfer. + * + * ┌────────────────────────────┐ + * │ Payload │ + * ├─────────┬──────┬───────────┤ + * │ 0 │ 1:4 │ 5 │ + * ├─────────┼──────┼───────────┤ + * │ Address │ Data │ Operation │ + * └─────────┴──────┴───────────┘ + * ┌────────────────────────────┐ + * │ Response payload │ + * ├─────────┬──────┬───────────┤ + * │ 0 │ 1:4 │ 5 │ + * ├─────────┼──────┼───────────┤ + * │ Address │ Data │ Status │ + * └─────────┴──────┴───────────┘ + * See wchlink_protocol.h for more information. + * + * Returns true for success, false for failure. + */ +bool wchlink_transfer_dmi(const uint8_t operation, const uint32_t address, const uint32_t data_in, + uint32_t *const data_out, uint8_t *const status) +{ + /* The DMI register address must be a 7 or 8-bit address */ + if (address & ~0xffU) { + DEBUG_ERROR("wchlink protocol error: DMI address 0x%08" PRIx32 " is out of range\n", address); + return false; + } + + /* Stack buffer for the transfer */ + uint8_t buffer[9U] = {0}; + + /* Prepare the command packet */ + buffer[WCH_CMD_PACKET_HEADER_OFFSET] = WCH_CMD_PACKET_HEADER_OUT; /* Command packet header */ + buffer[WCH_CMD_PACKET_CMD_ERROR_OFFSET] = WCH_CMD_DMI; /* Command */ + buffer[WCH_CMD_PACKET_SIZE_OFFSET] = 6U; /* Payload size */ + + /* Construct the payload */ + buffer[WCH_CMD_PACKET_PAYLOAD_OFFSET + WCH_DMI_ADDR_OFFSET] = address & 0xffU; /* Address */ + write_be4(buffer, WCH_CMD_PACKET_PAYLOAD_OFFSET + WCH_DMI_DATA_OFFSET, data_in); /* Data */ + buffer[WCH_CMD_PACKET_PAYLOAD_OFFSET + WCH_DMI_OP_STATUS_OFFSET] = operation; /* Operation */ + + /* Send the command and receive the response */ + if (bmda_usb_transfer(bmda_probe_info.usb_link, buffer, sizeof(buffer), buffer, sizeof(buffer), WCH_USB_TIMEOUT) < + 0) + return false; + + /* Check the response */ + if (buffer[WCH_CMD_PACKET_HEADER_OFFSET] != WCH_CMD_PACKET_HEADER_IN) { + DEBUG_ERROR("wchlink protocol error: malformed response\n"); + return false; + } + if (buffer[WCH_CMD_PACKET_CMD_ERROR_OFFSET] != WCH_CMD_DMI) { + DEBUG_ERROR("wchlink protocol error: 0x%02x - %s\n", buffer[WCH_CMD_PACKET_CMD_ERROR_OFFSET], + wchlink_command_error(WCH_CMD_DMI, 0, buffer[WCH_CMD_PACKET_CMD_ERROR_OFFSET])); + return false; + } + if (buffer[WCH_CMD_PACKET_SIZE_OFFSET] != 6U) { + DEBUG_ERROR("wchlink protocol error: response payload size mismatch\n"); + return false; + } + + /* Copy over the result */ + if (data_out) + *data_out = read_be4(buffer, WCH_CMD_PACKET_PAYLOAD_OFFSET + WCH_DMI_DATA_OFFSET); + if (status) + *status = buffer[WCH_CMD_PACKET_PAYLOAD_OFFSET + WCH_DMI_OP_STATUS_OFFSET]; + + return true; +} + +/* + * Try to claim the debugging interface of a WCH-Link. + * On success this copies the command endpoint addresses identified into the + * usb_link_s sub-structure of bmda_probe_s (bmda_probe_info.usb_link) for later use. + * Returns true for success, false for failure. + */ +static bool wchlink_claim_interface(void) +{ + libusb_config_descriptor_s *config = NULL; + const int result = libusb_get_active_config_descriptor(bmda_probe_info.libusb_dev, &config); + if (result != LIBUSB_SUCCESS) { + DEBUG_ERROR("Failed to get configuration descriptor: %s\n", libusb_error_name(result)); + return false; + } + const libusb_interface_descriptor_s *descriptor = NULL; + for (size_t idx = 0; idx < config->bNumInterfaces; ++idx) { + const libusb_interface_s *const interface = &config->interface[idx]; + const libusb_interface_descriptor_s *const interface_desc = &interface->altsetting[0]; + if (interface_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC && + interface_desc->bInterfaceSubClass == WCH_USB_INTERFACE_SUBCLASS && interface_desc->bNumEndpoints > 1U) { + const int claim_result = libusb_claim_interface(bmda_probe_info.usb_link->device_handle, (int)idx); + if (claim_result) { + DEBUG_ERROR("Can not claim handle: %s\n", libusb_error_name(claim_result)); + return false; + } + bmda_probe_info.usb_link->interface = idx; + descriptor = interface_desc; + break; + } + } + if (!descriptor) { + DEBUG_ERROR("No suitable interface found\n"); + libusb_free_config_descriptor(config); + return false; + } + for (size_t i = 0; i < descriptor->bNumEndpoints; i++) { + const libusb_endpoint_descriptor_s *endpoint = &descriptor->endpoint[i]; + if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_ADDRESS_MASK) == WCH_USB_MODE_RV_CMD_EPT_ADDR) { + if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + bmda_probe_info.usb_link->ep_rx = endpoint->bEndpointAddress; + else + bmda_probe_info.usb_link->ep_tx = endpoint->bEndpointAddress; + } + } + libusb_free_config_descriptor(config); + return true; +} + +/* WCH-Link command functions */ + +static char *wchlink_hw_type_to_string(const uint8_t hardware_id) +{ + switch (hardware_id) { + case WCH_HARDWARE_TYPE_WCHLINK: + return "WCH-Link (CH549)"; + case WCH_HARDWARE_TYPE_WCHLINKE2: + case WCH_HARDWARE_TYPE_WCHLINKE: + return "WCH-LinkE (CH32V305)"; + case WCH_HARDWARE_TYPE_WCHLINKS: + return "WCH-LinkS (CH32V203)"; + case WCH_HARDWARE_TYPE_WCHLINKB: + return "WCH-LinkB"; + case WCH_HARDWARE_TYPE_WCHLINKW: + return "WCH-LinkW (CH32V208)"; + default: + return "Unknown"; + } +} + +static char *wchlink_riscvchip_to_string(const uint8_t hardware_id) +{ + switch (hardware_id) { + case WCH_RISCVCHIP_CH32V103: + return "CH32V103 RISC-V3A series"; + case WCH_RISCVCHIP_CH57X: + return "CH571/CH573 RISC-V3A BLE 4.2 series"; + case WCH_RISCVCHIP_CH56X: + return "CH565/CH569 RISC-V3A series"; + case WCH_RISCVCHIP_CH32V20X: + return "CH32V20X RISC-V4B/V4C series"; + case WCH_RISCVCHIP_CH32V30X: + return "CH32V30X RISC-V4C/V4F series"; + case WCH_RISCVCHIP_CH58X: + return "CH581/CH582/CH583 RISC-V4A BLE 5.3 series"; + case WCH_RISCVCHIP_CH32V003: + return "CH32V003 RISC-V2A series"; + case WCH_RISCVCHIP_CH59X: + return "CH59x RISC-V4C BLE 5.4 series"; + case WCH_RISCVCHIP_CH32X035: + return "CH32X035 RISC-V4C series"; + default: + return "Unknown"; + } +} + +static bool wchlink_get_version(void) +{ + uint8_t response[4U]; + if (!wchlink_command_send_recv( + WCH_CMD_CONTROL, WCH_CONTROL_SUBCMD_GET_PROBE_INFO, NULL, 0, response, sizeof(response))) + return false; + + wchlink.fw_version.major = response[WCH_VERSION_MAJOR_OFFSET]; + wchlink.fw_version.minor = response[WCH_VERSION_MINOR_OFFSET]; + DEBUG_INFO("Firmware version: v%" PRIu32 ".%" PRIu32 "\n", wchlink.fw_version.major, wchlink.fw_version.minor); + + const uint8_t hardware_type = response[WCH_HARDWARE_TYPE_OFFSET]; + DEBUG_INFO("Hardware type: %s\n", wchlink_hw_type_to_string(hardware_type)); + + /* Build version string onto info struct for version command */ + snprintf(bmda_probe_info.version, sizeof(bmda_probe_info.version), "%s v%" PRIu32 ".%" PRIu32, + wchlink_hw_type_to_string(hardware_type), wchlink.fw_version.major, wchlink.fw_version.minor); + + return true; +} + +/* + * This function is called when the WCH-Link attaches to certain types of RISC-V chip + * It is unknown what this function does, but the official WCH-Link software calls it + * + * Removing this function still allows the WCH-Link to work and the scan is successful + * but it is unknown if it might required for some chips or states + */ +static bool wchlink_after_attach_unknown() +{ + DEBUG_INFO("Sending unknown WCH-Link command after attach\n"); + + /* Response seems to be WCH_CONTROL_SUBCMD_UNKNOWN, but without knowing what the command does we won't check it blindly */ + uint8_t response = 0; + return wchlink_command_send_recv(WCH_CMD_CONTROL, WCH_CONTROL_SUBCMD_UNKNOWN, NULL, 0, &response, sizeof(response)); +} + +/* WCH-Link attach routine, attempts to detect and attach to a connected RISC-V chip */ +bool wchlink_attach() +{ + uint8_t response[5U]; + if (!wchlink_command_send_recv(WCH_CMD_CONTROL, WCH_CONTROL_SUBCMD_ATTACH, NULL, 0, response, sizeof(response))) + return false; + + wchlink.riscvchip = response[WCH_RISCVCHIP_OFFSET]; + const uint32_t idcode = read_be4(response, WCH_IDCODDE_OFFSET); + + DEBUG_INFO("WCH-Link attached to RISC-V chip: %s\n", wchlink_riscvchip_to_string(wchlink.riscvchip)); + DEBUG_INFO("ID code: 0x%08" PRIx32 "\n", idcode); + + /* Some RISC-V chips require* an additional command to be sent after attach */ + switch (wchlink.riscvchip) { + case WCH_RISCVCHIP_CH32V103: + case WCH_RISCVCHIP_CH32V20X: + case WCH_RISCVCHIP_CH32V30X: + case WCH_RISCVCHIP_CH32V003: + if (!wchlink_after_attach_unknown()) + return false; + break; + default: + break; + } + + return true; +} + +bool wchlink_init(void) +{ + usb_link_s *link = calloc(1U, sizeof(usb_link_s)); + if (!link) + return false; + bmda_probe_info.usb_link = link; + link->context = bmda_probe_info.libusb_ctx; + const int result = libusb_open(bmda_probe_info.libusb_dev, &link->device_handle); + if (result != LIBUSB_SUCCESS) { + DEBUG_ERROR("libusb_open() failed (%d): %s\n", result, libusb_error_name(result)); + return false; + } + if (!wchlink_claim_interface()) { + libusb_close(bmda_probe_info.usb_link->device_handle); + return false; + } + if (!link->ep_tx || !link->ep_rx) { + DEBUG_ERROR("Device setup failed\n"); + libusb_release_interface(bmda_probe_info.usb_link->device_handle, bmda_probe_info.usb_link->interface); + libusb_close(bmda_probe_info.usb_link->device_handle); + return false; + } + if (!wchlink_get_version()) { + DEBUG_ERROR("Failed to read WCH-Link information\n"); + libusb_release_interface(bmda_probe_info.usb_link->device_handle, bmda_probe_info.usb_link->interface); + libusb_close(bmda_probe_info.usb_link->device_handle); + return false; + } + return true; +} diff --git a/src/platforms/hosted/wchlink.h b/src/platforms/hosted/wchlink.h new file mode 100644 index 00000000000..8e6a283dca2 --- /dev/null +++ b/src/platforms/hosted/wchlink.h @@ -0,0 +1,42 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rafael Silva + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PLATFORMS_HOSTED_WCHLINK_H +#define PLATFORMS_HOSTED_WCHLINK_H + +#include "bmp_hosted.h" + +bool wchlink_init(void); +bool wchlink_rvswd_scan(void); +void wchlink_riscv_dtm_handler(void); + +#endif /* PLATFORMS_HOSTED_WCHLINK_H */ diff --git a/src/platforms/hosted/wchlink_protocol.h b/src/platforms/hosted/wchlink_protocol.h new file mode 100644 index 00000000000..4de7b70695d --- /dev/null +++ b/src/platforms/hosted/wchlink_protocol.h @@ -0,0 +1,324 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rafael Silva + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLATFORMS_HOSTED_WCHLINK_PROTOCOL_H +#define PLATFORMS_HOSTED_WCHLINK_PROTOCOL_H + +#include +#include +#include + +/* + * This file contains the definitions for the WCH-Link USB Protocol, no public documentation is available + * so these definitions are the result of reverse engineering the protocol and trial and error. + * + * !!! THIS IS LARGELY INCOMPLETE AND UNTESTED, DO NOT TAKE THIS AS A DEFINITIVE SOURCE OF INFORMATION !!! + * + * The WCH-Link has two modes of operation, DAPLink and RV (i.e. RISC-V). + * This refers to the RV mode of operation only, changing the mode of operation is also out of scope. + * This was based on probes with firmware v2.5 and v2.8, differences are expected on untested/future versions. + * + * Overview + * + * WCH-Link uses USB Bulk Transfers to communicate with the host + * + * The WCH-Link exposes 4 endpoints through a Vendor interface: + * 0x82: EP 2 IN (Raw data) + * 0x02: EP 2 OUT (Raw data) + * 0x81: EP 1 IN (Command packets) + * 0x01: EP 1 OUT (Command packets) + * EP 1 IN/OUT is used for most of the communication, EP 2 IN/OUT is used for some flash related operations. + * + * Command packet format: + * ┌─────────────┬────────┬─────────┬──────────────┬──────────────────────────────┐ + * │ Byte │ 0 │ 1 │ 2 │ 3:End │ + * ├─────────────┼────────┼─────────┼──────────────┼──────────────────────────────┤ + * │ Description │ Header │ Command │ Payload Size │ Payload (Sub-command + Data) │ + * └─────────────┴────────┴─────────┴──────────────┴──────────────────────────────┘ + * + * Header: + * - 0x81 for host command packets + * - 0x82 for device response packets + * + * Command: command, used to identify how the payload will be interpreted. + * Payload Size: length in bytes of the remaining command data. + * Payload: command data, interpreted according to the command, most command have a subcommand as the 1st byte. + * + * Responses are sent in the same format, with the header set to 0x82 and the same command. + * In case of an error, the response will contain the error value instead of the requested command in the command field. + */ + +/* USB protocol */ +#define WCH_USB_MODE_RV_CMD_EPT_ADDR 0x1U +#define WCH_USB_MODE_RV_RAW_EPT_ADDR 0x2U +#define WCH_USB_MODE_DAP_OUT_EPT_ADDR 0x2U +#define WCH_USB_MODE_DAP_IN_EPT_ADDR 0x3U + +#define WCH_USB_TIMEOUT 5000U + +#define WCH_USB_INTERFACE_SUBCLASS 0x80U + +/* Command packet */ +#define WCH_CMD_PACKET_HEADER_OFFSET 0U +#define WCH_CMD_PACKET_HEADER_OUT 0x81U +#define WCH_CMD_PACKET_HEADER_IN 0x82U +#define WCH_CMD_PACKET_CMD_ERROR_OFFSET 1U +#define WCH_CMD_PACKET_SIZE_OFFSET 2U +#define WCH_CMD_PACKET_PAYLOAD_OFFSET 3U + +/* Error */ +#define WCH_ERR_ATTACH 0x55U /* Failed to attach to target */ + +/* RISC-V targets AKA "riscvchip" */ +#define WCH_RISCVCHIP_CH32V103 0x01U /* CH32V103 RISC-V3A series */ +#define WCH_RISCVCHIP_CH57X 0x02U /* CH571/CH573 RISC-V3A BLE 4.2 series */ +#define WCH_RISCVCHIP_CH56X 0x03U /* CH565/CH569 RISC-V3A series */ +#define WCH_RISCVCHIP_CH32V20X 0x05U /* CH32V20X RISC-V4B/V4C series */ +#define WCH_RISCVCHIP_CH32V30X 0x06U /* CH32V30X RISC-V4C/V4F series */ +#define WCH_RISCVCHIP_CH58X 0x07U /* CH581/CH582/CH583 RISC-V4A BLE 5.3 series */ +#define WCH_RISCVCHIP_CH32V003 0x09U /* CH32V003 RISC-V2A series */ +#define WCH_RISCVCHIP_CH59X 0x0bU /* CH59x RISC-V4C BLE 5.4 series */ +#define WCH_RISCVCHIP_CH32X035 0x0dU /* CH32X035 RISC-V4C series */ + +/* Commands */ +#define WCH_CMD_ADDR_N_SIZE 0x01U /* Set address and size command */ +#define WCH_CMD_FLASH 0x02U /* Flash command */ +#define WCH_CMD_READ_MEM 0x03U /* Memory read command */ +#define WCH_CMD_PROTECT 0x06U /* Flash potection command */ +#define WCH_CMD_DMI 0x08U /* DMI transfer command */ +#define WCH_CMD_RESET 0x0bU /* Reset command */ +#define WCH_CMD_PROBE_CONTROL 0x0cU /* Probe control command */ +#define WCH_CMD_CONTROL 0x0dU /* Control command */ +#define WCH_CMD_RV_DIS_DBG 0x0eU /* RV disable debug command */ +#define WCH_CMD_VERIFY 0x0fU /* Verify command */ +#define WCH_CMD_UID 0x11U /* Chip UID command */ +#define WCH_CMD_MODDE_SWITCH 0xffU /* Switch probe mode command */ + +/* + * Set address and size command - WCH_CMD_ADDR_N_SIZE + * + * This command does not have a sub-command byte, + * the payload is the address followed by the size + * + * ┌──────┬──────┐ + * │ 0:4 │ 5:8 │ + * ├──────┼──────┤ + * │ ADDR │ SIZE │ + * └──────┴──────┘ + */ + +/* Flash command - WCH_CMD_FLASH */ +#define WCH_FLASH_SUBCMD_CHIPERASE 0x01U /* Chip erase */ +#define WCH_FLASH_SUBCMD_BEGIN_WRITE_FLASH 0x02U /* Begin write flash - ?? */ +#define WCH_FLASH_SUBCMD_EXEC_RAM 0x03U /* Execute ram - ?? */ +#define WCH_FLASH_SUBCMD_BEGIN_WRITE_MEM 0x05U /* Begin transfer - ?? */ +#define WCH_FLASH_SUBCMD_PREPARE 0x06U /* ?? */ +#define WCH_FLASH_SUBCMD_EXEC_MEM 0x07U /* ?? */ +#define WCH_FLASH_SUBCMD_TERMINATE 0x08U /* ?? */ +#define WCH_FLASH_SUBCMD_READY_WRITE 0x09U /* End transfer - ?? */ +#define WCH_FLASH_SUBCMD_VERIFY2 0x0aU /* Verify - ?? */ +#define WCH_FLASH_SUBCMD_RV_VERIFY 0x0bU /* Verify - ?? */ +#define WCH_FLASH_SUBCMD_BEGIN_READ_MEM 0x0cU /* ?? */ + +/* + * Memory read commands - WCH_CMD_READ_MEM + * + * This command does not have a sub-command byte, + * the payload is the address to read from followed by the number of bytes to read + * + * ┌──────┬────────┐ + * │ 0:4 │ 5:8 │ + * ├──────┼────────┤ + * │ ADDR │ LENGTH │ + * └──────┴────────┘ + */ + +/* + * Flash potection command - WCH_CMD_PROTECT + * + * Not supported on riscvchip: + * - 0x01: CH32V103 + * - 0x09: CH32V003 + * - 0x05: CH32V20X + * - 0x06: CH32V30X + * - 0x0d: CH32X035 + */ +#define WCH_PROTECT_SUBCMD_CHECK 0x01U /* Check if flash is read-protected */ +#define WCH_PROTECT_SUBCMD_FLASH_UNPROTECT 0x02U /* Set flash read unprotected */ +#define WCH_PROTECT_SUBCMD_FLASH_PROTECT 0x03U /* Set flash read protected */ +#define WCH_PROTECT_SUBCMD_CHECK_V2 0x04U /* Check if flash is read-protected - Firmware >= v2.9 */ +/* PROTECT_V2 and UNPROTECT_V2 require `0xbf ff ff ff ff ff ff` as payload */ +#define WCH_PROTECT_SUBCMD_FLASH_UNPROTECT_V2 0xf2U /* Set flash read unprotected - Firmware >= v2.9 */ +#define WCH_PROTECT_SUBCMD_FLASH_PROTECT_V2 0xf3U /* Set flash read protected - Firmware >= v2.9 */ + +#define WCH_PROTECTED 0x01U /* Protected, memory read returns random data */ +#define WCH_UNPROTECTED 0x02U /* Unprotected */ +#define WCH_PROTECTED_V2 0x01U /* Protected, memory read returns random data */ +#define WCH_UNPROTECTED_V2 0x00U /* Unprotected */ + +/* + * DMI transfer command - WCH_CMD_DMI + * + * This command does not have a sub-command byte + * + * ┌────────────────────────────┐ + * │ Payload │ + * ├─────────┬──────┬───────────┤ + * │ 0 │ 1:4 │ 5 │ + * ├─────────┼──────┼───────────┤ + * │ Address │ Data │ Operation │ + * └─────────┴──────┴───────────┘ + * ┌────────────────────────────┐ + * │ Response payload │ + * ├─────────┬──────┬───────────┤ + * │ 0 │ 1:4 │ 5 │ + * ├─────────┼──────┼───────────┤ + * │ Address │ Data │ Status │ + * └─────────┴──────┴───────────┘ + * + * Operation and Status correspond to the same values + * found in the JTAG implementation of RISC-V DMI: + * + * Operation: + * - 0x00: no-op + * - 0x01: read + * - 0x02: write + * + * Status: + * - 0x00: success + * - 0x01: error + * - 0x03: busy + */ +#define WCH_DMI_ADDR_OFFSET 0U +#define WCH_DMI_DATA_OFFSET 1U +#define WCH_DMI_OP_STATUS_OFFSET 5U + +/* Reset command - WCH_CMD_RESET */ +#define WCH_RESET_SUBCMD_RELEASE 0x01U /* Release reset (after 300ms delay) */ +/* + * There are two _SUBCMD_ASSERT sub-commands, used depending on the riscvchip: + * + * ASSERT2 used for riscvchip: + * - 0x02: CH57X + * - 0x07: CH58X + * - 0x0b: CH59X + */ +#define WCH_RESET_SUBCMD_ASSERT 0x03U /* Reset */ +#define WCH_RESET_SUBCMD_ASSERT2 0x02U /* Reset */ + +/* + * Probe constrol command - WCH_CMD_PROBE_CONTROL + * + * This command does not have a sub-command byte, + * the payload is the the riscvchip number followed by the speed + * + * ┌───────────┬───────┐ + * │ 0 │ 1 │ + * ├───────────┼───────┤ + * │ RISCVCHIP │ Speed │ + * └───────────┴───────┘ + * + * Response is one byte, 0x01 meaning success + */ +#define WCH_SPEED_LOW 0x03U +#define WCH_SPEED_MEDIUM 0x02U +#define WCH_SPEED_HIGH 0x01U +#define WCH_SPEED_VERYHIGH 0x00U + +#define WCH_PROBE_CONTROL_OK 0x01U /* Success response */ + +/* Control command - WCH_CMD_CONTROL */ +#define WCH_CONTROL_SUBCMD_GET_PROBE_INFO 0x01U /* Firmware version and hardware type */ +#define WCH_CONTROL_SUBCMD_ATTACH 0x02U /* Attach to target */ +/* + * On some riscvchip targets, a _SUBCMD_UNKNOWN is issued after attach + * + * Issued on riscvchip: + * - 0x01: CH32V103 + * - 0x05: CH32V20X + * - 0x06: CH32V30X + * - 0x09: CH32V003 + */ +#define WCH_CONTROL_SUBCMD_UNKNOWN 0x03U /* ?? - issued after attach */ +/* + * _GET_MEMORY_INFO + * + * Supported on riscvchip: + * - 0x05: CH32V20X + * - 0x06: CH32V30X + */ +#define WCH_CONTROL_SUBCMD_GET_MEMORY_INFO 0x04U /* RAM size, flash size and addr */ +#define WCH_CONTROL_SUBCMD_CLOSE 0xffU /* Terminate connection (unsure what this entails) */ + +/* Probe info subcommand - WCH_SYS_SUBCMD_GET_PROBE_INFO */ +#define WCH_VERSION_MAJOR_OFFSET 0U /* 8 bit */ +#define WCH_VERSION_MINOR_OFFSET 1U /* 8 bit */ + +#define WCH_HARDWARE_TYPE_OFFSET 2U +#define WCH_HARDWARE_TYPE_WCHLINK 1U /* WCH-Link (CH549) *does not support SDIO (single wire debug) */ +#define WCH_HARDWARE_TYPE_WCHLINKE 2U /* WCH-LinkE (CH32V305) */ +#define WCH_HARDWARE_TYPE_WCHLINKS 3U /* WCH-LinkS (CH32V203) */ +#define WCH_HARDWARE_TYPE_WCHLINKB 4U /* WCH-LinkB */ +#define WCH_HARDWARE_TYPE_WCHLINKW 5U /* WCH-LinkW (CH32V208) *wireless */ +#define WCH_HARDWARE_TYPE_WCHLINKE2 18U /* WCH-LinkE (CH32V305) - ?? */ + +/* Attach to target subcommand - WCH_CONTROL_SUBCMD_ATTACH */ +#define WCH_RISCVCHIP_OFFSET 0U /* 8 bit */ +#define WCH_IDCODDE_OFFSET 1U /* 32 bit */ + +/* + * RV disable debug command - WCH_CMD_RV_DIS_DBG + * + * Supported on riscvchip: + * - 0x02: CH57X + * - 0x03: CH56X + * - 0x07: CH58X + * - 0x0b: CH59X + */ +#define WCH_RV_DIS_DBG_SUBCMD_DISABLE 0x01U /* Disable debug */ + +/* + * Verify command - WCH_CMD_VERIFY + * FIXME: TBD + */ + +/* + * Chip UID command - WCH_CMD_UID + * + * The reply does not use the standard format. + * + * Raw response: ffff00 20 aeb4abcd 16c6bc45 e339e339e339e339 + * Corresponding UID: 0xcdabb4ae45bcc616 + * Unknown value: e339e339e339e339 -> inital value for erased flash + */ +#define WCH_UID_SUBCMD_GET 0x09U /* Get chip UID */ +#define WCH_UID_SUBCMD_GET_V2 0x06U /* Get chip UID - Firmware >= v2.9 */ + +/* Switch probe mode command - WCH_CMD_MODDE_SWITCH */ +#define WCH_MODDE_SWITCH_SUBCMD_SUPPORTED 0x41U /* Check if mode switching is supported - ?? */ +#define WCH_MODDE_SWITCH_SUBCMD_DAP_TO_RV 0x52U /* Switch to RV mode - ?? */ + +bool wchlink_command_send_recv(uint8_t command, uint8_t subcommand, const void *payload, size_t payload_length, + void *response, size_t response_length); +bool wchlink_transfer_dmi(uint8_t operation, uint32_t address, uint32_t data_in, uint32_t *data_out, uint8_t *status); + +bool wchlink_attach(void); + +#endif /* PLATFORMS_HOSTED_WCHLINK_PROTOCOL_H */ diff --git a/src/platforms/hosted/wchlink_riscv_dtm.c b/src/platforms/hosted/wchlink_riscv_dtm.c new file mode 100644 index 00000000000..4aa27198fc5 --- /dev/null +++ b/src/platforms/hosted/wchlink_riscv_dtm.c @@ -0,0 +1,93 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rafael Silva + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "general.h" +#include "jep106.h" +#include "riscv_debug.h" +#include "wchlink_protocol.h" + +static void wchlink_riscv_dtm_init(riscv_dmi_s *dmi); +static bool wchlink_riscv_dmi_read(riscv_dmi_s *dmi, uint32_t address, uint32_t *value); +static bool wchlink_riscv_dmi_write(riscv_dmi_s *dmi, uint32_t address, uint32_t value); + +void wchlink_riscv_dtm_handler(void) +{ + riscv_dmi_s *dmi = calloc(1, sizeof(*dmi)); + if (!dmi) { /* calloc failed: heap exhaustion */ + DEBUG_WARN("calloc: failed in %s\n", __func__); + return; + } + + wchlink_riscv_dtm_init(dmi); + /* If we failed to find any DMs or Harts, free the structure */ + if (!dmi->ref_count) + free(dmi); +} + +static void wchlink_riscv_dtm_init(riscv_dmi_s *const dmi) +{ + /* WCH-Link doesn't have any mechanism to identify the DTM manufacturer, so we'll just assume it's WCH */ + dmi->designer_code = NOT_JEP106_MANUFACTURER_WCH; + + dmi->version = RISCV_DEBUG_UNSPECIFIED; /* Not available */ + + /* + * WCH-Link has a fixed address width of 7 bits, + * technically limited by the USB protocol to 8 bits but the underlying protocols are 7 bits + */ + dmi->address_width = 7U; + + dmi->read = wchlink_riscv_dmi_read; + dmi->write = wchlink_riscv_dmi_write; + + riscv_dmi_init(dmi); +} + +static bool wchlink_riscv_dmi_read(riscv_dmi_s *const dmi, const uint32_t address, uint32_t *const value) +{ + uint8_t status = 0; + const bool result = wchlink_transfer_dmi(RV_DMI_OP_READ, address, 0, value, &status); + + /* Translate error 1 into RV_DMI_FAILURE per the spec, also write RV_DMI_FAILURE if the transfer failed */ + dmi->fault = !result || status == RV_DMI_RESERVED ? RV_DMI_FAILURE : status; + return dmi->fault == RV_DMI_SUCCESS; +} + +static bool wchlink_riscv_dmi_write(riscv_dmi_s *const dmi, const uint32_t address, const uint32_t value) +{ + uint8_t status = 0; + const bool result = wchlink_transfer_dmi(RV_DMI_OP_WRITE, address, value, NULL, &status); + + /* Translate error 1 into RV_DMI_FAILURE per the spec, also write RV_DMI_FAILURE if the transfer failed */ + dmi->fault = !result || status == RV_DMI_RESERVED ? RV_DMI_FAILURE : status; + return dmi->fault == RV_DMI_SUCCESS; +} diff --git a/src/platforms/hosted/wchlink_rvswd.c b/src/platforms/hosted/wchlink_rvswd.c new file mode 100644 index 00000000000..249aa7f49f9 --- /dev/null +++ b/src/platforms/hosted/wchlink_rvswd.c @@ -0,0 +1,53 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rafael Silva + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "general.h" +#include "target_internal.h" +#include "wchlink.h" +#include "wchlink_protocol.h" + +bool wchlink_rvswd_scan(void) +{ + target_list_free(); + + /* + * This is redundant as we will run a generic scan routine after this + * but without it the scan will fail + */ + if (!wchlink_attach()) + return false; + + /* deffer to WCH-Link riscv_dtm_handler */ + wchlink_riscv_dtm_handler(); + + return target_list != NULL; +} diff --git a/src/platforms/native/platform.h b/src/platforms/native/platform.h index e2c393c7a33..abc7e99cc14 100644 --- a/src/platforms/native/platform.h +++ b/src/platforms/native/platform.h @@ -29,6 +29,7 @@ #define PLATFORM_HAS_TRACESWO #define PLATFORM_HAS_POWER_SWITCH +#define PLATFORM_HAS_RVSWD #if ENABLE_DEBUG == 1 #define PLATFORM_HAS_DEBUG diff --git a/src/target/ch32vx.c b/src/target/ch32vx.c new file mode 100644 index 00000000000..5332164f2af --- /dev/null +++ b/src/target/ch32vx.c @@ -0,0 +1,164 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2022 1BitSquared + * Written by Rafael Silva + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* This file implements RISC-V CH32Vx target specific functions */ + +#include "general.h" +#include "target.h" +#include "target_internal.h" +#include "buffer_utils.h" + +/* + * IDCODE register + * [31:16] - REVID + * [15:0] - DEVID + */ +#define CH32V003X_IDCODE 0x1ffff7c4U + +/* IDCODE register */ +#define CH32VX_IDCODE 0x1ffff704U +#define CH32VX_IDCODE_MASK 0x0ffffff0f +#define CH32VX_IDCODE_FAMILY_OFFSET 20U +#define CH32VX_IDCODE_FAMILY_MASK (0xfffU << CH32VX_IDCODE_FAMILY_OFFSET) + +#define CH32V203_IDCODE_FAMILY 0x203U +#define CH32V208_IDCODE_FAMILY 0x208U +#define CH32V305_IDCODE_FAMILY 0x305U +#define CH32V303_IDCODE_FAMILY 0x303U +#define CH32V307_IDCODE_FAMILY 0x307U + +/* Electronic Signature (ESIG) registers */ +#define CH32VX_ESIG_FLASH_CAP 0x1ffff7e0U /* Flash capacity register, 16 bits, KiB units */ +#define CH32VX_ESIG_UID1 0x1ffff7e8U /* Unique ID register, bits 0:31 */ +#define CH32VX_ESIG_UID2 0x1ffff7ecU /* Unique ID register, bits 32:63 */ +#define CH32VX_ESIG_UID3 0x1ffff7f0U /* Unique ID register, bits 64:95 */ + +static bool ch32vx_uid_cmd(target_s *t, int argc, const char **argv); + +const command_s ch32vx_cmd_list[] = { + {"uid", ch32vx_uid_cmd, "Prints 96 bit unique id"}, + {NULL, NULL, NULL}, +}; + +static size_t ch32vx_read_flash_size(target_s *const t) +{ + return target_mem32_read16(t, CH32VX_ESIG_FLASH_CAP) * 1024U; +} + +static void ch32vx_read_uid(target_s *const t, uint8_t *const uid) +{ + for (size_t uid_reg_offset = 0; uid_reg_offset < 12U; uid_reg_offset += 4U) + write_be4(uid, uid_reg_offset, target_mem32_read32(t, CH32VX_ESIG_UID1 + uid_reg_offset)); +} + +bool ch32v003x_probe(target_s *const target) +{ + const uint32_t idcode = target_mem32_read32(target, CH32V003X_IDCODE); + + switch (idcode & CH32VX_IDCODE_MASK) { + case 0x00300500U: /* CH32V003F4P6 */ + case 0x00310500U: /* CH32V003F4U6 */ + case 0x00320500U: /* CH32V003A4M6 */ + case 0x00330500U: /* CH32V003J4M6 */ + break; + default: + DEBUG_INFO("Unrecognized CH32V003x IDCODE: 0x%08x\n", idcode); + return false; + break; + } + + target->driver = "CH32V003"; + + const size_t flash_size = ch32vx_read_flash_size(target); + DEBUG_INFO("CH32V003x flash size: %zu\n", flash_size); + (void)flash_size; + + target->part_id = idcode; + + target_add_commands(target, ch32vx_cmd_list, "CH32Vx"); + + return true; +} + +bool ch32vx_probe(target_s *const target) +{ + const uint32_t idcode = target_mem32_read32(target, CH32VX_IDCODE); + + switch (idcode & CH32VX_IDCODE_MASK) { + case 0x30330504U: /* CH32V303CBT6 */ + case 0x30320504U: /* CH32V303RBT6 */ + case 0x30310504U: /* CH32V303RCT6 */ + case 0x30300504U: /* CH32V303VCT6 */ + case 0x30520508U: /* CH32V305FBP6 */ + case 0x30500508U: /* CH32V305RBT6 */ + case 0x30730508U: /* CH32V307WCU6 */ + case 0x30720508U: /* CH32V307FBP6 */ + case 0x30710508U: /* CH32V307RCT6 */ + case 0x30700508U: /* CH32V307VCT6 */ + break; + default: + DEBUG_INFO("Unrecognized CH32Vx IDCODE: 0x%08x\n", idcode); + return false; + break; + } + + const uint16_t family = (idcode & CH32VX_IDCODE_FAMILY_MASK) >> CH32VX_IDCODE_FAMILY_OFFSET; + switch (family) { + case CH32V303_IDCODE_FAMILY: + target->driver = "CH32V303"; + break; + case CH32V305_IDCODE_FAMILY: + target->driver = "CH32V305"; + break; + case CH32V307_IDCODE_FAMILY: + target->driver = "CH32V307"; + break; + default: + return false; + break; + } + + const size_t flash_size = ch32vx_read_flash_size(target); + DEBUG_INFO("CH32Vx flash size: %zu\n", flash_size); + (void)flash_size; + + target->part_id = idcode; + + target_add_commands(target, ch32vx_cmd_list, "CH32Vx"); + + return true; +} + +/* Reads the 96 bit unique id */ +static bool ch32vx_uid_cmd(target_s *const target, const int argc, const char **const argv) +{ + (void)argc; + (void)argv; + + uint8_t uid[12U]; + ch32vx_read_uid(target, uid); + + tc_printf(target, "Unique id: 0x"); + for (size_t i = 0U; i < sizeof(uid); i++) + tc_printf(target, "%02" PRIx8, uid[i]); + tc_printf(target, "\n"); + + return true; +} diff --git a/src/target/jep106.h b/src/target/jep106.h index f9225067151..a07f94d11a0 100644 --- a/src/target/jep106.h +++ b/src/target/jep106.h @@ -44,14 +44,27 @@ * * |15 |11 |7|6 0| * | | | | | | | | |0| | | | | | | | - * |\____/ \______/|\_____________/ - * | V V | V - * | Unused Cont | code - * | Code | - * \_ Legacy flag \_ Parity bit (always 0) + * | |\__/ \______/|\_____________/ + * | | V V | V + * | | Unused Cont | code + * | | Code | + * | | \_ Parity bit (always 0) + * | \_ BMD internal flag + * \_ Legacy flag */ #define ASCII_CODE_FLAG (1U << 15U) /* flag the code as legacy ASCII */ +/* + * BMD internal flag + * + * This is used to mark codes that are not part of the JEP106 or any other standard but are used + * internally by BMD to identify targets that don't provide a usable identification code + * + * These codes may be changed or removed at any time, and should not be relied upon outside of + * the context of BMD + */ +#define BMD_INTERNAL_FLAG (1U << 14U) /* flag the code as internal use in BMD */ + #define JEP106_MANUFACTURER_ARM 0x43bU /* ARM Ltd. */ #define JEP106_MANUFACTURER_FREESCALE 0x00eU /* Freescale */ #define JEP106_MANUFACTURER_NXP 0x015U /* NXP */ @@ -69,6 +82,11 @@ #define JEP106_MANUFACTURER_RENESAS 0x423U /* Renesas */ #define JEP106_MANUFACTURER_WCH 0x72aU /* "Nanjing Yihuo Technology", used by CH579 */ #define JEP106_MANUFACTURER_XILINX 0x309U /* Xilinx - Technically 0x049, but they use Ikanos Communications' code */ + +/* BMD internal codes */ +/* WCH AKA Nanjing Qinheng Microelectronics */ +#define NOT_JEP106_MANUFACTURER_WCH (BMD_INTERNAL_FLAG | 0x01U) /* WCH (WinChipHead) */ + /* * This JEP code should belong to "Andes Technology Corporation", but is used on RISC-V by GigaDevice, * so in the unlikely event we need to support chips by them, here be dragons. diff --git a/src/target/meson.build b/src/target/meson.build index 4e11cb99ef0..c137bd69883 100644 --- a/src/target/meson.build +++ b/src/target/meson.build @@ -42,6 +42,7 @@ target_common_sources = files( 'gdb_reg.c', 'jtag_devs.c', 'jtag_scan.c', + 'rvswd_dtm.c', 'semihosting.c', 'sfdp.c', 'spi.c', @@ -289,6 +290,7 @@ target_stm32f4 = declare_dependency( target_ch32 = declare_dependency( sources: files( 'ch32f1.c', + 'ch32vx.c', ), dependencies: target_stm32f1, ) diff --git a/src/target/riscv32.c b/src/target/riscv32.c index 57e9472c844..8165af9d86a 100644 --- a/src/target/riscv32.c +++ b/src/target/riscv32.c @@ -40,11 +40,6 @@ #include "gdb_packet.h" #include "adiv5.h" -typedef struct riscv32_regs { - uint32_t gprs[32]; - uint32_t pc; -} riscv32_regs_s; - /* This defines a match trigger that's for an address or data location */ #define RV32_MATCH_ADDR_DATA_TRIGGER 0x20000000U /* A dmode of 1 restricts the writability of the trigger to debug mode only */ @@ -72,16 +67,19 @@ static size_t riscv32_reg_read(target_s *target, uint32_t reg, void *data, size_ static size_t riscv32_reg_write(target_s *target, uint32_t reg, const void *data, size_t max); static void riscv32_regs_read(target_s *target, void *data); static void riscv32_regs_write(target_s *target, const void *data); +static void riscv32_mem_read(target_s *target, void *dest, target_addr64_t src, size_t len); +static void riscv32_mem_write(target_s *target, target_addr64_t dest, const void *src, size_t len); static int riscv32_breakwatch_set(target_s *target, breakwatch_s *breakwatch); static int riscv32_breakwatch_clear(target_s *target, breakwatch_s *breakwatch); bool riscv32_probe(target_s *const target) { + /* 'E' base ISA has 16 GPRs + PC, 'I' base ISA has 32 GPRs + PC */ + riscv_hart_s *const hart = riscv_hart_struct(target); + target->regs_size = ((hart->extensions & RV_ISA_EXT_EMBEDDED ? 16U : 32U) + 1U) * sizeof(uint32_t); + /* Finish setting up the target structure with generic rv32 functions */ - target->core = "rv32"; - /* Provide the length of a suitable registers structure */ - target->regs_size = sizeof(riscv32_regs_s); target->regs_read = riscv32_regs_read; target->regs_write = riscv32_regs_write; target->reg_write = riscv32_reg_write; @@ -101,6 +99,10 @@ bool riscv32_probe(target_s *const target) break; default: break; + case NOT_JEP106_MANUFACTURER_WCH: + PROBE(ch32v003x_probe); + PROBE(ch32vx_probe); + break; } #if CONFIG_BMDA == 0 @@ -115,30 +117,30 @@ static void riscv32_regs_read(target_s *const target, void *const data) { /* Grab the hart structure and figure out how many registers need reading out */ riscv_hart_s *const hart = riscv_hart_struct(target); - riscv32_regs_s *const regs = (riscv32_regs_s *)data; + uint32_t *const regs = (uint32_t *)data; const size_t gprs_count = hart->extensions & RV_ISA_EXT_EMBEDDED ? 16U : 32U; /* Loop through reading out the GPRs */ for (size_t gpr = 0; gpr < gprs_count; ++gpr) { // TODO: handle when this fails.. - riscv_csr_read(hart, RV_GPR_BASE + gpr, ®s->gprs[gpr]); + riscv_csr_read(hart, RV_GPR_BASE + gpr, ®s[gpr]); } /* Special access to grab the program counter that would be executed on resuming the hart */ - riscv_csr_read(hart, RV_DPC, ®s->pc); + riscv_csr_read(hart, RV_DPC, ®s[gprs_count]); } static void riscv32_regs_write(target_s *const target, const void *const data) { /* Grab the hart structure and figure out how many registers need reading out */ riscv_hart_s *const hart = riscv_hart_struct(target); - const riscv32_regs_s *const regs = (const riscv32_regs_s *)data; + const uint32_t *const regs = (const uint32_t *)data; const size_t gprs_count = hart->extensions & RV_ISA_EXT_EMBEDDED ? 16U : 32U; /* Loop through writing out the GPRs, except for the first which is always 0 */ for (size_t gpr = 1; gpr < gprs_count; ++gpr) { // TODO: handle when this fails.. - riscv_csr_write(hart, RV_GPR_BASE + gpr, ®s->gprs[gpr]); + riscv_csr_write(hart, RV_GPR_BASE + gpr, ®s[gpr]); } /* Special access to poke in the program counter that will be executed on resuming the hart */ - riscv_csr_write(hart, RV_DPC, ®s->pc); + riscv_csr_write(hart, RV_DPC, ®s[gprs_count]); } static inline size_t riscv32_bool_to_4(const bool ret) @@ -238,7 +240,7 @@ static void riscv32_abstract_mem_read( const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_READ | (access_width << RV_ABST_MEM_ACCESS_SHIFT) | (access_length < len ? RV_ABST_MEM_ADDR_POST_INC : 0U); /* Write the address to read to arg1 */ - if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA1, src)) + if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA(1U), src)) return; uint8_t *const data = (uint8_t *)dest; for (size_t offset = 0; offset < len; offset += access_length) { @@ -247,7 +249,7 @@ static void riscv32_abstract_mem_read( return; /* Extract back the data from arg0 */ uint32_t value = 0; - if (!riscv_dm_read(hart->dbg_module, RV_DM_DATA0, &value)) + if (!riscv_dm_read(hart->dbg_module, RV_DM_DATA(0U), &value)) return; riscv32_unpack_data(data + offset, value, access_width); } @@ -263,13 +265,13 @@ static void riscv32_abstract_mem_write( const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_WRITE | (access_width << RV_ABST_MEM_ACCESS_SHIFT) | (access_length < len ? RV_ABST_MEM_ADDR_POST_INC : 0U); /* Write the address to write to arg1 */ - if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA1, dest)) + if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA(1U), dest)) return; const uint8_t *const data = (const uint8_t *)src; for (size_t offset = 0; offset < len; offset += access_length) { /* Pack the data to write into arg0 */ uint32_t value = riscv32_pack_data(data + offset, access_width); - if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA0, value)) + if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA(0U), value)) return; /* Execute the write */ if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_COMMAND, command) || !riscv_command_wait_complete(hart)) @@ -564,7 +566,139 @@ static void riscv32_sysbus_mem_write( riscv32_sysbus_mem_adjusted_write(hart, address, data, remainder, native_access_width, native_access_length); } -void riscv32_mem_read(target_s *const target, void *const dest, const target_addr64_t src, const size_t len) +static void riscv32_abstract_progbuf_mem_read( + riscv_hart_s *const hart, void *const dest, const target_addr_t src, const size_t len) +{ + if (!(hart->extensions & RV_ISA_EXT_COMPRESSED)) { + DEBUG_ERROR("This target does not implement the compressed ISA extension\n"); + return; + } + + /* Figure out the maximal width of access to perform, up to the bitness of the target */ + const uint8_t access_width = riscv_mem_access_width(hart, src, len); + // const uint8_t access_length = 1U << access_width; + // /* Build the access command */ + // const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_READ | (access_width << RV_ABST_MEM_ACCESS_SHIFT) | + // (access_length < len ? RV_ABST_MEM_ADDR_POST_INC : 0U); + // /* Write the address to read to arg1 */ + // if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA1, src)) + // return; + // uint8_t *const data = (uint8_t *)dest; + // for (size_t offset = 0; offset < len; offset += access_length) { + // /* Execute the read */ + // if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_COMMAND, command) || !riscv_command_wait_complete(hart)) + // return; + // /* Extract back the data from arg0 */ + // uint32_t value = 0; + // if (!riscv_dm_read(hart->dbg_module, RV_DM_DATA0, &value)) + // return; + // riscv32_unpack_data(data + offset, value, access_width); + // } + + /* Disable auto-exec */ + // if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_AUTO, 0)) + // return; + + /* + * progbuf 0 + * c.lw x8,0(x11) // Pull the address from DATA1 + * c.lw x9,0(x8) // Read the data at that location + */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF(0U), 0x40044180U)) + return; + + /* + * progbuf 1 + * c.nop // alternately, `c.addi x8, 4` , for auto-increment (0xc1040411) + * c.sw x9, 0(x10) // Write back to DATA0 + */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF(1U), 0xc1040001U)) + return; + + /* + * progbuf 2 + * c.sw x8, 0(x11) // Write addy to DATA1 + * c.ebreak + */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF(2U), 0x9002c180U)) + return; + + /* StaticUpdatePROGBUFRegs */ + uint32_t rr; + if (!riscv_dm_read(hart->dbg_module, 0x12U, &rr)) { + DEBUG_ERROR("Could not get hart info\n"); + return; + } + DEBUG_INFO("rr: %08" PRIx32 "\n", rr); + const uint32_t data0_offset = 0xe0000000U | (rr & 0x7ffU); + if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA(0U), data0_offset)) // DATA0's location in memory. + return; + if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_COMMAND, 0x0023100aU)) // Copy data to x10 + return; + if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA(0U), data0_offset + 4U)) // DATA1's location in memory. + return; + if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_COMMAND, 0x0023100bU)) // Copy data to x11 + return; + // if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA0, 0x40022010U)) // FLASH->CTLR + // return; + // if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_COMMAND, 0x0023100cU)) // Copy data to x12 + // return; + // #define CR_PAGE_PG ((uint32_t)0x00010000) + // #define CR_BUF_LOAD ((uint32_t)0x00040000) + // if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA0, CR_PAGE_PG | CR_BUF_LOAD)) + // return; + // if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_COMMAND, 0x0023100dU)) // Copy data to x13 + // return; + + /* Enable auto-exec */ + // if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_AUTO, 1U)) + // return; + + if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA(1U), src)) + return; + if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_COMMAND, 0x00241000U) || !riscv_command_wait_complete(hart)) + return; + + /* Extract back the data from arg0 */ + uint32_t value = 0; + if (!riscv_dm_read(hart->dbg_module, RV_DM_DATA(0U), &value)) + return; + + riscv32_unpack_data(dest, value, access_width); +} + +static void riscv32_abstract_progbuf_mem_write( + riscv_hart_s *const hart, const target_addr_t dest, const void *const src, const size_t len) +{ + DEBUG_TARGET("Performing %zu byte write of %08" PRIx32 " using PROGBUF\n", len, dest); + + (void)hart; + (void)dest; + (void)src; + (void)len; + + // /* Figure out the maximal width of access to perform, up to the bitness of the target */ + // const uint8_t access_width = riscv_mem_access_width(hart, dest, len); + // const uint8_t access_length = 1U << access_width; + // /* Build the access command */ + // const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_WRITE | (access_width << RV_ABST_MEM_ACCESS_SHIFT) | + // (access_length < len ? RV_ABST_MEM_ADDR_POST_INC : 0U); + // /* Write the address to write to arg1 */ + // if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA1, dest)) + // return; + // const uint8_t *const data = (const uint8_t *)src; + // for (size_t offset = 0; offset < len; offset += access_length) { + // /* Pack the data to write into arg0 */ + // uint32_t value = riscv32_pack_data(data + offset, access_width); + // if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA0, value)) + // return; + // /* Execute the write */ + // if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_COMMAND, command) || !riscv_command_wait_complete(hart)) + // return; + // } +} + +static void riscv32_mem_read(target_s *const target, void *const dest, const target_addr64_t src, const size_t len) { /* If we're asked to do a 0-byte read, do nothing */ if (!len) { @@ -575,8 +709,15 @@ void riscv32_mem_read(target_s *const target, void *const dest, const target_add riscv_hart_s *const hart = riscv_hart_struct(target); if (hart->flags & RV_HART_FLAG_MEMORY_SYSBUS) riscv32_sysbus_mem_read(hart, dest, src, len); - else + else if (hart->flags & RV_HART_FLAG_MEMORY_ABSTRACT) { riscv32_abstract_mem_read(hart, dest, src, len); + if (hart->status == RISCV_HART_NOT_SUPP) { + DEBUG_WARN("Abstract memory access not supported, falling back to prog buffer\n"); + hart->flags &= (uint8_t)~RV_HART_FLAG_MEMORY_ABSTRACT; + riscv32_abstract_progbuf_mem_read(hart, dest, src, len); + } + } else + riscv32_abstract_progbuf_mem_read(hart, dest, src, len); #if ENABLE_DEBUG DEBUG_PROTO("%s: @ %08" PRIx32 " len %zu:", __func__, (uint32_t)src, len); @@ -594,7 +735,8 @@ void riscv32_mem_read(target_s *const target, void *const dest, const target_add #endif } -void riscv32_mem_write(target_s *const target, const target_addr64_t dest, const void *const src, const size_t len) +static void riscv32_mem_write( + target_s *const target, const target_addr64_t dest, const void *const src, const size_t len) { #if ENABLE_DEBUG DEBUG_PROTO("%s: @ %" PRIx32 " len %zu:", __func__, (uint32_t)dest, len); @@ -617,8 +759,15 @@ void riscv32_mem_write(target_s *const target, const target_addr64_t dest, const riscv_hart_s *const hart = riscv_hart_struct(target); if (hart->flags & RV_HART_FLAG_MEMORY_SYSBUS) riscv32_sysbus_mem_write(hart, dest, src, len); - else + else if (hart->flags & RV_HART_FLAG_MEMORY_ABSTRACT) { riscv32_abstract_mem_write(hart, dest, src, len); + if (hart->status == RISCV_HART_NOT_SUPP) { + DEBUG_WARN("Abstract memory access not supported, falling back to prog buffer\n"); + hart->flags &= (uint8_t)~RV_HART_FLAG_MEMORY_ABSTRACT; + riscv32_abstract_progbuf_mem_write(hart, dest, src, len); + } + } else + riscv32_abstract_progbuf_mem_write(hart, dest, src, len); } /* diff --git a/src/target/riscv64.c b/src/target/riscv64.c index b2fe72051e2..beb626d67d4 100644 --- a/src/target/riscv64.c +++ b/src/target/riscv64.c @@ -49,9 +49,7 @@ static void riscv64_mem_read(target_s *target, void *dest, target_addr64_t src, bool riscv64_probe(target_s *const target) { /* Finish setting up the target structure with generic rv64 functions */ - target->core = "rv64"; - /* Provide the length of a suitable registers structure */ - target->regs_size = sizeof(riscv64_regs_s); + target->regs_size = sizeof(riscv64_regs_s); /* Provide the length of a suitable registers structure */ target->regs_read = riscv64_regs_read; target->regs_write = riscv64_regs_write; target->mem_read = riscv64_mem_read; @@ -116,15 +114,15 @@ static void riscv64_mem_read(target_s *const target, void *const dest, const tar if (!len) return; riscv_hart_s *const hart = riscv_hart_struct(target); - /* Figure out the maxmial width of access to perform, up to the bitness of the target */ + /* Figure out the maximal width of access to perform, up to the bitness of the target */ const uint8_t access_width = riscv_mem_access_width(hart, src, len); const uint8_t access_length = 1U << access_width; /* Build the access command */ const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_READ | (access_width << RV_ABST_MEM_ACCESS_SHIFT) | (access_length < len ? RV_ABST_MEM_ADDR_POST_INC : 0U); /* Write the address to read to arg1 */ - if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA2, (uint32_t)src) || - !riscv_dm_write(hart->dbg_module, RV_DM_DATA3, (uint32_t)(src >> 32U))) + if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA(2U), (uint32_t)src) || + !riscv_dm_write(hart->dbg_module, RV_DM_DATA(3U), (uint32_t)(src >> 32U))) return; uint8_t *const data = (uint8_t *)dest; for (size_t offset = 0; offset < len; offset += access_length) { @@ -134,8 +132,8 @@ static void riscv64_mem_read(target_s *const target, void *const dest, const tar /* Extract back the data from arg0 */ uint32_t value_low = 0; uint32_t value_high = 0; - if (!riscv_dm_read(hart->dbg_module, RV_DM_DATA0, &value_low) || - !riscv_dm_read(hart->dbg_module, RV_DM_DATA1, &value_high)) + if (!riscv_dm_read(hart->dbg_module, RV_DM_DATA(0U), &value_low) || + !riscv_dm_read(hart->dbg_module, RV_DM_DATA(1U), &value_high)) return; riscv64_unpack_data(data + offset, value_low, value_high, access_width); } diff --git a/src/target/riscv_debug.c b/src/target/riscv_debug.c index 14d28b35926..d253c2cffe5 100644 --- a/src/target/riscv_debug.c +++ b/src/target/riscv_debug.c @@ -36,6 +36,7 @@ #include "target_internal.h" #include "gdb_reg.h" #include "riscv_debug.h" +#include "buffer_utils.h" #include @@ -49,29 +50,6 @@ * https://github.com/riscv/riscv-debug-spec/blob/master/riscv-debug-stable.pdf */ -#define RV_DM_CONTROL 0x10U -#define RV_DM_STATUS 0x11U -#define RV_DM_NEXT_DM 0x1dU -#define RV_DM_PROGBUF_BASE 0x20U - -#define RV_DM_CTRL_ACTIVE (1U << 0U) -#define RV_DM_CTRL_SYSTEM_RESET (1U << 1U) -#define RV_DM_CTRL_HARTSEL_MASK 0x03ffffc0U -#define RV_DM_CTRL_HARTSELLO_MASK 0x03ff0000U -#define RV_DM_CTRL_HARTSELHI_MASK 0x0000ffc0U -#define RV_DM_CTRL_HART_ACK_RESET (1U << 28U) -#define RV_DM_CTRL_HART_RESET (1U << 29U) -#define RV_DM_CTRL_RESUME_REQ (1U << 30U) -#define RV_DM_CTRL_HALT_REQ (1U << 31U) -#define RV_DM_CTRL_HARTSELLO_SHIFT 16U -#define RV_DM_CTRL_HARTSELHI_SHIFT 4U - -#define RV_DM_STAT_ALL_HALTED (1U << 9U) -#define RV_DM_STAT_UNAVAILABLE (1U << 12U) -#define RV_DM_STAT_NON_EXISTENT (1U << 14U) -#define RV_DM_STAT_ALL_RESUME_ACK (1U << 17U) -#define RV_DM_STAT_ALL_RESET (1U << 19U) - #define RV_DM_ABST_STATUS_BUSY (1U << 12U) #define RV_DM_ABST_STATUS_DATA_COUNT 0x0000000fU #define RV_DM_ABST_STATUS_PROGBUFSIZE_MASK 0x1f000000U @@ -421,6 +399,58 @@ static void riscv_hart_read_ids(riscv_hart_s *const hart) /* rv128 is unimpl. */ } +static size_t riscv_snprint_isa_subset( + char *const string_buffer, const size_t buffer_size, const uint8_t access_width, const uint32_t extensions) +{ + size_t offset = snprintf(string_buffer, buffer_size, "rv%u", access_width); + + const bool is_embedded = extensions & RV_ISA_EXT_EMBEDDED; + + offset = write_char(string_buffer, buffer_size, offset, is_embedded ? 'e' : 'i'); + + const bool is_general_purpose_isa = + !is_embedded && (extensions & RV_ISA_EXT_GENERAL_PURPOSE) == RV_ISA_EXT_GENERAL_PURPOSE; + + if (is_general_purpose_isa) { + offset = write_char(string_buffer, buffer_size, offset, 'g'); + if (extensions & RV_ISA_EXT_QUAD_FLOAT) + offset = write_char(string_buffer, buffer_size, offset, 'q'); + } else { + if (extensions & RV_ISA_EXT_MUL_DIV_INT) + offset = write_char(string_buffer, buffer_size, offset, 'm'); + if (extensions & RV_ISA_EXT_ATOMIC) + offset = write_char(string_buffer, buffer_size, offset, 'a'); + if (extensions & RV_ISA_EXT_QUAD_FLOAT) + offset = write_char(string_buffer, buffer_size, offset, 'q'); /* Implies d */ + else if (extensions & RV_ISA_EXT_DOUBLE_FLOAT) + offset = write_char(string_buffer, buffer_size, offset, 'd'); /* Implies f */ + else if (extensions & RV_ISA_EXT_SINGLE_FLOAT) + offset = write_char(string_buffer, buffer_size, offset, 'f'); + } + if (extensions & RV_ISA_EXT_DECIMAL_FLOAT) + offset = write_char(string_buffer, buffer_size, offset, 'l'); + if (extensions & RV_ISA_EXT_COMPRESSED) + offset = write_char(string_buffer, buffer_size, offset, 'c'); + if (extensions & RV_ISA_EXT_BIT_MANIP) + offset = write_char(string_buffer, buffer_size, offset, 'b'); + if (extensions & RV_ISA_EXT_DYNAMIC_LANG) + offset = write_char(string_buffer, buffer_size, offset, 'j'); + if (extensions & RV_ISA_EXT_TRANSACT_MEM) + offset = write_char(string_buffer, buffer_size, offset, 't'); + if (extensions & RV_ISA_EXT_PACKED_SIMD) + offset = write_char(string_buffer, buffer_size, offset, 'p'); + if (extensions & RV_ISA_EXT_VECTOR) + offset = write_char(string_buffer, buffer_size, offset, 'v'); + if (extensions & RV_ISA_EXT_USER_INTERRUPTS) + offset = write_char(string_buffer, buffer_size, offset, 'n'); + + /* null-terminate the string */ + if (string_buffer && buffer_size > 0) + string_buffer[offset < buffer_size ? offset : buffer_size - 1U] = '\0'; + + return offset; +} + static bool riscv_hart_init(riscv_hart_s *const hart) { /* Allocate a new target */ @@ -428,7 +458,7 @@ static bool riscv_hart_init(riscv_hart_s *const hart) if (!target) return false; - /* Grab a reference to the DMI and DM structurues and do preliminary setup of the target structure */ + /* Grab a reference to the DMI and DM structures and do preliminary setup of the target structure */ riscv_dm_ref(hart->dbg_module); target->driver = "RISC-V"; target->priv = hart; @@ -444,13 +474,16 @@ static bool riscv_hart_init(riscv_hart_s *const hart) /* Then read out the ID registers */ riscv_hart_read_ids(hart); - DEBUG_INFO("Hart %" PRIx32 ": %u-bit RISC-V (arch = %08" PRIx32 "), vendor = %" PRIx32 ", impl = %" PRIx32 - ", exts = %08" PRIx32 "\n", - hart->hartid, hart->access_width, hart->archid, hart->vendorid, hart->implid, hart->extensions); + /* Build the ISA subset string from the Hart */ + riscv_snprint_isa_subset(hart->isa_name, sizeof(hart->isa_name), hart->access_width, hart->extensions); + target->core = hart->isa_name; + + DEBUG_INFO("Hart %" PRIx32 ": %u-bit RISC-V (arch = %08" PRIx32 "), %s ISA (exts = %08" PRIx32 + "), vendor = %" PRIx32 ", impl = %" PRIx32 "\n", + hart->hartid, hart->access_width, hart->archid, hart->isa_name, hart->extensions, hart->vendorid, hart->implid); /* We don't support rv128, so tell the user and fast-quit on this target. */ if (hart->access_width == 128U) { - target->core = "(unsup) rv128"; DEBUG_WARN("rv128 is unsupported, ignoring this hart\n"); return true; } @@ -504,16 +537,30 @@ static void riscv_hart_free(void *const priv) static bool riscv_dmi_read(riscv_dmi_s *const dmi, const uint32_t address, uint32_t *const value) { - const bool result = dmi->read(dmi, address, value); + bool result = false; + do { + result = dmi->read(dmi, address, value); + } while (dmi->fault == RV_DMI_TOO_SOON); + if (result) DEBUG_PROTO("%s: %08" PRIx32 " -> %08" PRIx32 "\n", __func__, address, *value); + else + DEBUG_WARN("%s: %08" PRIx32 " failed: %u\n", __func__, address, dmi->fault); return result; } static bool riscv_dmi_write(riscv_dmi_s *const dmi, const uint32_t address, const uint32_t value) { DEBUG_PROTO("%s: %08" PRIx32 " <- %08" PRIx32 "\n", __func__, address, value); - return dmi->write(dmi, address, value); + + bool result = false; + do { + result = dmi->write(dmi, address, value); + } while (dmi->fault == RV_DMI_TOO_SOON); + + if (!result) + DEBUG_WARN("%s: %08" PRIx32 " failed: %u\n", __func__, address, dmi->fault); + return result; } bool riscv_dm_read(riscv_dm_s *dbg_module, const uint8_t address, uint32_t *const value) @@ -673,23 +720,23 @@ static bool riscv_csr_read_data(riscv_hart_s *const hart, void *const data, cons uint32_t *const value = (uint32_t *)data; /* If we're doing a 128-bit read, grab the upper-most 2 uint32_t's */ if (access_width == 128U && - !(riscv_dm_read(hart->dbg_module, RV_DM_DATA3, value + 3) && - riscv_dm_read(hart->dbg_module, RV_DM_DATA2, value + 2))) + !(riscv_dm_read(hart->dbg_module, RV_DM_DATA(3U), value + 3) && + riscv_dm_read(hart->dbg_module, RV_DM_DATA(2U), value + 2))) return false; /* If we're doing at least a 64-bit read, grab the next uint32_t */ - if (access_width >= 64U && !riscv_dm_read(hart->dbg_module, RV_DM_DATA1, value + 1)) + if (access_width >= 64U && !riscv_dm_read(hart->dbg_module, RV_DM_DATA(1U), value + 1)) return false; /* Finally grab the last and lowest uint32_t */ - return riscv_dm_read(hart->dbg_module, RV_DM_DATA0, value); + return riscv_dm_read(hart->dbg_module, RV_DM_DATA(0U), value); } static bool riscv_csr_progbuf_read(riscv_hart_s *const hart, const uint16_t reg, void *const data) { /* Set up the program buffer to read out the target CSR */ - if (!riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF_BASE + 0U, RV_CSRR_A0 | ((reg & RV_CSR_ADDR_MASK) << 20U))) + if (!riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF(0U), RV_CSRR_A0 | ((reg & RV_CSR_ADDR_MASK) << 20U))) return false; /* If there's more than one progbuf register, set the second to an ebreak */ - if (hart->progbuf_size > 1U && !riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF_BASE + 1U, RV_EBREAK)) + if (hart->progbuf_size > 1U && !riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF(1U), RV_EBREAK)) return false; /* Execute the program buffer we've set up, reading a0 out to keep it safe */ @@ -738,15 +785,15 @@ static bool riscv_csr_write_data(riscv_hart_s *const hart, const void *const dat { const uint32_t *const value = (const uint32_t *)data; /* Regardless of width, we have to write data0 */ - if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA0, value[0])) + if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA(0U), value[0])) return false; /* If we're doing at least a 64-bit wide access, set up data1 */ - if (access_width >= 64U && !riscv_dm_write(hart->dbg_module, RV_DM_DATA1, value[1])) + if (access_width >= 64U && !riscv_dm_write(hart->dbg_module, RV_DM_DATA(1U), value[1])) return false; /* For a 128-bit access, set up data2 and data3 too */ if (access_width == 128 && - !(riscv_dm_write(hart->dbg_module, RV_DM_DATA2, value[2]) && - riscv_dm_write(hart->dbg_module, RV_DM_DATA3, value[3]))) + !(riscv_dm_write(hart->dbg_module, RV_DM_DATA(2U), value[2]) && + riscv_dm_write(hart->dbg_module, RV_DM_DATA(3U), value[3]))) return false; return true; } @@ -759,10 +806,10 @@ static bool riscv_csr_progbuf_write(riscv_hart_s *const hart, const uint16_t reg return false; /* Set up the program buffer to write to the target CSR */ - if (!riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF_BASE + 0U, RV_CSRW_A0 | ((reg & RV_CSR_ADDR_MASK) << 20U))) + if (!riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF(0U), RV_CSRW_A0 | ((reg & RV_CSR_ADDR_MASK) << 20U))) return false; /* If there's more than one progbuf register, set the second to an ebreak */ - if (hart->progbuf_size > 1U && !riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF_BASE + 1U, RV_EBREAK)) + if (hart->progbuf_size > 1U && !riscv_dm_write(hart->dbg_module, RV_DM_PROGBUF(1U), RV_EBREAK)) return false; /* Figure out what access width should be used for the data phase of this */ @@ -891,19 +938,25 @@ static void riscv_hart_discover_triggers(riscv_hart_s *const hart) static void riscv_hart_memory_access_type(target_s *const target) { riscv_hart_s *const hart = riscv_hart_struct(target); - hart->flags &= (uint8_t)~RV_HART_FLAG_MEMORY_SYSBUS; - uint32_t sysbus_status; + hart->flags &= (uint8_t)~RV_HART_FLAG_MEMORY_MASK; /* * Try reading the system bus access control and status register. * Check if the value read back is non-zero for the sbasize field */ - if (!riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, &sysbus_status) || - !(sysbus_status & RV_DM_SYSBUS_STATUS_ADDR_WIDTH_MASK)) - return; - /* If all the checks passed, we now have a valid system bus so can proceed with using it for memory access */ - hart->flags = RV_HART_FLAG_MEMORY_SYSBUS | (sysbus_status & RV_HART_FLAG_ACCESS_WIDTH_MASK); - /* System Bus also means the target can have memory read without halting */ - target->target_options |= TOPT_NON_HALTING_MEM_IO; + uint32_t sysbus_status; + if (riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, &sysbus_status) && + sysbus_status & RV_DM_SYSBUS_STATUS_ADDR_WIDTH_MASK) { + /* If all the checks passed, we now have a valid system bus so can proceed with using it for memory access */ + hart->flags = RV_HART_FLAG_MEMORY_SYSBUS | (sysbus_status & RV_HART_FLAG_ACCESS_WIDTH_MASK); + /* System Bus also means the target can have memory read without halting */ + target->target_options |= TOPT_NON_HALTING_MEM_IO; + } else { + /* + * If the system bus is not valid, we need to fall back to using abstract commands + * Later, if the memory access fails, we'll clear the flag and fall back to use the prog buffer + */ + hart->flags = RV_HART_FLAG_MEMORY_ABSTRACT; + } /* Make sure the system bus is not in any kind of error state */ (void)riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, 0x00407000U); } @@ -1091,17 +1144,6 @@ static void riscv_reset(target_s *const target) target_check_error(target); } -static const char *riscv_fpu_ext_string(const uint32_t extensions) -{ - if (extensions & RV_ISA_EXT_QUAD_FLOAT) - return "q"; - if (extensions & RV_ISA_EXT_DOUBLE_FLOAT) - return "d"; - if (extensions & RV_ISA_EXT_SINGLE_FLOAT) - return "f"; - return ""; -} - /* * Generate the FPU section of the description. * fpu_size = 32 -> single precision float @@ -1220,19 +1262,24 @@ static size_t riscv_build_target_fpu_description(char *const buffer, size_t max_ * */ static size_t riscv_build_target_description( - char *const buffer, size_t max_length, const uint8_t address_width, const uint32_t extensions) + char *const buffer, const size_t max_length, const uint8_t address_width, const uint32_t extensions) { - const bool embedded = extensions & RV_ISA_EXT_EMBEDDED; - const uint32_t fpu = extensions & RV_ISA_EXT_ANY_FLOAT; - size_t print_size = max_length; /* Start with the "preamble" chunks, which are mostly common across targets save for 2 words. */ - size_t offset = - (size_t)snprintf(buffer, print_size, "%s target %sriscv:rv%u%c%s%s ", - gdb_xml_preamble_first, gdb_xml_preamble_second, address_width, embedded ? 'e' : 'i', - riscv_fpu_ext_string(fpu), gdb_xml_preamble_third); + int offset = snprintf(buffer, print_size, "%s target %sriscv:", gdb_xml_preamble_first, gdb_xml_preamble_second); + if (max_length != 0) + print_size = max_length - (size_t)offset; + /* Write the architecture string, which is the ISA subset */ + offset += riscv_snprint_isa_subset(buffer + offset, print_size, address_width, extensions); + if (max_length != 0) + print_size = max_length - (size_t)offset; + /* Finally finish the rest of the preamble */ + offset += + snprintf(buffer + offset, print_size, "%s ", gdb_xml_preamble_third); + if (max_length != 0) + print_size = max_length - (size_t)offset; - const uint8_t gprs = embedded ? 16U : 32U; + const uint8_t gprs = extensions & RV_ISA_EXT_EMBEDDED ? 16U : 32U; /* Then build the general purpose register descriptions using the arrays at top of file */ /* Note that in a device using the embedded (E) extension, we only generate the first 16. */ for (uint8_t i = 0; i < gprs; ++i) { diff --git a/src/target/riscv_debug.h b/src/target/riscv_debug.h index aa4a305aa02..1bae6421bb6 100644 --- a/src/target/riscv_debug.h +++ b/src/target/riscv_debug.h @@ -45,6 +45,7 @@ typedef enum riscv_debug_version { RISCV_DEBUG_0_11, RISCV_DEBUG_0_13, RISCV_DEBUG_1_0, + RISCV_DEBUG_UNSPECIFIED = 15, /* Version not described in any available version of the spec */ } riscv_debug_version_e; /* This enum describes the Hart status (eg after a CSR read/write) */ @@ -150,27 +151,78 @@ typedef struct riscv_hart { uint32_t implid; uint32_t hartid; + char isa_name[32U]; + uint32_t triggers; uint32_t trigger_uses[RV_TRIGGERS_MAX]; } riscv_hart_s; #define RV_STATUS_VERSION_MASK 0x0000000fU -#define RV_DM_DATA0 0x04U -#define RV_DM_DATA1 0x05U -#define RV_DM_DATA2 0x06U -#define RV_DM_DATA3 0x07U -#define RV_DM_ABST_CTRLSTATUS 0x16U -#define RV_DM_ABST_COMMAND 0x17U -#define RV_DM_SYSBUS_CTRLSTATUS 0x38U -#define RV_DM_SYSBUS_ADDR0 0x39U -#define RV_DM_SYSBUS_ADDR1 0x3aU -#define RV_DM_SYSBUS_DATA0 0x3cU -#define RV_DM_SYSBUS_DATA1 0x3dU +#define RV_DMI_OP_NOOP 0U /* Ignore data and address */ +#define RV_DMI_OP_READ 1U /* Read data from address */ +#define RV_DMI_OP_WRITE 2U /* Write data to address */ +#define RV_DMI_OP_RESERVED 3U /* Reserved */ + +#define RV_DMI_SUCCESS 0U /* he previous operation completed successfully */ +#define RV_DMI_RESERVED 1U /* Reserved */ +#define RV_DMI_FAILURE 2U /* A previous operation failed */ +#define RV_DMI_TOO_SOON 3U /* An operation was attempted while a DMI request is still in progress */ + +/* Debug Module Registers (spec version 0.13.2) */ +#define RV_DM_DATA_BASE 0x04U /* Abstract Data 0 */ +#define RV_DM_DATA(n) (RV_DM_DATA_BASE + (n)) /* Abstract Data n (0 - 11) */ +#define RV_DM_CONTROL 0x10U /* Debug Module Control */ +#define RV_DM_STATUS 0x11U /* Debug Module Status */ +#define RV_DM_HART_INFO 0x12U /* Hart Info */ +#define RV_DM_HALT_SUM1 0x13U /* Halt Summary 1 */ +#define RV_DM_HAWINDOW_SEL 0x14U /* Hart Array Window Select */ +#define RV_DM_HAWINDOW 0x15U /* Hart Array Window */ +#define RV_DM_ABST_CTRLSTATUS 0x16U /* Abstract Control and Status */ +#define RV_DM_ABST_COMMAND 0x17U /* Abstract Command */ +#define RV_DM_ABST_AUTO 0x18U /* Abstract Command Autoexec */ +#define RV_DM_CONFSTRPTR0 0x19U /* Configuration String Pointer 0 */ +#define RV_DM_CONFSTRPTR1 0x1aU /* Configuration String Pointer 1 */ +#define RV_DM_CONFSTRPTR2 0x1bU /* Configuration String Pointer 2 */ +#define RV_DM_CONFSTRPTR3 0x1cU /* Configuration String Pointer 3 */ +#define RV_DM_NEXT_DM 0x1dU /* Next Debug Module */ +#define RV_DM_PROGBUF_BASE 0x20U /* Program Buffer 0 */ +#define RV_DM_PROGBUF(n) (RV_DM_PROGBUF_BASE + (n)) /* Program Buffer n (0 - 15) */ +#define RV_DM_AUTHDATA 0x30U /* Authentication Data */ +#define RV_DM_HALT_SUM2 0x34U /* Halt Summary 2 */ +#define RV_DM_HALT_SUM3 0x35U /* Halt Summary 3 */ +#define RV_DM_SYSBUS_ADDR3 0x37U /* System Bus Address 127:96 */ +#define RV_DM_SYSBUS_CTRLSTATUS 0x38U /* System Bus Access Control and Status */ +#define RV_DM_SYSBUS_ADDR0 0x39U /* System Bus Address 31:0 */ +#define RV_DM_SYSBUS_ADDR1 0x3aU /* System Bus Address 63:32 */ +#define RV_DM_SYSBUS_ADDR2 0x3bU /* System Bus Address 95:64 */ +#define RV_DM_SYSBUS_DATA0 0x3cU /* System Bus Data 31:0 */ +#define RV_DM_SYSBUS_DATA1 0x3dU /* System Bus Data 63:32 */ +#define RV_DM_SYSBUS_DATA2 0x3eU /* System Bus Data 95:64 */ +#define RV_DM_SYSBUS_DATA3 0x3fU /* System Bus Data 127:96 */ +#define RV_DM_HALT_SUM0 0x40U /* Halt Summary 0 */ #define RV_DM_ABST_CMD_ACCESS_REG 0x00000000U #define RV_DM_ABST_CMD_ACCESS_MEM 0x02000000U +#define RV_DM_CTRL_ACTIVE (1U << 0U) +#define RV_DM_CTRL_SYSTEM_RESET (1U << 1U) +#define RV_DM_CTRL_HARTSEL_MASK 0x03ffffc0U +#define RV_DM_CTRL_HARTSELLO_MASK 0x03ff0000U +#define RV_DM_CTRL_HARTSELHI_MASK 0x0000ffc0U +#define RV_DM_CTRL_HART_ACK_RESET (1U << 28U) +#define RV_DM_CTRL_HART_RESET (1U << 29U) +#define RV_DM_CTRL_RESUME_REQ (1U << 30U) +#define RV_DM_CTRL_HALT_REQ (1U << 31U) +#define RV_DM_CTRL_HARTSELLO_SHIFT 16U +#define RV_DM_CTRL_HARTSELHI_SHIFT 4U + +#define RV_DM_STAT_ALL_HALTED (1U << 9U) +#define RV_DM_STAT_UNAVAILABLE (1U << 12U) +#define RV_DM_STAT_NON_EXISTENT (1U << 14U) +#define RV_DM_STAT_ALL_RESUME_ACK (1U << 17U) +#define RV_DM_STAT_ALL_RESET (1U << 19U) + #define RV_ABST_READ (0U << 16U) #define RV_ABST_WRITE (1U << 16U) #define RV_REG_XFER (1U << 17U) @@ -201,30 +253,58 @@ typedef struct riscv_hart { /* The FP base defines the starting register space address for the floating point registers */ #define RV_FP_BASE 0x1020U -#define RV_ISA_EXT_EMBEDDED 0x00000010U -#define RV_ISA_EXT_ANY_FLOAT 0x00010028U -#define RV_ISA_EXT_SINGLE_FLOAT 0x00000020U -#define RV_ISA_EXT_DOUBLE_FLOAT 0x00000008U -#define RV_ISA_EXT_QUAD_FLOAT 0x00010000U +/* + * The Extensions field encodes the presence of standard extensions, with a single bit per alphabet letter + * (bit 0 encodes presence of extension “A” through to bit 25 which encodes “Z”) + * + * This list is taken from the RISC-V Instruction Set Manual v2.2 + * + * The list order is the canonical representation order in the ISA subset string + */ + +/* Base ISA */ +#define RV_ISA_EXT_INTEGER (1U << 8U) /* 'I': RV32I/64I/128I integer base ISA */ +#define RV_ISA_EXT_EMBEDDED (1U << 4U) /* 'E': RV32E reduced integer base ISA (Embedded) */ + +/* Standard general-purpose ISA */ +#define RV_ISA_EXT_MUL_DIV_INT (1U << 12U) /* 'M': Integer multiplication and division */ +#define RV_ISA_EXT_ATOMIC (1U << 0U) /* 'A': Atomic instructions */ +#define RV_ISA_EXT_SINGLE_FLOAT (1U << 5U) /* 'F': Single-precision floating-point */ +#define RV_ISA_EXT_DOUBLE_FLOAT (1U << 3U) /* 'D': Double-precision floating-point */ + +/* 'G' standard general-purpose ISA abbreviation, representing 'IMAFD' */ +#define RV_ISA_EXT_GENERAL_PURPOSE \ + (RV_ISA_EXT_INTEGER | RV_ISA_EXT_MUL_DIV_INT | RV_ISA_EXT_ATOMIC | RV_ISA_EXT_SINGLE_FLOAT | \ + RV_ISA_EXT_DOUBLE_FLOAT) + +/* Standard Unprivileged Extensions */ +#define RV_ISA_EXT_QUAD_FLOAT (1U << 16U) /* 'Q': Quad-precision floating-point */ +#define RV_ISA_EXT_DECIMAL_FLOAT (1U << 11U) /* 'L': Decimal floating-point */ +#define RV_ISA_EXT_COMPRESSED (1U << 2U) /* 'C': 16-bit compressed instructions */ +#define RV_ISA_EXT_BIT_MANIP (1U << 1U) /* 'B': Bit manipulation */ +#define RV_ISA_EXT_DYNAMIC_LANG (1U << 9U) /* 'J': Dynamic languages */ +#define RV_ISA_EXT_TRANSACT_MEM (1U << 19U) /* 'T': Transactional memory */ +#define RV_ISA_EXT_PACKED_SIMD (1U << 15U) /* 'P': Packed-SIMD */ +#define RV_ISA_EXT_VECTOR (1U << 21U) /* 'V': Vector extensions */ +#define RV_ISA_EXT_USER_INTERRUPTS (1U << 13U) /* 'N': User-level interrupts */ #define RV_TRIGGER_SUPPORT_MASK 0x0000fffeU #define RV_TRIGGER_MODE_MASK 0xffff0000U #define RV_TRIGGER_SUPPORT_BREAKWATCH 0x00000004U -/* - * The CSR number when requested by GDB is shifted by RV_CSR_GDB_OFFSET so they cannot collide with +/* The CSR number when requested by GDB is shifted by RV_CSR_GDB_OFFSET so they cannot collide with * the GPRs. As a result, we have to subtract RV_CSR_GDB_OFFSET from the value received from GDB. */ -#define RV_CSR_GDB_OFFSET 128 -#define RV_CSR_STATUS 0x300 -#define RV_CSR_MISA 0x301 -#define RV_CSR_MIE 0x304 -#define RV_CSR_MTVEC 0x305 -#define RV_CSR_MSCRATCH 0x340 -#define RV_CSR_MEPC 0x341 -#define RV_CSR_MCAUSE 0x342 -#define RV_CSR_MTVAL 0x343 -#define RV_CSR_MIP 0x344 +#define RV_CSR_GDB_OFFSET 128U +#define RV_CSR_STATUS 0x300U +#define RV_CSR_MISA 0x301U +#define RV_CSR_MIE 0x304U +#define RV_CSR_MTVEC 0x305U +#define RV_CSR_MSCRATCH 0x340U +#define RV_CSR_MEPC 0x341U +#define RV_CSR_MCAUSE 0x342U +#define RV_CSR_MTVAL 0x343U +#define RV_CSR_MIP 0x344U /* * These two lines are about allowing GDB to access FPU registers through fake registers offset by @@ -265,7 +345,4 @@ uint8_t riscv_mem_access_width(const riscv_hart_s *hart, target_addr_t address, void riscv32_unpack_data(void *dest, uint32_t data, uint8_t access_width); uint32_t riscv32_pack_data(const void *src, uint8_t access_width); -void riscv32_mem_read(target_s *target, void *dest, target_addr64_t src, size_t len); -void riscv32_mem_write(target_s *target, target_addr64_t dest, const void *src, size_t len); - #endif /*TARGET_RISCV_DEBUG_H*/ diff --git a/src/target/riscv_jtag_dtm.c b/src/target/riscv_jtag_dtm.c index 6b41971146a..0cdd1f3995f 100644 --- a/src/target/riscv_jtag_dtm.c +++ b/src/target/riscv_jtag_dtm.c @@ -50,13 +50,6 @@ #define RV_DTMCS_ADDRESS_MASK 0x000003f0U #define RV_DTMCS_ADDRESS_SHIFT 4U -#define RV_DMI_NOOP 0U -#define RV_DMI_READ 1U -#define RV_DMI_WRITE 2U -#define RV_DMI_SUCCESS 0U -#define RV_DMI_FAILURE 2U -#define RV_DMI_TOO_SOON 3U - #ifdef CONFIG_RISCV static void riscv_jtag_dtm_init(riscv_dmi_s *dmi); static uint32_t riscv_shift_dtmcs(const riscv_dmi_s *dmi, uint32_t control); @@ -147,7 +140,7 @@ static uint8_t riscv_shift_dmi(riscv_dmi_s *const dmi, const uint8_t operation, jtag_proc.jtagtap_tdi_seq(true, ones, device->dr_postscan); jtagtap_return_idle(dmi->idle_cycles); /* Translate error 1 into RV_DMI_FAILURE per the spec */ - if (status == 1U) + if (status == RV_DMI_RESERVED) return RV_DMI_FAILURE; return status; } @@ -183,33 +176,21 @@ static bool riscv_dmi_transfer(riscv_dmi_s *const dmi, const uint8_t operation, bool riscv_jtag_dmi_read(riscv_dmi_s *const dmi, const uint32_t address, uint32_t *const value) { - bool result = true; - do { - /* Setup the location to read from */ - result = riscv_dmi_transfer(dmi, RV_DMI_READ, address, 0U, NULL); - if (result) - /* If that worked, read back the value and check the operation status */ - result = riscv_dmi_transfer(dmi, RV_DMI_NOOP, 0U, 0U, value); - } while (dmi->fault == RV_DMI_TOO_SOON); - - if (!result) - DEBUG_WARN("DMI read at 0x%08" PRIx32 " failed with status %u\n", address, dmi->fault); + /* Setup the location to read from */ + bool result = riscv_dmi_transfer(dmi, RV_DMI_OP_READ, address, 0U, NULL); + if (result) + /* If that worked, read back the value and check the operation status */ + result = riscv_dmi_transfer(dmi, RV_DMI_OP_NOOP, 0U, 0U, value); return result; } bool riscv_jtag_dmi_write(riscv_dmi_s *const dmi, const uint32_t address, const uint32_t value) { - bool result = true; - do { - /* Write a value to the requested register */ - result = riscv_dmi_transfer(dmi, RV_DMI_WRITE, address, value, NULL); - if (result) - /* If that worked, read back the operation status to ensure the write actually worked */ - result = riscv_dmi_transfer(dmi, RV_DMI_NOOP, 0U, 0U, NULL); - } while (dmi->fault == RV_DMI_TOO_SOON); - - if (!result) - DEBUG_WARN("DMI write at 0x%08" PRIx32 " failed with status %u\n", address, dmi->fault); + /* Write a value to the requested register */ + bool result = riscv_dmi_transfer(dmi, RV_DMI_OP_WRITE, address, value, NULL); + if (result) + /* If that worked, read back the operation status to ensure the write actually worked */ + result = riscv_dmi_transfer(dmi, RV_DMI_OP_NOOP, 0U, 0U, NULL); return result; } diff --git a/src/target/rvswd_dtm.c b/src/target/rvswd_dtm.c new file mode 100644 index 00000000000..2d27df12159 --- /dev/null +++ b/src/target/rvswd_dtm.c @@ -0,0 +1,376 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2025 1BitSquared + * Written by Rafael Silva + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* This file implements RVSWD protocol support. TODO */ + +#include "general.h" +#include "platform.h" +#include "rvswd.h" +#include "riscv_debug.h" +#include "maths_utils.h" +#include "jep106.h" + +// #define ENABLE_LONG_PACKET_PROBE 1 + +#define WCH_DTM_IDCODE 0x7fU /* Non-standard DM register which mirrors the target IDCODE register */ + +static void rvswd_wakeup_sequence(const bool stop_condition) +{ + /* + * A wakeup sequence consist of 100 CLK cycles with DIO high followed by a STOP condition + */ + DEBUG_INFO("Performing RVSWD wakeup sequence %s stop condition\n", stop_condition ? "with" : "without"); + + platform_critical_enter(); + + /* 100 CLK cycles with DIO high */ + rvswd_proc.seq_out(0xffffffffU, 32U); /* 32 */ + rvswd_proc.seq_out(0xffffffffU, 32U); /* 64 */ + rvswd_proc.seq_out(0xffffffffU, 32U); /* 96 */ + rvswd_proc.seq_out(0xffffffffU, 4U); /* 100 */ + + if (stop_condition) + rvswd_proc.stop(); /* STOP condition */ + + platform_critical_exit(); +} + +static bool rvswd_transfer_dmi_long(const uint8_t operation, const uint32_t address, const uint32_t value, + uint32_t *const result, uint8_t *const status) +{ + /* + * RVSWD "Long" packet format: + * + * + */ + // DEBUG_INFO("Performing RVSWD long DMI transfer: operation %d, address 0x%08" PRIx32 ", value 0x%08" PRIx32 "\n", + // operation, address, value); + + /* RVSWD DTM Address space is limited to 7 bits */ + if (address & ~0x7fU) { + DEBUG_ERROR("Address 0x%08" PRIx32 " is too large for RVSWD\n", address); + return false; + } + + /* TODO: This is just experimental code, it should packed into 32-bit words for efficiency */ + + const uint8_t host_parity = + calculate_odd_parity(address & 0x7fU) ^ calculate_odd_parity(value) ^ calculate_odd_parity(operation & 0x3U); + + platform_critical_enter(); + + /* Start condition */ + rvswd_proc.start(); + + /* Host address */ + rvswd_proc.seq_out(address & 0x7fU, 7U); + + /* Host data */ + rvswd_proc.seq_out(value, 32U); + + /* Operation */ + rvswd_proc.seq_out(operation & 0x3U, 2U); + + /* Host parity */ + rvswd_proc.seq_out(host_parity, 1U); + + /* Target address */ + const uint32_t target_address = rvswd_proc.seq_in(7U); + + /* Target data */ + const uint32_t target_data = rvswd_proc.seq_in(32U); + + /* Status */ + const uint32_t target_status = rvswd_proc.seq_in(2U); + + /* Target parity */ + const uint32_t target_parity = rvswd_proc.seq_in(1U); + + /* Stop condition */ + rvswd_proc.stop(); + + platform_critical_exit(); + + /* Check parity */ + const uint8_t calculated_target_parity = calculate_odd_parity(target_address & 0x7fU) ^ + calculate_odd_parity(target_data) ^ calculate_odd_parity(target_status & 0x3U); + + if (target_parity != calculated_target_parity) { + DEBUG_ERROR("Parity error in RVSWD long transfer: expected %u, got %u\n", (uint8_t)target_parity, + (uint8_t)calculated_target_parity); + return false; + } + + // DEBUG_INFO("RVSWD long DMI transfer result: status %u, address 0x%08" PRIx32 ", value 0x%08" PRIx32 "\n", + // (uint8_t)target_status, target_address, target_data); + + if (result) + *result = target_data; + if (status) + *status = target_status; + + return true; +} + +static bool rvswd_riscv_dmi_read_long(riscv_dmi_s *const dmi, const uint32_t address, uint32_t *const value) +{ + uint8_t status = 0; + const bool result = rvswd_transfer_dmi_long(RV_DMI_OP_READ, address, 0, value, &status); + + /* Translate error 1 into RV_DMI_FAILURE per the spec, also write RV_DMI_FAILURE if the transfer failed */ + dmi->fault = !result || status == RV_DMI_RESERVED ? RV_DMI_FAILURE : status; + return dmi->fault == RV_DMI_SUCCESS; +} + +static bool rvswd_riscv_dmi_write_long(riscv_dmi_s *const dmi, const uint32_t address, const uint32_t value) +{ + uint8_t status = 0; + const bool result = rvswd_transfer_dmi_long(RV_DMI_OP_WRITE, address, value, NULL, &status); + + /* Translate error 1 into RV_DMI_FAILURE per the spec, also write RV_DMI_FAILURE if the transfer failed */ + dmi->fault = !result || status == RV_DMI_RESERVED ? RV_DMI_FAILURE : status; + return dmi->fault == RV_DMI_SUCCESS; +} + +static inline uint8_t rvswd_calculate_parity_short(const uint32_t value) +{ + /* Short packet parity is odd parity */ + const uint8_t parity = calculate_odd_parity(value); + /* Short packet parity bit is duplicated for some strange reason */ + return parity << 1U | parity; +} + +static bool rvswd_transfer_dmi_short( + const bool write, const uint32_t address, const uint32_t value, uint32_t *const result, uint8_t *const status) +{ + /* + * RVSWD "Short" packet format: + * + * + */ + // DEBUG_INFO("Performing RVSWD short DMI transfer: %s, address 0x%08" PRIx32 ", value 0x%08" PRIx32 "\n", + // write ? "write" : "read", address, value); + + /* RVSWD DTM Address space is limited to 7 bits */ + if (address & ~0x7fU) { + DEBUG_ERROR("Address 0x%08" PRIx32 " is too large for RVSWD\n", address); + return false; + } + + /* TODO: This is just experimental code, it should packed into 32-bit words for efficiency */ + + /* Host parity */ + const uint8_t host_parity = rvswd_calculate_parity_short(((address & 0x7fU) << 1U) | (uint32_t)write); + uint32_t data_parity = write ? rvswd_calculate_parity_short(value) : 0U; + + platform_critical_enter(); + + /* Start condition */ + rvswd_proc.start(); + + /* Host address */ + rvswd_proc.seq_out(address & 0x7fU, 7U); + + /* Operation */ + rvswd_proc.seq_out(write, 1U); + + /* Host parity */ + rvswd_proc.seq_out(host_parity, 2U); + + /* 4 zero (padding?) bits */ + rvswd_proc.seq_out(0U, 4U); + + if (write) { + /* Host data */ + rvswd_proc.seq_out(value, 32U); + + /* Host data parity */ + rvswd_proc.seq_out(data_parity, 2U); + } else { + /* Target data */ + *result = rvswd_proc.seq_in(32U); + + /* Target parity */ + data_parity = rvswd_proc.seq_in(2U) & 0x3U; + } + + /* Status (4 bits? also duplicated?) seems more like last 2 bits are padding 1 */ + const uint32_t raw_status = rvswd_proc.seq_in(4U) & 0xfU; + + /* Stop condition */ + rvswd_proc.stop(); + + platform_critical_exit(); + + /* Check parity */ + if (!write) { + const uint8_t calculated_data_parity = rvswd_calculate_parity_short(*result); + if (data_parity != calculated_data_parity) { + DEBUG_ERROR("Parity error in RVSWD short transfer: expected %u, got %u\n", (uint8_t)data_parity, + (uint8_t)calculated_data_parity); + return false; + } + } + + /* discard the padding bits */ + *status = raw_status >> 2U; + + // DEBUG_INFO("RVSWD short DMI transfer result: status %u (raw %u), address 0x%08" PRIx32 ", value 0x%08" PRIx32 "\n", + // (uint8_t)*status, (uint8_t)raw_status, address, write ? value : *result); + + return true; +} + +static bool rvswd_riscv_dmi_read_short(riscv_dmi_s *const dmi, const uint32_t address, uint32_t *const value) +{ + uint8_t status = 0; + const bool result = rvswd_transfer_dmi_short(false, address, 0, value, &status); + + /* Translate error 1 into RV_DMI_FAILURE per the spec, also write RV_DMI_FAILURE if the transfer failed */ + dmi->fault = !result || status == RV_DMI_RESERVED ? RV_DMI_FAILURE : status; + return dmi->fault == RV_DMI_SUCCESS; +} + +static bool rvswd_riscv_dmi_write_short(riscv_dmi_s *const dmi, const uint32_t address, const uint32_t value) +{ + uint8_t status = 0; + const bool result = rvswd_transfer_dmi_short(true, address, value, NULL, &status); + + /* Translate error 1 into RV_DMI_FAILURE per the spec, also write RV_DMI_FAILURE if the transfer failed */ + dmi->fault = !result || status == RV_DMI_RESERVED ? RV_DMI_FAILURE : status; + return dmi->fault == RV_DMI_SUCCESS; +} + +static void rvswd_riscv_dtm_init(riscv_dmi_s *const dmi, const bool short_packets) +{ + /* WCH-Link doesn't have any mechanism to identify the DTM manufacturer, so we'll just assume it's WCH */ + dmi->designer_code = NOT_JEP106_MANUFACTURER_WCH; + + dmi->version = RISCV_DEBUG_UNSPECIFIED; /* Not available, compatible with version 0.13 */ + + /* WCH-Link RVSWD has a fixed address width of 7 bits */ + dmi->address_width = 7U; + + if (short_packets) { + dmi->read = rvswd_riscv_dmi_read_short; + dmi->write = rvswd_riscv_dmi_write_short; + } else { + dmi->read = rvswd_riscv_dmi_read_long; + dmi->write = rvswd_riscv_dmi_write_long; + } + + riscv_dmi_init(dmi); +} + +static void riscv_rvswd_dtm_handler(const bool short_packets) +{ + riscv_dmi_s *const dmi = calloc(1, sizeof(*dmi)); + if (!dmi) { /* calloc failed: heap exhaustion */ + DEBUG_WARN("calloc: failed in %s\n", __func__); + return; + } + + rvswd_riscv_dtm_init(dmi, short_packets); + /* If we failed to find any DMs or Harts, free the structure */ + if (!dmi->ref_count) + free(dmi); +} + +bool rvswd_scan(void) +{ + /* Free the device list if any, and clean state ready */ + target_list_free(); + +#if CONFIG_BMDA == 0 + rvswd_init(); +#endif + + platform_target_clk_output_enable(true); + + /* Run the wakeup sequence */ + rvswd_wakeup_sequence(true); + +#ifdef ENABLE_LONG_PACKET_PROBE + /* Look for a DTM with long packets */ + DEBUG_INFO("Scanning for RISC-V DTM with RVSWD long packets\n"); + /* WCH-Link attempts 202 times, we can probably do less */ + for (size_t i = 0; i < 202U; i++) { + /* Read the DTM status register */ + uint8_t status = 0U; + uint32_t value = 0U; + if (!rvswd_transfer_dmi_long(RV_DMI_READ, RV_DM_STATUS, 0, &value, &status) || status != RV_DMI_SUCCESS) + continue; + + /* A successful read of the status register means we found a DTM, probably? */ + if (value != 0U && value != 0xffffffffU) { + /* Delegate to the RISC-V DTM handler */ + riscv_rvswd_dtm_handler(false); + return true; + } + } +#endif + + /* Run the short packet wakeup sequence */ + rvswd_wakeup_sequence(false); + + /* Look for a DTM with short packets */ + DEBUG_INFO("Scanning for RISC-V DTM with RVSWD short packets\n"); + for (size_t i = 0; i < 10U; i++) { + /* Enable the DM */ + /* TODO: verify the spec, is the STATUS register available in all states? if so use it */ + uint8_t status = 0U; + if (!rvswd_transfer_dmi_short(true, RV_DM_CONTROL, RV_DM_CTRL_ACTIVE, NULL, &status) || + status != RV_DMI_SUCCESS) + continue; + if (!rvswd_transfer_dmi_short(true, RV_DM_CONTROL, RV_DM_CTRL_ACTIVE, NULL, &status) || + status != RV_DMI_SUCCESS) + continue; + + /* Read the WCH IDCODE register */ + uint32_t idcode = 0U; + if (!rvswd_transfer_dmi_short(false, WCH_DTM_IDCODE, 0U, &idcode, &status) || status != RV_DMI_SUCCESS) + continue; + + /* A successful read of the IDCODE register means we found a DTM, probably */ + if (idcode != 0U && idcode != 0xffffffffU) { + /* Put the DM back into reset so it's in a known good state */ + (void)rvswd_transfer_dmi_short(true, RV_DM_CONTROL, 0U, NULL, &status); + + /* Delegate to the RISC-V DTM handler */ + riscv_rvswd_dtm_handler(true); + return true; + } + } + + return false; +} diff --git a/src/target/target_internal.h b/src/target/target_internal.h index f7755cc94ae..85e58c93ab8 100644 --- a/src/target/target_internal.h +++ b/src/target/target_internal.h @@ -171,7 +171,7 @@ struct target { /* Other stuff */ const char *driver; uint32_t cpuid; - char *core; + const char *core; char cmdline[MAX_CMDLINE]; target_addr_t heapinfo[4]; target_command_s *commands; diff --git a/src/target/target_probe.c b/src/target/target_probe.c index 2fee2a324e1..5be60806147 100644 --- a/src/target/target_probe.c +++ b/src/target/target_probe.c @@ -104,6 +104,8 @@ CORTEXM_PROBE_WEAK_NOP(rp2040_rescue_probe) TARGET_PROBE_WEAK_NOP(apollo_3_probe) TARGET_PROBE_WEAK_NOP(at32f40x_probe) TARGET_PROBE_WEAK_NOP(at32f43x_probe) +TARGET_PROBE_WEAK_NOP(ch32v003x_probe) +TARGET_PROBE_WEAK_NOP(ch32vx_probe) TARGET_PROBE_WEAK_NOP(ch32f1_probe) TARGET_PROBE_WEAK_NOP(ch579_probe) TARGET_PROBE_WEAK_NOP(efm32_probe) diff --git a/src/target/target_probe.h b/src/target/target_probe.h index a68693dea8c..9976a23cd5a 100644 --- a/src/target/target_probe.h +++ b/src/target/target_probe.h @@ -58,6 +58,8 @@ bool at32f43x_probe(target_s *target); bool ch32f1_probe(target_s *target); // will catch all the clones bool ch579_probe(target_s *target); bool efm32_probe(target_s *target); +bool ch32v003x_probe(target_s *target); +bool ch32vx_probe(target_s *target); bool gd32f1_probe(target_s *target); bool gd32f4_probe(target_s *target); bool gd32vf1_probe(target_s *target);