From 00db0eeab206a02cb2e9144ebd76f88638f80b06 Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Thu, 10 Aug 2023 17:25:34 +0100 Subject: [PATCH 01/18] hosted: barebones wch-link implementation --- src/platforms/hosted/bmp_libusb.c | 3 +- src/platforms/hosted/meson.build | 1 + src/platforms/hosted/platform.c | 9 + src/platforms/hosted/platform.h | 6 + src/platforms/hosted/wchlink.c | 241 ++++++++++++++++++ src/platforms/hosted/wchlink.h | 40 +++ src/platforms/hosted/wchlink_protocol.h | 321 ++++++++++++++++++++++++ 7 files changed, 620 insertions(+), 1 deletion(-) create mode 100644 src/platforms/hosted/wchlink.c create mode 100644 src/platforms/hosted/wchlink.h create mode 100644 src/platforms/hosted/wchlink_protocol.h diff --git a/src/platforms/hosted/bmp_libusb.c b/src/platforms/hosted/bmp_libusb.c index dd3c06b994c..07b59ca4e82 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..321e040439a 100644 --- a/src/platforms/hosted/meson.build +++ b/src/platforms/hosted/meson.build @@ -54,6 +54,7 @@ bmda_sources = files( 'jlink.c', 'jlink_jtag.c', 'jlink_swd.c', + 'wchlink.c', ) subdir('remote') diff --git a/src/platforms/hosted/platform.c b/src/platforms/hosted/platform.c index ad508cd58e5..116ca55e450 100644 --- a/src/platforms/hosted/platform.c +++ b/src/platforms/hosted/platform.c @@ -51,6 +51,7 @@ #include "stlinkv2.h" #include "ftdi_bmp.h" #include "jlink.h" +#include "wchlink.h" #include "cmsis_dap.h" #endif @@ -163,6 +164,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 @@ -411,6 +417,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..78a8544a78b 100644 --- a/src/platforms/hosted/platform.h +++ b/src/platforms/hosted/platform.h @@ -77,6 +77,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 +89,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..7decf28fb76 --- /dev/null +++ b/src/platforms/hosted/wchlink.c @@ -0,0 +1,241 @@ +/* + * 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" + +typedef struct wchlink { + struct wchlink_fw_version { + uint8_t major; + uint8_t minor; + } fw_version; /* Firmware version */ + + uint8_t hw_type; /* Hardware type */ +} 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; +} + +/* + * 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 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; +} + +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..c653801d2ef --- /dev/null +++ b/src/platforms/hosted/wchlink.h @@ -0,0 +1,40 @@ +/* + * 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); + +#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..0fa46e27736 --- /dev/null +++ b/src/platforms/hosted/wchlink_protocol.h @@ -0,0 +1,321 @@ +/* + * 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); + +#endif /* PLATFORMS_HOSTED_WCHLINK_PROTOCOL_H */ From 9cbf248bf7fa3b372c09569fa1be80880219b86f Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Fri, 11 Aug 2023 16:54:44 +0100 Subject: [PATCH 02/18] hosted/wchlink: implement DMI transfer --- src/platforms/hosted/wchlink.c | 73 +++++++++++++++++++++++++ src/platforms/hosted/wchlink_protocol.h | 1 + 2 files changed, 74 insertions(+) diff --git a/src/platforms/hosted/wchlink.c b/src/platforms/hosted/wchlink.c index 7decf28fb76..f342ec62c66 100644 --- a/src/platforms/hosted/wchlink.c +++ b/src/platforms/hosted/wchlink.c @@ -34,6 +34,7 @@ #include "wchlink.h" #include "wchlink_protocol.h" +#include "buffer_utils.h" typedef struct wchlink { struct wchlink_fw_version { @@ -119,6 +120,78 @@ bool wchlink_command_send_recv(const uint8_t command, const uint8_t subcommand, 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 diff --git a/src/platforms/hosted/wchlink_protocol.h b/src/platforms/hosted/wchlink_protocol.h index 0fa46e27736..7c106dcc166 100644 --- a/src/platforms/hosted/wchlink_protocol.h +++ b/src/platforms/hosted/wchlink_protocol.h @@ -317,5 +317,6 @@ 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); #endif /* PLATFORMS_HOSTED_WCHLINK_PROTOCOL_H */ From 5e9cd9222d647f639f2737070c03bc490ffcc47f Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Fri, 11 Aug 2023 16:49:08 +0100 Subject: [PATCH 03/18] riscv_debug: expose DMI operation and status defines --- src/target/riscv_debug.h | 7 +++++++ src/target/riscv_jtag_dtm.c | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/target/riscv_debug.h b/src/target/riscv_debug.h index 9db24a4993e..8e5f633e743 100644 --- a/src/target/riscv_debug.h +++ b/src/target/riscv_debug.h @@ -158,6 +158,13 @@ typedef struct riscv_hart { #define RV_STATUS_VERSION_MASK 0x0000000fU +#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 + #define RV_DM_DATA0 0x04U #define RV_DM_DATA1 0x05U #define RV_DM_DATA2 0x06U diff --git a/src/target/riscv_jtag_dtm.c b/src/target/riscv_jtag_dtm.c index 6b41971146a..edbf8e82b32 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); From 433ac22c6c048f99d5b67e8932b26c2722c56e4e Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Fri, 11 Aug 2023 21:17:56 +0100 Subject: [PATCH 04/18] riscv_jtag_dtm: move RV_DMI_TOO_SOON handling one level up --- src/target/riscv_debug.c | 18 ++++++++++++++++-- src/target/riscv_jtag_dtm.c | 32 ++++++++++---------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/target/riscv_debug.c b/src/target/riscv_debug.c index b16d60a1129..3cf30cc67e3 100644 --- a/src/target/riscv_debug.c +++ b/src/target/riscv_debug.c @@ -557,16 +557,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) diff --git a/src/target/riscv_jtag_dtm.c b/src/target/riscv_jtag_dtm.c index edbf8e82b32..df3a09631f6 100644 --- a/src/target/riscv_jtag_dtm.c +++ b/src/target/riscv_jtag_dtm.c @@ -176,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_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); 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_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); return result; } From 0225657c3f8c1deecf8c94c2d4737fac546fd307 Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Wed, 22 Jan 2025 22:43:45 +0000 Subject: [PATCH 05/18] riscv_debug: add non standard dm/dmi version enum Since spec v0.13 there is an explicit version value (15) to identify dm/dmi's that do not conform with any available specs --- src/target/riscv_debug.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/target/riscv_debug.h b/src/target/riscv_debug.h index 8e5f633e743..3aa74d33a11 100644 --- a/src/target/riscv_debug.h +++ b/src/target/riscv_debug.h @@ -41,10 +41,16 @@ typedef enum riscv_debug_version { RISCV_DEBUG_UNKNOWN, + /* No Debug Module [Interface] present */ RISCV_DEBUG_UNIMPL, + /* Debug Module [Interface] conforming to version 0.11 of the spec */ RISCV_DEBUG_0_11, + /* Debug Module [Interface] conforming to version 0.13 of the spec */ RISCV_DEBUG_0_13, + /* Debug Module [Interface] conforming to version 1.0 of the spec */ RISCV_DEBUG_1_0, + /* Debug Module [Interface] not conforming to any available version of the spec */ + RISCV_DEBUG_NONSTANDARD, } riscv_debug_version_e; /* This enum describes the Hart status (eg after a CSR read/write) */ From c0413fdc68d5bede42ea8c611db77d2ebec3df27 Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Wed, 22 Jan 2025 22:49:22 +0000 Subject: [PATCH 06/18] riscv_debug: identify non-standard dm/dmi's Identify but allow non-standard DMI versions but only process standard explicitly know DM versions --- src/target/riscv_adi_dtm.c | 2 +- src/target/riscv_debug.c | 26 +++++++++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/target/riscv_adi_dtm.c b/src/target/riscv_adi_dtm.c index 6c9cce48c62..f46693311cd 100644 --- a/src/target/riscv_adi_dtm.c +++ b/src/target/riscv_adi_dtm.c @@ -51,7 +51,7 @@ void riscv_adi_dtm_handler(adiv5_access_port_s *const ap) dmi_ap->dmi.dev_index = 0xffU; dmi_ap->dmi.idle_cycles = 0xffU; dmi_ap->dmi.designer_code = ap->dp->designer_code; - dmi_ap->dmi.version = RISCV_DEBUG_0_13; /* The DMI version doesn't actually matter, so just make it spec v0.13 */ + dmi_ap->dmi.version = RISCV_DEBUG_NONSTANDARD; /* This DTM/DMI is not part of any official spec */ dmi_ap->dmi.address_width = ap->flags & ADIV5_AP_FLAGS_64BIT ? 64U : 32U; dmi_ap->dmi.read = riscv_adi_dmi_read; diff --git a/src/target/riscv_debug.c b/src/target/riscv_debug.c index 3cf30cc67e3..0dd02dc76e2 100644 --- a/src/target/riscv_debug.c +++ b/src/target/riscv_debug.c @@ -290,13 +290,22 @@ static void riscv_reset(target_s *target); void riscv_dmi_init(riscv_dmi_s *const dmi) { - /* If we don't currently know how to talk to this DMI, warn and fail */ + /* + * The DMI version does not actually matter here, the implementation details have already been + * abstracted away at this point and we have a generic DMI to work with + * + * But the dminfo register (at 0x11) of v0.11 DM is incompatible with dmstatus (also at 0x11) of + * later versions, meaning we can't easily/reliably determine the version of the DM. + * We ignore all v0.11 DMI's in the hope we don't encounter a v0.11 DM with a later version DMI. + */ if (dmi->version == RISCV_DEBUG_UNKNOWN) return; if (dmi->version == RISCV_DEBUG_0_11) { - DEBUG_INFO("RISC-V debug v0.11 not presently supported\n"); + DEBUG_INFO("RISC-V debug v0.11 DMI is not presently supported\n"); return; } + if (dmi->version == RISCV_DEBUG_NONSTANDARD) + DEBUG_INFO("RISC-V non-standard DMI, proceeding anyway\n"); /* Iterate through the possible DMs and probe implemented ones */ /* The first DM is always at base address 0 */ @@ -310,8 +319,11 @@ void riscv_dmi_init(riscv_dmi_s *const dmi) } const riscv_debug_version_e dm_version = riscv_dm_version(dm_status); - /* If the DM is not unimplemented, allocate a structure for it and do further processing */ - if (dm_version != RISCV_DEBUG_UNIMPL) { + /* + * If the DM is not unimplemented and is a supported version, + * allocate a structure for it and do further processing + */ + if (dm_version == RISCV_DEBUG_0_13 || dm_version == RISCV_DEBUG_1_0) { riscv_dm_s *dbg_module = calloc(1, sizeof(*dbg_module)); if (!dbg_module) { /* calloc failed: heap exhaustion */ DEBUG_WARN("calloc: failed in %s\n", __func__); @@ -325,7 +337,8 @@ void riscv_dmi_init(riscv_dmi_s *const dmi) /* If we failed to discover any Harts, free the structure */ if (!dbg_module->ref_count) free(dbg_module); - } + } else if (dm_version != RISCV_DEBUG_UNIMPL) + DEBUG_INFO("Ignoring DM with unsupported version\n"); /* Read out the address of the next DM */ if (!riscv_dmi_read(dmi, base_addr + RV_DM_NEXT_DM, &base_addr)) { @@ -608,6 +621,9 @@ static riscv_debug_version_e riscv_dm_version(const uint32_t status) case 3: DEBUG_INFO("RISC-V debug v1.0 DM\n"); return RISCV_DEBUG_1_0; + case 15: + DEBUG_INFO("RISC-V debug non-standard DM\n"); + return RISCV_DEBUG_NONSTANDARD; default: break; } From 0c4923aaa78c7696088b60f46633932f60357c1c Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Fri, 11 Aug 2023 18:31:26 +0100 Subject: [PATCH 07/18] hosted/wchlink: implement RISC-V DTM handler --- src/platforms/hosted/meson.build | 1 + src/platforms/hosted/wchlink.h | 1 + src/platforms/hosted/wchlink_riscv_dtm.c | 94 ++++++++++++++++++++++++ src/target/jep106.h | 7 +- 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 src/platforms/hosted/wchlink_riscv_dtm.c diff --git a/src/platforms/hosted/meson.build b/src/platforms/hosted/meson.build index 321e040439a..7acc54eb61b 100644 --- a/src/platforms/hosted/meson.build +++ b/src/platforms/hosted/meson.build @@ -55,6 +55,7 @@ bmda_sources = files( 'jlink_jtag.c', 'jlink_swd.c', 'wchlink.c', + 'wchlink_riscv_dtm.c', ) subdir('remote') diff --git a/src/platforms/hosted/wchlink.h b/src/platforms/hosted/wchlink.h index c653801d2ef..720d7f74760 100644 --- a/src/platforms/hosted/wchlink.h +++ b/src/platforms/hosted/wchlink.h @@ -36,5 +36,6 @@ #include "bmp_hosted.h" bool wchlink_init(void); +void wchlink_riscv_dtm_handler(void); #endif /* PLATFORMS_HOSTED_WCHLINK_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..e0c8417e06c --- /dev/null +++ b/src/platforms/hosted/wchlink_riscv_dtm.c @@ -0,0 +1,94 @@ +/* + * 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 = JEP106_MANUFACTURER_WCH; + + /* This DTM/DMI is not part of any official spec */ + dmi->version = RISCV_DEBUG_NONSTANDARD; + + /* + * WCH-Link has a fixed address width of 7 bits, + * technically only 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_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 == 1U ? 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_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 == 1U ? RV_DMI_FAILURE : status; + return dmi->fault == RV_DMI_SUCCESS; +} diff --git a/src/target/jep106.h b/src/target/jep106.h index f9225067151..1dd626b7669 100644 --- a/src/target/jep106.h +++ b/src/target/jep106.h @@ -67,7 +67,6 @@ #define JEP106_MANUFACTURER_GIGADEVICE 0x751U /* GigaDevice */ #define JEP106_MANUFACTURER_RASPBERRY 0x913U /* Raspberry Pi */ #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 */ /* * This JEP code should belong to "Andes Technology Corporation", but is used on RISC-V by GigaDevice, @@ -75,6 +74,12 @@ */ #define JEP106_MANUFACTURER_RV_GIGADEVICE 0x61eU +/* + * Used by WCH (WinChipHead) aka Nanjing Qinheng Microelectronics + * Not the same company but related + */ +#define JEP106_MANUFACTURER_WCH 0x72aU /* Nanjing Yihuo Technology */ + /* * This code is not listed in the JEP106 standard, but is used by some stm32f1 clones * since we're not using this code elsewhere let's switch to the stm code. From fbb26a124dc972bbd7813cc148b4355e3bfb6b2d Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Wed, 22 Jan 2025 22:41:16 +0000 Subject: [PATCH 08/18] riscv_debug: make dmi op constants more verbose --- src/platforms/hosted/wchlink_riscv_dtm.c | 8 ++++---- src/target/riscv_debug.h | 15 +++++++++------ src/target/riscv_jtag_dtm.c | 14 ++++++-------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/platforms/hosted/wchlink_riscv_dtm.c b/src/platforms/hosted/wchlink_riscv_dtm.c index e0c8417e06c..0531655b7b7 100644 --- a/src/platforms/hosted/wchlink_riscv_dtm.c +++ b/src/platforms/hosted/wchlink_riscv_dtm.c @@ -76,19 +76,19 @@ static void wchlink_riscv_dtm_init(riscv_dmi_s *const 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_READ, address, 0, value, &status); + 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 == 1U ? RV_DMI_FAILURE : status; + 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_WRITE, address, value, NULL, &status); + 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 == 1U ? RV_DMI_FAILURE : status; + dmi->fault = !result || status == RV_DMI_RESERVED ? RV_DMI_FAILURE : status; return dmi->fault == RV_DMI_SUCCESS; } diff --git a/src/target/riscv_debug.h b/src/target/riscv_debug.h index 3aa74d33a11..0c89137b06d 100644 --- a/src/target/riscv_debug.h +++ b/src/target/riscv_debug.h @@ -164,12 +164,15 @@ typedef struct riscv_hart { #define RV_STATUS_VERSION_MASK 0x0000000fU -#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 +#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 /* The 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 */ #define RV_DM_DATA0 0x04U #define RV_DM_DATA1 0x05U diff --git a/src/target/riscv_jtag_dtm.c b/src/target/riscv_jtag_dtm.c index df3a09631f6..f5e43803863 100644 --- a/src/target/riscv_jtag_dtm.c +++ b/src/target/riscv_jtag_dtm.c @@ -139,10 +139,8 @@ static uint8_t riscv_shift_dmi(riscv_dmi_s *const dmi, const uint8_t operation, jtag_proc.jtagtap_tdi_seq(!device->dr_postscan, (const uint8_t *)&address, dmi->address_width); 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) - return RV_DMI_FAILURE; - return status; + /* Translate error 1 (Reserved) into RV_DMI_FAILURE per the spec */ + return status == RV_DMI_RESERVED ? RV_DMI_FAILURE : status; } static bool riscv_dmi_transfer(riscv_dmi_s *const dmi, const uint8_t operation, const uint32_t address, @@ -177,20 +175,20 @@ 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) { /* Setup the location to read from */ - bool result = riscv_dmi_transfer(dmi, RV_DMI_READ, address, 0U, NULL); + 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_NOOP, 0U, 0U, value); + 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) { /* Write a value to the requested register */ - bool result = riscv_dmi_transfer(dmi, RV_DMI_WRITE, address, value, NULL); + 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_NOOP, 0U, 0U, NULL); + result = riscv_dmi_transfer(dmi, RV_DMI_OP_NOOP, 0U, 0U, NULL); return result; } From d188055fed6e464068e9da164ad0b99852b8a445 Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Fri, 11 Aug 2023 15:43:13 +0100 Subject: [PATCH 09/18] command: add rvswd scan routine command --- src/command.c | 57 ++++++++++++++++++++++++++++++++- src/include/target.h | 1 + src/platforms/hosted/platform.c | 10 ++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/command.c b/src/command.c index c30043b1fdd..902aac9e304 100644 --- a/src/command.c +++ b/src/command.c @@ -56,6 +56,7 @@ 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); +static bool cmd_rvswd_scan(target_s *target, int argc, const char **argv); 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); @@ -97,6 +98,7 @@ 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"}, + {"rvswd_scan", cmd_rvswd_scan, "Scan RVSWD for devices"}, {"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"}, @@ -322,6 +324,50 @@ bool cmd_swd_scan(target_s *target, int argc, const char **argv) return true; } +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 = false; +#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; +} + bool cmd_auto_scan(target_s *target, int argc, const char **argv) { (void)target; @@ -348,8 +394,17 @@ 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"); + +#if CONFIG_BMDA == 1 + scan_result = bmda_rvswd_scan(); +#else + scan_result = false; +#endif + if (!scan_result) + gdb_out("RVSWD scan found no devices.\n"); + } } } CATCH () { diff --git a/src/include/target.h b/src/include/target.h index c32c9db245d..21619d57f42 100644 --- a/src/include/target.h +++ b/src/include/target.h @@ -45,6 +45,7 @@ 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); diff --git a/src/platforms/hosted/platform.c b/src/platforms/hosted/platform.c index 116ca55e450..09f1419b40f 100644 --- a/src/platforms/hosted/platform.c +++ b/src/platforms/hosted/platform.c @@ -315,6 +315,16 @@ bool bmda_jtag_init(void) } } +bool bmda_rvswd_scan() +{ + bmda_probe_info.is_jtag = false; + + switch (bmda_probe_info.type) { + default: + return false; + } +} + void bmda_adiv5_dp_init(adiv5_debug_port_s *const dp) { switch (bmda_probe_info.type) { From 3cbee610524e0c119adbf1537f49a4937af38931 Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Fri, 11 Aug 2023 18:32:17 +0100 Subject: [PATCH 10/18] hosted/wchlink: implement RVSWD scan --- src/platforms/hosted/meson.build | 1 + src/platforms/hosted/platform.c | 5 ++ src/platforms/hosted/wchlink.c | 73 +++++++++++++++++++++++++ src/platforms/hosted/wchlink.h | 1 + src/platforms/hosted/wchlink_protocol.h | 2 + src/platforms/hosted/wchlink_rvswd.c | 53 ++++++++++++++++++ 6 files changed, 135 insertions(+) create mode 100644 src/platforms/hosted/wchlink_rvswd.c diff --git a/src/platforms/hosted/meson.build b/src/platforms/hosted/meson.build index 7acc54eb61b..13c0d511113 100644 --- a/src/platforms/hosted/meson.build +++ b/src/platforms/hosted/meson.build @@ -55,6 +55,7 @@ bmda_sources = files( '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 09f1419b40f..a6f90ee4251 100644 --- a/src/platforms/hosted/platform.c +++ b/src/platforms/hosted/platform.c @@ -320,6 +320,11 @@ 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; } diff --git a/src/platforms/hosted/wchlink.c b/src/platforms/hosted/wchlink.c index f342ec62c66..78c58c9126d 100644 --- a/src/platforms/hosted/wchlink.c +++ b/src/platforms/hosted/wchlink.c @@ -43,6 +43,8 @@ typedef struct wchlink { } 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; @@ -261,6 +263,32 @@ static char *wchlink_hw_type_to_string(const uint8_t hardware_id) } } +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]; @@ -282,6 +310,51 @@ static bool wchlink_get_version(void) 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)); diff --git a/src/platforms/hosted/wchlink.h b/src/platforms/hosted/wchlink.h index 720d7f74760..8e6a283dca2 100644 --- a/src/platforms/hosted/wchlink.h +++ b/src/platforms/hosted/wchlink.h @@ -36,6 +36,7 @@ #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 index 7c106dcc166..4de7b70695d 100644 --- a/src/platforms/hosted/wchlink_protocol.h +++ b/src/platforms/hosted/wchlink_protocol.h @@ -319,4 +319,6 @@ bool wchlink_command_send_recv(uint8_t command, uint8_t subcommand, const void * 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_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; +} From 75bd3adf010e204af9d18151b027f53918c74a95 Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Thu, 23 Feb 2023 15:30:35 +0000 Subject: [PATCH 11/18] riscv32: add ch32v3 target probe --- meson_options.txt | 1 + src/target/ch32vx.c | 80 +++++++++++++++++++++++++++++++++++++++ src/target/meson.build | 9 +++++ src/target/riscv32.c | 3 ++ src/target/target_probe.c | 1 + src/target/target_probe.h | 1 + 6 files changed, 95 insertions(+) create mode 100644 src/target/ch32vx.c diff --git a/meson_options.txt b/meson_options.txt index 8608c8361b0..78b8a55cb1e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -33,6 +33,7 @@ option( 'apollo3', 'at32f4', 'ch32', + 'ch32v', 'ch579', 'efm', 'gd32', diff --git a/src/target/ch32vx.c b/src/target/ch32vx.c new file mode 100644 index 00000000000..d82c2f82214 --- /dev/null +++ b/src/target/ch32vx.c @@ -0,0 +1,80 @@ +/* + * 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" + +/* 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 + +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%08" PRIx32 "\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; + } + + target->part_id = idcode; + + return true; +} diff --git a/src/target/meson.build b/src/target/meson.build index f6bf1df3c43..da762dcbc6e 100644 --- a/src/target/meson.build +++ b/src/target/meson.build @@ -78,6 +78,7 @@ if is_firmware_build 'at32f4': 'Arterytek parts', 'apollo3': 'Ambiq Apollo3 parts', 'ch32': 'WinChipHead CH32 parts', + 'ch32v': 'WinChipHead CH32 RISC-V parts', 'ch579': 'WinChipHead CH579 parts', 'efm': 'Energy Micro parts', 'gd32': 'GigaDevice parts', @@ -316,6 +317,13 @@ target_ch32 = declare_dependency( dependencies: target_stm32f1, ) +target_ch32v = declare_dependency( + sources: files( + 'ch32vx.c', + ), + dependencies: target_riscv32, +) + target_stm = declare_dependency( sources: files( 'stm32g0.c', @@ -409,6 +417,7 @@ libbmd_target_deps = [ target_apollo3, target_at32f4, target_ch32, + target_ch32v, target_ch579, target_efm, target_gd32, diff --git a/src/target/riscv32.c b/src/target/riscv32.c index 0d98a1b1596..d317fb1668e 100644 --- a/src/target/riscv32.c +++ b/src/target/riscv32.c @@ -97,6 +97,9 @@ bool riscv32_probe(target_s *const target) break; default: break; + case JEP106_MANUFACTURER_WCH: + PROBE(ch32vx_probe); + break; } #if CONFIG_BMDA == 0 diff --git a/src/target/target_probe.c b/src/target/target_probe.c index b2ec38ebe97..26ded94f17c 100644 --- a/src/target/target_probe.c +++ b/src/target/target_probe.c @@ -106,6 +106,7 @@ 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(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 ceebefce477..d0b2d08982a 100644 --- a/src/target/target_probe.h +++ b/src/target/target_probe.h @@ -60,6 +60,7 @@ 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 ch32vx_probe(target_s *target); bool gd32f1_probe(target_s *target); bool gd32f4_probe(target_s *target); bool gd32vf1_probe(target_s *target); From 07fcbc6bf92de84b36c84bf46cba03c82a25efdc Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Fri, 11 Aug 2023 22:38:00 +0100 Subject: [PATCH 12/18] ch32vx: implement electronic signature (ESIG) register handling --- src/target/ch32vx.c | 48 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/target/ch32vx.c b/src/target/ch32vx.c index d82c2f82214..2a91febfea7 100644 --- a/src/target/ch32vx.c +++ b/src/target/ch32vx.c @@ -23,6 +23,7 @@ #include "general.h" #include "target.h" #include "target_internal.h" +#include "buffer_utils.h" /* IDCODE register */ #define CH32VX_IDCODE 0x1ffff704U @@ -36,6 +37,30 @@ #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 ch32vx_probe(target_s *const target) { const uint32_t idcode = target_mem32_read32(target, CH32VX_IDCODE); @@ -74,7 +99,30 @@ bool ch32vx_probe(target_s *const target) 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; } From 29aefd29273c33448ad0cfebbdef563ffdbd02da Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Thu, 5 Oct 2023 16:28:01 +0100 Subject: [PATCH 13/18] riscv32: add ch32v003 target probe --- src/target/ch32vx.c | 36 ++++++++++++++++++++++++++++++++++++ src/target/riscv32.c | 1 + src/target/target_probe.c | 1 + src/target/target_probe.h | 1 + 4 files changed, 39 insertions(+) diff --git a/src/target/ch32vx.c b/src/target/ch32vx.c index 2a91febfea7..e220797e8f1 100644 --- a/src/target/ch32vx.c +++ b/src/target/ch32vx.c @@ -25,6 +25,13 @@ #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 @@ -61,6 +68,35 @@ static void ch32vx_read_uid(target_s *const t, uint8_t *const uid) 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%08" PRIx32 "\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); diff --git a/src/target/riscv32.c b/src/target/riscv32.c index d317fb1668e..aee1d676974 100644 --- a/src/target/riscv32.c +++ b/src/target/riscv32.c @@ -98,6 +98,7 @@ bool riscv32_probe(target_s *const target) default: break; case JEP106_MANUFACTURER_WCH: + PROBE(ch32v003x_probe); PROBE(ch32vx_probe); break; } diff --git a/src/target/target_probe.c b/src/target/target_probe.c index 26ded94f17c..b767ae9576c 100644 --- a/src/target/target_probe.c +++ b/src/target/target_probe.c @@ -106,6 +106,7 @@ 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) diff --git a/src/target/target_probe.h b/src/target/target_probe.h index d0b2d08982a..9c906cb3ef0 100644 --- a/src/target/target_probe.h +++ b/src/target/target_probe.h @@ -60,6 +60,7 @@ 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); From 83dd2736dcdeccfc8acdf0603496cffae396dc21 Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Wed, 17 Jan 2024 14:51:05 +0000 Subject: [PATCH 14/18] rvswd: gate rvswd functionality behind platform define and project option --- meson_options.txt | 5 +++++ src/command.c | 9 ++++++++- src/include/target.h | 4 ++++ src/meson.build | 8 ++++++++ src/platforms/hosted/platform.h | 1 + 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/meson_options.txt b/meson_options.txt index 78b8a55cb1e..9129524f380 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -149,3 +149,8 @@ option( value: false, description: 'Enable firmware-side protocol acceleration of RISC-V Debug' ) +option( + 'rvswd_support', + type: 'boolean', + value: false +) diff --git a/src/command.c b/src/command.c index 902aac9e304..cfad2bbb5f9 100644 --- a/src/command.c +++ b/src/command.c @@ -56,7 +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); +#if defined(CONFIG_RVSWD) && defined(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); @@ -98,7 +100,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"}, +#if defined(CONFIG_RVSWD) && defined(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"}, @@ -324,6 +328,7 @@ bool cmd_swd_scan(target_s *target, int argc, const char **argv) return true; } +#if defined(CONFIG_RVSWD) && defined(PLATFORM_HAS_RVSWD) bool cmd_rvswd_scan(target_s *target, int argc, const char **argv) { (void)target; @@ -367,6 +372,7 @@ bool cmd_rvswd_scan(target_s *target, int argc, const char **argv) morse(NULL, false); return true; } +#endif bool cmd_auto_scan(target_s *target, int argc, const char **argv) { @@ -396,7 +402,7 @@ bool cmd_auto_scan(target_s *target, int argc, const char **argv) #endif if (!scan_result) { gdb_out("SWD scan found no devices.\n"); - +#if defined(CONFIG_RVSWD) && defined(PLATFORM_HAS_RVSWD) #if CONFIG_BMDA == 1 scan_result = bmda_rvswd_scan(); #else @@ -404,6 +410,7 @@ bool cmd_auto_scan(target_s *target, int argc, const char **argv) #endif if (!scan_result) gdb_out("RVSWD scan found no devices.\n"); +#endif } } } diff --git a/src/include/target.h b/src/include/target.h index 21619d57f42..1a48282985f 100644 --- a/src/include/target.h +++ b/src/include/target.h @@ -30,6 +30,8 @@ #include #include +#include "platform.h" + #if defined(_MSC_VER) #include typedef SSIZE_T ssize_t; @@ -45,8 +47,10 @@ typedef struct target_controller target_controller_s; #if CONFIG_BMDA == 1 bool bmda_swd_scan(uint32_t targetid); bool bmda_jtag_scan(void); +#if defined(CONFIG_RVSWD) && defined(PLATFORM_HAS_RVSWD) bool bmda_rvswd_scan(void); #endif +#endif bool adiv5_swd_scan(uint32_t targetid); bool jtag_scan(void); diff --git a/src/meson.build b/src/meson.build index 6ebf83ea501..bb71a4b7410 100644 --- a/src/meson.build +++ b/src/meson.build @@ -79,6 +79,13 @@ if rtt_support endif endif +# RVSWD support handling +rvswd_support = get_option('rvswd_support') +if rvswd_support + libbmd_core_args += ['-DCONFIG_RVSWD=1'] + bmd_core_args += ['-DCONFIG_RVSWD=1'] +endif + # Advertise QStartNoAckMode advertise_noackmode = get_option('advertise_noackmode') if advertise_noackmode @@ -117,6 +124,7 @@ summary( { 'Debug output': debug_output, 'RTT support': rtt_support, + 'RVSWD support': rvswd_support, 'Advertise QStartNoAckMode': advertise_noackmode, }, bool_yn: true, diff --git a/src/platforms/hosted/platform.h b/src/platforms/hosted/platform.h index 78a8544a78b..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 From 3ded95ad4e6d40977f0ca3d08f300ed6d04fb595 Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Tue, 4 Feb 2025 16:40:00 +0000 Subject: [PATCH 15/18] misc: update copyright dates --- src/platforms/hosted/wchlink.c | 2 +- src/platforms/hosted/wchlink.h | 2 +- src/platforms/hosted/wchlink_protocol.h | 2 +- src/platforms/hosted/wchlink_riscv_dtm.c | 2 +- src/platforms/hosted/wchlink_rvswd.c | 2 +- src/target/ch32vx.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platforms/hosted/wchlink.c b/src/platforms/hosted/wchlink.c index 78c58c9126d..06f408dadba 100644 --- a/src/platforms/hosted/wchlink.c +++ b/src/platforms/hosted/wchlink.c @@ -1,7 +1,7 @@ /* * This file is part of the Black Magic Debug project. * - * Copyright (C) 2023 1BitSquared + * Copyright (C) 2023-2025 1BitSquared * Written by Rafael Silva * * Redistribution and use in source and binary forms, with or without diff --git a/src/platforms/hosted/wchlink.h b/src/platforms/hosted/wchlink.h index 8e6a283dca2..89e5dcd38b2 100644 --- a/src/platforms/hosted/wchlink.h +++ b/src/platforms/hosted/wchlink.h @@ -1,7 +1,7 @@ /* * This file is part of the Black Magic Debug project. * - * Copyright (C) 2023 1BitSquared + * Copyright (C) 2023-2025 1BitSquared * Written by Rafael Silva * * Redistribution and use in source and binary forms, with or without diff --git a/src/platforms/hosted/wchlink_protocol.h b/src/platforms/hosted/wchlink_protocol.h index 4de7b70695d..6453669db2d 100644 --- a/src/platforms/hosted/wchlink_protocol.h +++ b/src/platforms/hosted/wchlink_protocol.h @@ -1,7 +1,7 @@ /* * This file is part of the Black Magic Debug project. * - * Copyright (C) 2023 1BitSquared + * Copyright (C) 2023-2025 1BitSquared * Written by Rafael Silva * * This program is free software: you can redistribute it and/or modify diff --git a/src/platforms/hosted/wchlink_riscv_dtm.c b/src/platforms/hosted/wchlink_riscv_dtm.c index 0531655b7b7..2945bb04f79 100644 --- a/src/platforms/hosted/wchlink_riscv_dtm.c +++ b/src/platforms/hosted/wchlink_riscv_dtm.c @@ -1,7 +1,7 @@ /* * This file is part of the Black Magic Debug project. * - * Copyright (C) 2023 1BitSquared + * Copyright (C) 2023-2025 1BitSquared * Written by Rafael Silva * * Redistribution and use in source and binary forms, with or without diff --git a/src/platforms/hosted/wchlink_rvswd.c b/src/platforms/hosted/wchlink_rvswd.c index 249aa7f49f9..5858387611f 100644 --- a/src/platforms/hosted/wchlink_rvswd.c +++ b/src/platforms/hosted/wchlink_rvswd.c @@ -1,7 +1,7 @@ /* * This file is part of the Black Magic Debug project. * - * Copyright (C) 2023 1BitSquared + * Copyright (C) 2023-2025 1BitSquared * Written by Rafael Silva * * Redistribution and use in source and binary forms, with or without diff --git a/src/target/ch32vx.c b/src/target/ch32vx.c index e220797e8f1..d4e994804da 100644 --- a/src/target/ch32vx.c +++ b/src/target/ch32vx.c @@ -1,7 +1,7 @@ /* * This file is part of the Black Magic Debug project. * - * Copyright (C) 2022 1BitSquared + * Copyright (C) 2022-2025 1BitSquared * Written by Rafael Silva * * This program is free software: you can redistribute it and/or modify From e99b787b7506f2c190e214c6573792ebfa004107 Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Thu, 6 Feb 2025 13:00:47 +0000 Subject: [PATCH 16/18] target: split swd scan function into with and without a targetid This gives the swd scan function an equivalent signature to the other scan commands allowing us to group them for the auto scan command --- src/command.c | 8 ++++---- src/include/target.h | 6 ++++-- src/platforms/hosted/cli.c | 4 ++-- src/platforms/hosted/platform.c | 9 +++++++-- src/target/adiv5_swd.c | 7 ++++++- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/command.c b/src/command.c index cfad2bbb5f9..f89c9fd8c69 100644 --- a/src/command.c +++ b/src/command.c @@ -299,9 +299,9 @@ bool cmd_swd_scan(target_s *target, int argc, const char **argv) bool scan_result = false; TRY (EXCEPTION_ALL) { #if CONFIG_BMDA == 1 - scan_result = bmda_swd_scan(targetid); + scan_result = bmda_swd_scan_targetid(targetid); #else - scan_result = adiv5_swd_scan(targetid); + scan_result = adiv5_swd_scan_targetid(targetid); #endif } CATCH () { @@ -396,9 +396,9 @@ bool cmd_auto_scan(target_s *target, int argc, const char **argv) gdb_out("JTAG scan found no devices, trying SWD!\n"); #if CONFIG_BMDA == 1 - scan_result = bmda_swd_scan(0); + scan_result = bmda_swd_scan(); #else - scan_result = adiv5_swd_scan(0); + scan_result = adiv5_swd_scan(); #endif if (!scan_result) { gdb_out("SWD scan found no devices.\n"); diff --git a/src/include/target.h b/src/include/target.h index 1a48282985f..97ddefddf57 100644 --- a/src/include/target.h +++ b/src/include/target.h @@ -45,13 +45,15 @@ typedef target_addr32_t target_addr_t; typedef struct target_controller target_controller_s; #if CONFIG_BMDA == 1 -bool bmda_swd_scan(uint32_t targetid); +bool bmda_swd_scan(void); +bool bmda_swd_scan_targetid(uint32_t targetid); bool bmda_jtag_scan(void); #if defined(CONFIG_RVSWD) && defined(PLATFORM_HAS_RVSWD) bool bmda_rvswd_scan(void); #endif #endif -bool adiv5_swd_scan(uint32_t targetid); +bool adiv5_swd_scan(void); +bool adiv5_swd_scan_targetid(uint32_t targetid); bool jtag_scan(void); size_t target_foreach(void (*callback)(size_t index, target_s *target, void *context), void *context); diff --git a/src/platforms/hosted/cli.c b/src/platforms/hosted/cli.c index 0467e7bb79a..867e09b95a2 100644 --- a/src/platforms/hosted/cli.c +++ b/src/platforms/hosted/cli.c @@ -461,11 +461,11 @@ bool scan_for_targets(const bmda_cli_options_s *const opt) if (opt->opt_scanmode == BMP_SCAN_JTAG) return bmda_jtag_scan(); if (opt->opt_scanmode == BMP_SCAN_SWD) - return bmda_swd_scan(opt->opt_targetid); + return bmda_swd_scan_targetid(opt->opt_targetid); if (bmda_jtag_scan()) return true; DEBUG_WARN("JTAG scan found no devices, trying SWD.\n"); - if (bmda_swd_scan(opt->opt_targetid)) + if (bmda_swd_scan_targetid(opt->opt_targetid)) return true; DEBUG_ERROR("SWD scan failed!\n"); return false; diff --git a/src/platforms/hosted/platform.c b/src/platforms/hosted/platform.c index a6f90ee4251..09374bd032b 100644 --- a/src/platforms/hosted/platform.c +++ b/src/platforms/hosted/platform.c @@ -196,7 +196,12 @@ void platform_init(int argc, char **argv) } } -bool bmda_swd_scan(const uint32_t targetid) +bool bmda_swd_scan(void) +{ + return bmda_swd_scan_targetid(0); +} + +bool bmda_swd_scan_targetid(const uint32_t targetid) { bmda_probe_info.is_jtag = false; platform_max_frequency_set(max_frequency); @@ -209,7 +214,7 @@ bool bmda_swd_scan(const uint32_t targetid) #ifdef ENABLE_GPIOD case PROBE_TYPE_GPIOD: #endif - return adiv5_swd_scan(targetid); + return adiv5_swd_scan_targetid(targetid); #if HOSTED_BMP_ONLY == 0 case PROBE_TYPE_STLINK_V2: diff --git a/src/target/adiv5_swd.c b/src/target/adiv5_swd.c index 5034b3df9c6..011f248a5c9 100644 --- a/src/target/adiv5_swd.c +++ b/src/target/adiv5_swd.c @@ -155,7 +155,12 @@ uint32_t adiv5_swd_read_no_check(const uint16_t addr) return res == SWD_ACK_OK ? data : 0; } -bool adiv5_swd_scan(const uint32_t targetid) +bool adiv5_swd_scan() +{ + return adiv5_swd_scan_targetid(0); +} + +bool adiv5_swd_scan_targetid(const uint32_t targetid) { /* Free the device list if any */ target_list_free(); From bb2e0b8c194d0fbcc31ead83fc00dd67670f710a Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Thu, 6 Feb 2025 13:22:01 +0000 Subject: [PATCH 17/18] command: refactor auto scan command with a for loop --- src/command.c | 71 +++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/src/command.c b/src/command.c index f89c9fd8c69..c0d8bf7e023 100644 --- a/src/command.c +++ b/src/command.c @@ -51,6 +51,11 @@ #include "usb.h" #endif +typedef struct scan_command { + bool (*scan)(void); + const char *name; +} scan_command_s; + static bool cmd_version(target_s *target, int argc, const char **argv); static bool cmd_help(target_s *target, int argc, const char **argv); @@ -385,44 +390,44 @@ bool cmd_auto_scan(target_s *target, int argc, const char **argv) 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_jtag_scan(); -#else - scan_result = jtag_scan(); -#endif - if (!scan_result) { - gdb_out("JTAG scan found no devices, trying SWD!\n"); - + static const scan_command_s scan_commands[] = #if CONFIG_BMDA == 1 - scan_result = bmda_swd_scan(); -#else - scan_result = adiv5_swd_scan(); -#endif - if (!scan_result) { - gdb_out("SWD scan found no devices.\n"); + /* clang-format off */ + { + {bmda_jtag_scan, "JTAG"}, + {bmda_swd_scan, "SWD"}, #if defined(CONFIG_RVSWD) && defined(PLATFORM_HAS_RVSWD) -#if CONFIG_BMDA == 1 - scan_result = bmda_rvswd_scan(); -#else - scan_result = false; + {bmda_rvswd_scan, "RVSWD"}, #endif - if (!scan_result) - gdb_out("RVSWD scan found no devices.\n"); + }; +#else + { + {jtag_scan, "JTAG"}, + {adiv5_swd_scan, "SWD"}, + }; + /* clang-format on */ #endif - } + + bool scan_result = false; + for (size_t i = 0; i < ARRAY_LENGTH(scan_commands); i++) { + TRY (EXCEPTION_ALL) { + scan_result = scan_commands[i].scan(); } - } - 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; + CATCH () { + case EXCEPTION_TIMEOUT: + gdb_outf("Timeout during %s scan. Is target stuck in WFI?\n", scan_commands[i].name); + break; + case EXCEPTION_ERROR: + gdb_outf("%s exception: %s\n", scan_commands[i].name, exception_frame.msg); + break; + default: + break; + } + + if (scan_result) + break; + + gdb_outf("%s scan found no devices.\n", scan_commands[i].name); } if (!scan_result) { From 778d40363d0df4c192f1c9a02b5e2d24821596ae Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Fri, 22 Aug 2025 17:46:25 +0100 Subject: [PATCH 18/18] ch32: be more explicit in target description Makes the distintion between "ch32" and "ch32v" target support clearer --- src/target/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/target/meson.build b/src/target/meson.build index da762dcbc6e..f04bd7f1226 100644 --- a/src/target/meson.build +++ b/src/target/meson.build @@ -77,7 +77,7 @@ if is_firmware_build 'riscv64': 'RISC-V 64-bit support', 'at32f4': 'Arterytek parts', 'apollo3': 'Ambiq Apollo3 parts', - 'ch32': 'WinChipHead CH32 parts', + 'ch32': 'WinChipHead CH32 ARM Cortex parts', 'ch32v': 'WinChipHead CH32 RISC-V parts', 'ch579': 'WinChipHead CH579 parts', 'efm': 'Energy Micro parts',