diff --git a/src/Makefile b/src/Makefile index ca97790fa7b..8666e89f2b0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -23,65 +23,73 @@ ifeq ($(ENABLE_DEBUG), 1) CFLAGS += -DENABLE_DEBUG endif -SRC = \ - adiv5.c \ - adiv5_jtag.c \ - adiv5_swd.c \ - command.c \ - cortex.c \ - cortexa.c \ - cortexm.c \ - crc32.c \ - efm32.c \ - exception.c \ - gdb_if.c \ - gdb_main.c \ - gdb_hostio.c \ - gdb_packet.c \ - gdb_reg.c \ - hex_utils.c \ - hc32l110.c \ - imxrt.c \ - jtag_devs.c \ - jtag_scan.c \ - lmi.c \ - lpc_common.c \ - lpc11xx.c \ - lpc17xx.c \ - lpc15xx.c \ - lpc40xx.c \ - lpc43xx.c \ - lpc546xx.c \ - lpc55xx.c \ - kinetis.c \ - main.c \ - maths_utils.c \ - morse.c \ - msp432e4.c \ - msp432p4.c \ - nrf51.c \ - nrf91.c \ - nxpke04.c \ - remote.c \ - rp.c \ - sam3x.c \ - sam4l.c \ - samd.c \ - samx5x.c \ - sfdp.c \ - spi.c \ - stm32f1.c \ - ch32f1.c \ - stm32f4.c \ - stm32h5.c \ - stm32h7.c \ - stm32mp15.c \ - stm32l0.c \ - stm32l4.c \ - stm32g0.c \ - renesas.c \ - target.c \ - target_flash.c \ +SRC = \ + adiv5.c \ + adiv5_jtag.c \ + adiv5_swd.c \ + command.c \ + cortex.c \ + cortexa.c \ + cortexm.c \ + crc32.c \ + efm32.c \ + esp32c3.c \ + exception.c \ + gdb_if.c \ + gdb_main.c \ + gdb_hostio.c \ + gdb_packet.c \ + gdb_reg.c \ + hex_utils.c \ + hc32l110.c \ + imxrt.c \ + jtag_devs.c \ + jtag_scan.c \ + lmi.c \ + lpc_common.c \ + lpc11xx.c \ + lpc17xx.c \ + lpc15xx.c \ + lpc40xx.c \ + lpc43xx.c \ + lpc546xx.c \ + lpc55xx.c \ + kinetis.c \ + main.c \ + maths_utils.c \ + morse.c \ + msp432e4.c \ + msp432p4.c \ + nrf51.c \ + nrf91.c \ + nxpke04.c \ + remote.c \ + riscv32.c \ + riscv64.c \ + riscv_debug.c \ + riscv_jtag_dtm.c \ + rp.c \ + sam3x.c \ + sam4l.c \ + samd.c \ + samx5x.c \ + sfdp.c \ + spi.c \ + stm32f1.c \ + ch32_flash.c \ + stm32_flash.c \ + ch32f1x.c \ + ch32v3x.c \ + stm32f4.c \ + stm32h5.c \ + stm32h7.c \ + stm32mp15.c \ + stm32l0.c \ + stm32l4.c \ + stm32g0.c \ + renesas.c \ + target.c \ + target_flash.c \ target_probe.c ifeq (,$(filter all_platforms,$(MAKECMDGOALS))) diff --git a/src/command.c b/src/command.c index 7ef7a39cb56..a5242df6601 100644 --- a/src/command.c +++ b/src/command.c @@ -57,6 +57,7 @@ static bool cmd_help(target_s *t, 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 *t, 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 *t, int argc, const char **argv); static bool cmd_frequency(target_s *t, int argc, const char **argv); static bool cmd_targets(target_s *t, int argc, const char **argv); @@ -88,6 +89,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"}, @@ -288,6 +290,49 @@ bool cmd_swd_scan(target_s *t, 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; + volatile exception_s e; + TRY_CATCH (e, EXCEPTION_ALL) { +#if PC_HOSTED == 1 + scan_result = bmda_rvswd_scan(); +#else + scan_result = false; +#endif + } + switch (e.type) { + case EXCEPTION_TIMEOUT: + gdb_outf("Timeout during scan. Is target stuck in WFI?\n"); + break; + case EXCEPTION_ERROR: + gdb_outf("Exception: %s\n", e.msg); + 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 *t, int argc, const char **argv) { (void)t; diff --git a/src/gdb_main.c b/src/gdb_main.c index 8af934921b9..d8d39d5f1a7 100644 --- a/src/gdb_main.c +++ b/src/gdb_main.c @@ -722,10 +722,13 @@ static void handle_z_packet(char *packet, const size_t plen) else ret = target_breakwatch_clear(cur_target, type, addr, len); + /* If the target handler was unable to set/clear the break/watch-point, return an error */ if (ret < 0) gdb_putpacketz("E01"); + /* If the handler does not support the kind requested, return empty string */ else if (ret > 0) gdb_putpacketz(""); + /* Otherwise let GDB know that everything went well */ else gdb_putpacketz("OK"); } diff --git a/src/include/buffer_utils.h b/src/include/buffer_utils.h index 085d75af9ad..0bad5e4904e 100644 --- a/src/include/buffer_utils.h +++ b/src/include/buffer_utils.h @@ -51,6 +51,14 @@ static inline void write_le4(uint8_t *const buffer, const size_t offset, const u buffer[offset + 3U] = (value >> 24U) & 0xffU; } +static inline void write_be4(uint8_t *const buffer, const size_t offset, const uint32_t value) +{ + buffer[offset + 0U] = (value >> 24U) & 0xffU; + buffer[offset + 1U] = (value >> 16U) & 0xffU; + buffer[offset + 2U] = (value >> 8U) & 0xffU; + buffer[offset + 3U] = value & 0xffU; +} + static inline uint16_t read_le2(const uint8_t *const buffer, const size_t offset) { return buffer[offset + 0U] | ((uint16_t)buffer[offset + 1U] << 8U); diff --git a/src/include/target.h b/src/include/target.h index 33303c399c2..f4185f6f6eb 100644 --- a/src/include/target.h +++ b/src/include/target.h @@ -37,6 +37,7 @@ typedef struct target_controller target_controller_s; #if PC_HOSTED == 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); @@ -63,6 +64,7 @@ bool target_mem_access_needs_halt(target_s *target); bool target_flash_erase(target_s *target, target_addr_t addr, size_t len); bool target_flash_write(target_s *target, target_addr_t dest, const void *src, size_t len); bool target_flash_complete(target_s *target); +bool target_flash_mass_erase(target_s *target); /* Register access functions */ size_t target_regs_size(target_s *target); diff --git a/src/platforms/hosted/Makefile.inc b/src/platforms/hosted/Makefile.inc index b02cb721b18..2e671226eae 100644 --- a/src/platforms/hosted/Makefile.inc +++ b/src/platforms/hosted/Makefile.inc @@ -126,6 +126,7 @@ ifneq ($(HOSTED_BMP_ONLY), 1) SRC += bmp_libusb.c stlinkv2.c stlinkv2_jtag.c stlinkv2_swd.c SRC += ftdi_bmp.c ftdi_jtag.c ftdi_swd.c SRC += jlink.c jlink_jtag.c jlink_swd.c + SRC += wchlink.c wchlink_rvswd.c wchlink_riscv_dtm.c else SRC += bmp_serial.c endif diff --git a/src/platforms/hosted/bmp_libusb.c b/src/platforms/hosted/bmp_libusb.c index 6c04a1f3924..2c6d3ddabb7 100644 --- a/src/platforms/hosted/bmp_libusb.c +++ b/src/platforms/hosted/bmp_libusb.c @@ -74,6 +74,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 JLink"}, + {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"}, @@ -99,7 +100,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/platform.c b/src/platforms/hosted/platform.c index fb82206d485..9c6639992d7 100644 --- a/src/platforms/hosted/platform.c +++ b/src/platforms/hosted/platform.c @@ -50,6 +50,7 @@ #include "stlinkv2.h" #include "ftdi_bmp.h" #include "jlink.h" +#include "wchlink.h" #include "cmsis_dap.h" #endif @@ -148,6 +149,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 default: @@ -269,6 +275,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) { @@ -334,6 +355,9 @@ char *bmda_adaptor_ident(void) case PROBE_TYPE_JLINK: return "J-Link"; + case PROBE_TYPE_WCHLINK: + return "WCH-Link"; + default: return NULL; } diff --git a/src/platforms/hosted/platform.h b/src/platforms/hosted/platform.h index 968fad1ad94..2f4ff47e81d 100644 --- a/src/platforms/hosted/platform.h +++ b/src/platforms/hosted/platform.h @@ -77,13 +77,19 @@ 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, PROBE_TYPE_STLINK_V2, PROBE_TYPE_FTDI, PROBE_TYPE_CMSIS_DAP, - PROBE_TYPE_JLINK + PROBE_TYPE_JLINK, + PROBE_TYPE_WCHLINK, } probe_type_e; void gdb_ident(char *p, int count); diff --git a/src/platforms/hosted/wchlink.c b/src/platforms/hosted/wchlink.c new file mode 100644 index 00000000000..b8dea2b2692 --- /dev/null +++ b/src/platforms/hosted/wchlink.c @@ -0,0 +1,380 @@ +/* + * 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 formaly known, so we hack it's identification a bit for now */ + return command == WCH_CMD_CONTROL && subcommand == WCH_CONTROL_SUBCMD_ATTACH && error == WCH_ERR_ATTACH ? + "Failed to attach to target" : + "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 */ + + /* 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) +{ + /* 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 */ + + /* 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; + 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 result = libusb_claim_interface(bmda_probe_info.usb_link->device_handle, (int)idx); + if (result) { + DEBUG_ERROR("Can not claim handle: %s\n", libusb_error_name(result)); + break; + } + bmda_probe_info.usb_link->interface = idx; + descriptor = interface_desc; + } + } + 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..35dfb30d458 --- /dev/null +++ b/src/platforms/hosted/wchlink_riscv_dtm.c @@ -0,0 +1,90 @@ +/* + * 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_0_13; /* Assumption, unverified */ + + /* WCH-Link has a fixed address width of 8 bits, limited by the USB protocol (is RVSWD also fixed?) */ + dmi->address_width = 8U; + + 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/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/target/adiv5.h b/src/target/adiv5.h index ed9ddb1025f..891a4d728f0 100644 --- a/src/target/adiv5.h +++ b/src/target/adiv5.h @@ -207,6 +207,11 @@ #define JTAG_IDCODE_PARTNO_MASK (0xffffU << JTAG_IDCODE_PARTNO_OFFSET) #define JTAG_IDCODE_DESIGNER_OFFSET 1U #define JTAG_IDCODE_DESIGNER_MASK (0x7ffU << JTAG_IDCODE_DESIGNER_OFFSET) +/* Bits 10:7 - JEP-106 Continuation code */ +/* Bits 6:0 - JEP-106 Identity code */ +#define JTAG_IDCODE_DESIGNER_JEP106_CONT_OFFSET 7U +#define JTAG_IDCODE_DESIGNER_JEP106_CONT_MASK (0xfU << ADIV5_DP_DESIGNER_JEP106_CONT_OFFSET) +#define JTAG_IDCODE_DESIGNER_JEP106_CODE_MASK (0x7fU) #define JTAG_IDCODE_PARTNO_DPv0 0xba00U diff --git a/src/target/ch32_flash.c b/src/target/ch32_flash.c new file mode 100644 index 00000000000..0b178a06a39 --- /dev/null +++ b/src/target/ch32_flash.c @@ -0,0 +1,372 @@ +/* + * 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 "ch32_flash.h" + +/* + * FIXME: these defines are here to determine if these are actually required + * if not remove them, else remove the defines + */ +#define ENABLE_CH32_FLASH_DELAYS 1U +#define ENABLE_CH32F1X_FLASH_MAGIC 1U + +/* Generic CH32 flash routines */ + +bool ch32_flash_fast_mode_locked(target_s *const target, const uint32_t fpec_base) +{ + const uint32_t ctrl = target_mem_read32(target, STM32_FLASH_CR(fpec_base)); + return ctrl & CH32_FLASH_CR_FLOCK; +} + +bool ch32_flash_fast_mode_unlock(target_s *const target, const uint32_t fpec_base) +{ + target_mem_write32(target, CH32_FLASH_MODEKEYR(fpec_base), STM32_FLASH_KEY1); + target_mem_write32(target, CH32_FLASH_MODEKEYR(fpec_base), STM32_FLASH_KEY2); + +#if ENABLE_CH32_FLASH_DELAYS + platform_delay(1U); /* FIXME: The flash controller is timing sensitive? */ +#endif + + /* Check that the fast mode is unlocked */ + if (ch32_flash_fast_mode_locked(target, fpec_base)) { + DEBUG_ERROR("ch32 flash fast unlock failed\n"); + return false; + } + return true; +} + +void ch32_flash_lock(target_s *const target, const uint32_t fpec_base) +{ + const uint32_t ctrl = target_mem_read32(target, STM32_FLASH_CR(fpec_base)); + /* Retain the EOPIE, ERRIE and OPTWRE bits, and set the LOCK and FLOCK bits */ + target_mem_write32(target, STM32_FLASH_CR(fpec_base), + (ctrl & (STM32_FLASH_CR_EOPIE | STM32_FLASH_CR_ERRIE | STM32_FLASH_CR_OPTWRE)) | STM32_FLASH_CR_LOCK | + CH32_FLASH_CR_FLOCK); + +#if ENABLE_CH32_FLASH_DELAYS + platform_delay(1U); /* FIXME: The flash controller is timing sensitive? */ +#endif +} + +static bool ch32_flash_prepare(target_flash_s *const flash) +{ + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* Unlock the flash if required */ + if (stm32_flash_locked(target, stm32_flash->fpec_base, 0U) && + !stm32_flash_unlock(target, stm32_flash->fpec_base, 0U)) + return false; + + /* Ensure no operation is ongoing */ + if (target_mem_read32(target, STM32_FLASH_SR_BANK(stm32_flash->fpec_base, 0U)) & STM32_FLASH_SR_BSY) { + DEBUG_ERROR("ch32 flash unexpectedly busy\n"); + return false; /* FIXME: should we re-lock here? */ + } + + /* Clear any previous operation status */ + stm32_flash_clear_status(target, stm32_flash->fpec_base, 0U); + + /* Set the instruction to the control register */ + uint32_t ctrl_instruction = 0U; + switch (flash->operation) { + case FLASH_OPERATION_WRITE: + /* Set flash fast mode programming instruction */ + ctrl_instruction = CH32_FLASH_CR_FTPG; + break; + case FLASH_OPERATION_ERASE: + /* Set flash fast mode page erase instruction */ + ctrl_instruction = CH32_FLASH_CR_FTER; + break; + case FLASH_OPERATION_MASS_ERASE: + /* Set flash mass erase instruction */ + ctrl_instruction = STM32_FLASH_CR_MER; + break; + default: + return false; /* Unsupported operation */ + } + + /* Unlock the fast mode extension if required by the requested instruction */ + if (flash->operation != FLASH_OPERATION_MASS_ERASE) { + if (ch32_flash_fast_mode_locked(target, stm32_flash->fpec_base) && + !ch32_flash_fast_mode_unlock(target, stm32_flash->fpec_base)) + return false; + } + + /* + * This will clear EOPIE, ERRIE and OPTWRE, but we don't care about them and expect them cleared + * after reset anyway, on CH32FV2x/V3x this also clears the EHMOD and SCKMOD, which follow the same logic + * as the former. + * + * note that we don't read-modify-write the control register after this, meaning we set the instruction + * allways, this is to avoid the extra overhead of reading the register since we know what bits should be set. + */ + /* FIXME: on CH32FV2x/V3x we might want to check the default SYSCLK and if setting SCKMOD makes sense */ + target_mem_write32(target, STM32_FLASH_CR(stm32_flash->fpec_base), ctrl_instruction); + + return true; +} + +static bool ch32_flash_done(target_flash_s *const flash) +{ + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* Lock the flash */ + ch32_flash_lock(target, stm32_flash->fpec_base); + + return true; +} + +/* CH32F1x flash routines */ + +#if ENABLE_CH32F1X_FLASH_MAGIC +static inline void ch32f1x_flash_magic(target_s *const target, const uint32_t fpec_base, const uint32_t page_addr) +{ + /* We don't know what this does or if we actually need it, but it is done on the standard peripheral lib */ + const uint32_t magic = target_mem_read32(target, page_addr ^ CH32F1X_FLASH_MAGIC_XOR); + target_mem_write32(target, CH32F1X_FLASH_MAGIC(fpec_base), magic); +} +#endif + +static bool ch32f1x_flash_fast_mode_buffer_clear(target_s *const target, const uint32_t fpec_base) +{ + /* Clear any previous operation status */ + stm32_flash_clear_status(target, fpec_base, 0U); + + /* Clear the internal buffer */ + target_mem_write32(target, STM32_FLASH_CR(fpec_base), CH32_FLASH_CR_FTPG | CH32F1X_FLASH_CR_BUFRST); + const bool result = stm32_flash_busy_wait(target, fpec_base, 0U, NULL); + +#if ENABLE_CH32_FLASH_DELAYS + platform_delay(2U); /* FIXME: The flash controller is timing sensitive? */ +#endif + + return result; +} + +static bool ch32f1x_flash_fast_mode_buffer_load( + target_s *const target, const uint32_t fpec_base, const uint32_t dest, const void *const src) +{ + /* + * The fast mode buffer is 128 bytes long, it is loaded in 8x16 byte chunks. + * The 16 byte chunks should be written continuously in 32 bit words to the destination address. + * The 8 chunks should be written consecutively. + */ + for (size_t offset = 0; offset < 128U; offset += 16U) { + /* Clear any previous operation status */ + stm32_flash_clear_status(target, fpec_base, 0U); + + /* Continuously write 16 bytes of data to the specified address in 32 bit writes */ + target_mem_write(target, dest + offset, (const uint8_t *)src + offset, 16U); + + /* Start buffer load instruction */ + target_mem_write32(target, STM32_FLASH_CR(fpec_base), CH32_FLASH_CR_FTPG | CH32F1X_FLASH_CR_BUFRST); + if (!stm32_flash_busy_wait(target, fpec_base, 0U, NULL)) + return false; + +#if ENABLE_CH32F1X_FLASH_MAGIC + /* Unknown magic sequence */ + ch32f1x_flash_magic(target, fpec_base, dest + offset); +#endif + } + return true; +} + +static bool ch32f1x_flash_fast_mode_erase(target_flash_s *const flash, const target_addr_t addr, const size_t length) +{ + (void)length; + + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* See §24.4.7 Main Memory Fast Erasure in CH32xRM */ + + /* Clear any previous operation status */ + stm32_flash_clear_status(target, stm32_flash->fpec_base, 0U); + + /* Write page address to address register */ + target_mem_write32(target, STM32_FLASH_AR(stm32_flash->fpec_base), addr); + + /* Start fast flash page erase instruction */ + target_mem_write32(target, STM32_FLASH_CR(stm32_flash->fpec_base), STM32_FLASH_CR_STRT | CH32_FLASH_CR_FTER); + + /* Wait for completion or an error, return false on error */ + const bool result = stm32_flash_busy_wait(target, stm32_flash->fpec_base, 0U, NULL); + +#if ENABLE_CH32F1X_FLASH_MAGIC + /* Unknown magic sequence */ + ch32f1x_flash_magic(target, stm32_flash->fpec_base, addr); +#endif + + return result; +} + +/* CH32F1x use a "buffer" for fast programming */ +static bool ch32f1x_flash_fast_mode_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len) +{ + (void)len; + + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* See §24.4.6 Main Memory Fast Programming in CH32xRM */ + + /* Clear the internal buffer */ + ch32f1x_flash_fast_mode_buffer_clear(target, stm32_flash->fpec_base); + + /* Load the internal buffer with the 128 byte page */ + ch32f1x_flash_fast_mode_buffer_load(target, stm32_flash->fpec_base, dest, src); + + /* Clear any previous operation status */ + stm32_flash_clear_status(target, stm32_flash->fpec_base, 0U); + + /* Write page address to address register */ + target_mem_write32(target, STM32_FLASH_AR(stm32_flash->fpec_base), dest); + + /* Start fast mode flash programming instruction */ + target_mem_write32(target, STM32_FLASH_CR(stm32_flash->fpec_base), STM32_FLASH_CR_STRT | CH32_FLASH_CR_FTPG); + + /* Wait for completion or an error, return false on error */ + const bool result = stm32_flash_busy_wait(target, stm32_flash->fpec_base, 0U, NULL); + +#if ENABLE_CH32F1X_FLASH_MAGIC + /* Unknown magic sequence */ + ch32f1x_flash_magic(target, stm32_flash->fpec_base, dest); +#endif + + return result; +} + +void ch32f1x_add_flash(target_s *const target, const target_addr_t addr, const size_t length) +{ + stm32_flash_s *const stm32_flash = calloc(1U, sizeof(*stm32_flash)); + if (!stm32_flash) { /* calloc failed: heap exhaustion */ + DEBUG_ERROR("calloc: failed in %s\n", __func__); + return; + } + target_flash_s *const flash = &stm32_flash->flash; + + stm32_flash->fpec_base = CH32_FPEC_BASE; + + flash->start = addr; + flash->length = length; + flash->blocksize = CH32F1X_FAST_MODE_PAGE_SIZE; + flash->writesize = CH32F1X_FAST_MODE_PAGE_SIZE; + flash->erased = 0xffU; + flash->erase = ch32f1x_flash_fast_mode_erase; + flash->mass_erase = stm32_flash_mass_erase; + flash->write = ch32f1x_flash_fast_mode_write; + flash->prepare = ch32_flash_prepare; + flash->done = ch32_flash_done; + + target_add_flash(target, flash); +} + +/* CH32FV2x/V3x flash routines */ + +/* FIXME: this function is very close to ch32f1x_flash_fast_mode_erase, the only exception is the magic sequence, optimize */ +static bool ch32fv2x_v3x_flash_fast_mode_erase( + target_flash_s *const flash, const target_addr_t addr, const size_t length) +{ + (void)length; + + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* See §32.5.7 Main Memory Fast Erasure in CH32FV2x_V3xRM */ + + /* Clear any previous operation status */ + stm32_flash_clear_status(target, stm32_flash->fpec_base, 0U); + + /* Write page address to address register */ + target_mem_write32(target, STM32_FLASH_AR(stm32_flash->fpec_base), addr); + + /* Start fast flash page erase instruction */ + target_mem_write32(target, STM32_FLASH_CR(stm32_flash->fpec_base), STM32_FLASH_CR_STRT | CH32_FLASH_CR_FTER); + + /* Wait for completion or an error, return false on error */ + return stm32_flash_busy_wait(target, stm32_flash->fpec_base, 0U, NULL); +} + +/* CH32FV2x/V3x don't use a "buffer" for fast programming */ +static bool ch32fv2x_v3x_flash_fast_mode_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len) +{ + (void)len; + + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* See §32.5.6 Main Memory Fast Programming in CH32FV2x_V3xRM */ + + /* Clear any previous operation status */ + stm32_flash_clear_status(target, stm32_flash->fpec_base, 0U); + + /* Write data to the FLASH address in 32 bit writes, 64 times */ + for (size_t offset = 0; offset < 64U; offset++) { + target_mem_write32(target, dest + (offset << 2U), *((const uint32_t *)src + offset)); + if (!stm32_flash_busy_wait(target, stm32_flash->fpec_base, 0U, NULL)) + return false; + } + + /* Start fast mode flash programming instruction */ + target_mem_write32( + target, STM32_FLASH_CR(stm32_flash->fpec_base), CH32FV2X_V3X_FLASH_CR_PGSTRT | CH32_FLASH_CR_FTPG); + + /* Wait for completion or an error, return false on error */ + return stm32_flash_busy_wait(target, stm32_flash->fpec_base, 0U, NULL); +} + +void ch32fv2x_v3x_add_flash(target_s *const target, const target_addr_t addr, const size_t length) +{ + stm32_flash_s *const stm32_flash = calloc(1U, sizeof(*stm32_flash)); + if (!stm32_flash) { /* calloc failed: heap exhaustion */ + DEBUG_ERROR("calloc: failed in %s\n", __func__); + return; + } + target_flash_s *const flash = &stm32_flash->flash; + + stm32_flash->fpec_base = CH32_FPEC_BASE; + + flash->start = addr; + flash->length = length; + flash->blocksize = CH32FV2X_V3X_FAST_MODE_PAGE_SIZE; + flash->writesize = CH32FV2X_V3X_FAST_MODE_PAGE_SIZE; + flash->erased = 0xffU; + flash->erase = ch32fv2x_v3x_flash_fast_mode_erase; + flash->mass_erase = stm32_flash_mass_erase; + flash->write = ch32fv2x_v3x_flash_fast_mode_write; + flash->prepare = ch32_flash_prepare; + flash->done = ch32_flash_done; + + target_add_flash(target, flash); +} diff --git a/src/target/ch32_flash.h b/src/target/ch32_flash.h new file mode 100644 index 00000000000..84ea7a9f4f2 --- /dev/null +++ b/src/target/ch32_flash.h @@ -0,0 +1,100 @@ +/* + * 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 TARGET_CH32_FLASH_H +#define TARGET_CH32_FLASH_H + +#include "general.h" +#include "target.h" +#include "target_internal.h" +#include "stm32_flash.h" + +/* + * STM32 Flash program and erase controller (FPEC) extension for CH32 + * contains the CH32 specific extension registers and bits. + * Macros named CH32FV2X_V3X are shared between CH32F2x, CH32V2x and CH32V3x + * + * This is based on CH32xRM Chapter §24 Flash Memory and CH32FV2x_V3xRM Chapter §32 lash Memory and User Option Bytes + * https://www.wch-ic.com/downloads/file/306.html + * https://www.wch-ic.com/downloads/file/324.html + */ +#define CH32_FPEC_BASE 0x40022000U /* Shared between CH32F1x, CH32F2x, CH32V2x and CH32V3x, may not apply to others */ + +#define CH32F1X_FAST_MODE_PAGE_SIZE 128U /* CH32F1x Fast erase/programming works on 128 byte pages */ +#define CH32FV2X_V3X_FAST_MODE_PAGE_SIZE 256U /* CH32FV2x/V3x Fast erase/programming works on 256 byte pages */ + +/* Flash control register (FLASH_CR) */ +#define CH32_FLASH_CR_FTER (1U << 17U) /* Fast page (128Byte) erase operation */ +#define CH32_FLASH_CR_FTPG (1U << 16U) /* Fast programming operation */ +#define CH32_FLASH_CR_FLOCK (1U << 15U) /* Fast programming lock (locked: fast programming/erase mode not available) */ +/* CH32F1x Only */ +#define CH32F1X_FLASH_CR_BUFRST (1U << 19U) /* Clear the internal buffer data */ +#define CH32F1X_FLASH_CR_BUFLOAD (1U << 18U) /* Load data into the internal buffer */ +/* CH32FV2x/V3x Only */ +#define CH32FV2X_V3X_FLASH_CR_SCKMOD (1U << 25U) /* Flash access clock (1: SYSCLK, 0: ½SYSCLK) (must be < 60 MHz) */ +#define CH32FV2X_V3X_FLASH_CR_EHMOD (1U << 24U) /* Flash enhanced read mode */ +#define CH32FV2X_V3X_FLASH_CR_RSENACT (1U << 22U) /* Exit the enhanced read mode, first clear the ENHANCE_MOD */ +#define CH32FV2X_V3X_FLASH_CR_PGSTRT (1U << 21U) /* Start a page programming */ +#define CH32FV2X_V3X_FLASH_CR_BER64 (1U << 19U) /* Perform a 64KB erase */ +#define CH32FV2X_V3X_FLASH_CR_BER32 (1U << 18U) /* Perform a 32KB erase */ + +/* Flash status register (FLASH_SR), CH32FV2x/V3x Only */ +#define CH32FV2X_V3X_FLASH_SR_EHMODS (1U << 7U) /* Flash enhanced read mode (1: enabled, 0: disabled) */ +#define CH32FV2X_V3X_FLASH_SR_WRBSY (1U << 0U) /* Fast page programming busy */ + +/* + * Extension key register (FLASH_MODEKEYR) + * Shares the keys with the ST FPEC key register (FLASH_KEYR) + */ +#define CH32_FLASH_MODEKEYR_OFFSET 0x24U +#define CH32_FLASH_MODEKEYR(fpec_base) ((fpec_base) + CH32_FLASH_MODEKEYR_OFFSET) + +/* + * FIXME: What is this?, CH32F1x Only + * It's unclear what the purpose of this register is, it's not referenced in CH32xRM documentation + * but it is used on the standard peripherals library for CH32F103x + */ +#define CH32F1X_FLASH_MAGIC_OFFSET 0x34U +#define CH32F1X_FLASH_MAGIC(fpec_base) ((fpec_base) + CH32F1X_FLASH_MAGIC_OFFSET) + +#define CH32F1X_FLASH_MAGIC_XOR 0x00000100U + +/* Insternal API */ +bool ch32_flash_fast_mode_locked(target_s *target, uint32_t fpec_base); +bool ch32_flash_fast_mode_unlock(target_s *target, uint32_t fpec_base); +void ch32_flash_lock(target_s *target, uint32_t fpec_base); + +/* Generic flash routines */ +void ch32f1x_add_flash(target_s *target, target_addr_t addr, size_t length); +void ch32fv2x_v3x_add_flash(target_s *target, target_addr_t addr, size_t length); + +#endif /* TARGET_CH32_FLASH_H */ diff --git a/src/target/ch32f1.c b/src/target/ch32f1.c deleted file mode 100644 index 98398d1bbd5..00000000000 --- a/src/target/ch32f1.c +++ /dev/null @@ -1,387 +0,0 @@ -/* - * This file is part of the Black Magic Debug project. - * - * Copyright (C) 2022 1BitSquared - * Written by mean00 - * - * 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 CH32F1xx target specific functions. - * The ch32 flash is rather slow so this code is using the so called fast mode (ch32 specific). - * 128 bytes are copied to a write buffer, then the write buffer is committed to flash - * /!\ There is some sort of bus stall/bus arbitration going on that does NOT work when - * programmed through SWD/jtag - * The workaround is to wait a few cycles before filling the write buffer. This is performed by reading the flash a few times - */ - -#include "general.h" -#include "target.h" -#include "target_internal.h" -#include "cortex.h" - -extern const command_s stm32f1_cmd_list[]; // Reuse stm32f1 stuff - -static bool ch32f1_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); -static bool ch32f1_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len); - -// These are common with stm32f1/gd32f1/... -#define FPEC_BASE 0x40022000U -#define FLASH_ACR (FPEC_BASE + 0x00U) -#define FLASH_KEYR (FPEC_BASE + 0x04U) -#define FLASH_SR (FPEC_BASE + 0x0cU) -#define FLASH_CR (FPEC_BASE + 0x10U) -#define FLASH_AR (FPEC_BASE + 0x14U) -#define FLASH_CR_LOCK (1U << 7U) -#define FLASH_CR_STRT (1U << 6U) -#define FLASH_SR_BSY (1U << 0U) -#define KEY1 0x45670123U -#define KEY2 0xcdef89abU -#define SR_ERROR_MASK 0x14U -#define SR_EOP 0x20U -#define DBGMCU_IDCODE 0xe0042000U -#define FLASHSIZE 0x1ffff7e0U - -// These are specific to ch32f1 -#define FLASH_MAGIC (FPEC_BASE + 0x34U) -#define FLASH_MODEKEYR_CH32 (FPEC_BASE + 0x24U) // Fast mode for CH32F10x -#define FLASH_CR_FLOCK_CH32 (1U << 15U) // Fast unlock -#define FLASH_CR_FTPG_CH32 (1U << 16U) // Fast page program -#define FLASH_CR_FTER_CH32 (1U << 17U) // Fast page erase -#define FLASH_CR_BUF_LOAD_CH32 (1U << 18U) // Buffer load -#define FLASH_CR_BUF_RESET_CH32 (1U << 19U) // Buffer reset -#define FLASH_SR_EOP (1U << 5U) // End of programming -#define FLASH_BEGIN_ADDRESS_CH32 0x8000000U - -/* "fast" Flash driver for CH32F10x chips */ -static void ch32f1_add_flash(target_s *t, uint32_t addr, size_t length, size_t erasesize) -{ - target_flash_s *f = calloc(1, sizeof(*f)); - if (!f) { /* calloc failed: heap exhaustion */ - DEBUG_ERROR("calloc: failed in %s\n", __func__); - return; - } - - f->start = addr; - f->length = length; - f->blocksize = erasesize; - f->erase = ch32f1_flash_erase; - f->write = ch32f1_flash_write; - f->writesize = erasesize; - f->erased = 0xffU; - target_add_flash(t, f); -} - -#define WAIT_BUSY() \ - do { \ - sr = target_mem_read32(t, FLASH_SR); \ - if (target_check_error(t)) { \ - DEBUG_ERROR("ch32f1 flash write: comm error\n"); \ - return -1; \ - } \ - } while (sr & FLASH_SR_BSY); - -#define WAIT_EOP() \ - do { \ - sr = target_mem_read32(t, FLASH_SR); \ - if (target_check_error(t)) { \ - DEBUG_ERROR("ch32f1 flash write: comm error\n"); \ - return -1; \ - } \ - } while (!(sr & FLASH_SR_EOP)); - -#define CLEAR_EOP() target_mem_write32(t, FLASH_SR, FLASH_SR_EOP) - -#define SET_CR(bit) \ - do { \ - const uint32_t cr = target_mem_read32(t, FLASH_CR) | (bit); \ - target_mem_write32(t, FLASH_CR, cr); \ - } while (0) - -#define CLEAR_CR(bit) \ - do { \ - const uint32_t cr = target_mem_read32(t, FLASH_CR) & (~(bit)); \ - target_mem_write32(t, FLASH_CR, cr); \ - } while (0) - -// Which one is the right value? -#define MAGIC_WORD 0x100U -// #define MAGIC_WORD 0x1000U -#define MAGIC(addr) \ - do { \ - magic = target_mem_read32(t, (addr) ^ MAGIC_WORD); \ - target_mem_write32(t, FLASH_MAGIC, magic); \ - } while (0) - -/* Attempt unlock ch32f103 in fast mode */ -static bool ch32f1_flash_unlock(target_s *t) -{ - DEBUG_INFO("CH32: flash unlock \n"); - - target_mem_write32(t, FLASH_KEYR, KEY1); - target_mem_write32(t, FLASH_KEYR, KEY2); - // fast mode - target_mem_write32(t, FLASH_MODEKEYR_CH32, KEY1); - target_mem_write32(t, FLASH_MODEKEYR_CH32, KEY2); - uint32_t cr = target_mem_read32(t, FLASH_CR); - if (cr & FLASH_CR_FLOCK_CH32) - DEBUG_ERROR("Fast unlock failed, cr: 0x%08" PRIx32 "\n", cr); - return !(cr & FLASH_CR_FLOCK_CH32); -} - -/* - * lock ch32f103 in fast mode - */ -static bool ch32f1_flash_lock(target_s *t) -{ - DEBUG_INFO("CH32: flash lock \n"); - /* - * The LOCK (bit 7) and FLOCK (bit 15) must be set (1) in the same write - * operation, if not FLOCK will be read back as unset (0). - */ - SET_CR(FLASH_CR_LOCK | FLASH_CR_FLOCK_CH32); - const uint32_t cr = target_mem_read32(t, FLASH_CR); - if (!(cr & FLASH_CR_FLOCK_CH32)) - DEBUG_ERROR("Fast lock failed, cr: 0x%08" PRIx32 "\n", cr); - return cr & FLASH_CR_FLOCK_CH32; -} - -/* - *check fast_unlock is there, if so it is a CH32fx - */ -static bool ch32f1_has_fast_unlock(target_s *t) -{ - DEBUG_INFO("CH32: has fast unlock \n"); - // reset fast unlock - SET_CR(FLASH_CR_FLOCK_CH32); - platform_delay(1); // The flash controller is timing sensitive - if (!(target_mem_read32(t, FLASH_CR) & FLASH_CR_FLOCK_CH32)) - return false; - // send unlock sequence - target_mem_write32(t, FLASH_KEYR, KEY1); - target_mem_write32(t, FLASH_KEYR, KEY2); - platform_delay(1); // The flash controller is timing sensitive - // send fast unlock sequence - target_mem_write32(t, FLASH_MODEKEYR_CH32, KEY1); - target_mem_write32(t, FLASH_MODEKEYR_CH32, KEY2); - platform_delay(1); // The flash controller is timing sensitive - return !(target_mem_read32(t, FLASH_CR) & FLASH_CR_FLOCK_CH32); -} - -/* - * Try to identify the ch32f1 chip family - * (Actually grab all Cortex-M3 with designer == ARM not caught earlier...) - */ -bool ch32f1_probe(target_s *t) -{ - if ((t->cpuid & CORTEX_CPUID_PARTNO_MASK) != CORTEX_M3) - return false; - - const uint32_t dbgmcu_idcode = target_mem_read32(t, DBGMCU_IDCODE); - const uint32_t device_id = dbgmcu_idcode & 0x00000fffU; - const uint32_t revision_id = (dbgmcu_idcode & 0xffff0000U) >> 16U; - - DEBUG_WARN("DBGMCU_IDCODE 0x%" PRIx32 ", DEVID 0x%" PRIx32 ", REVID 0x%" PRIx32 " \n", dbgmcu_idcode, device_id, - revision_id); - - if (device_id != 0x410U) // ch32f103, cks32f103, apm32f103 - return false; - - if (revision_id != 0x2000U) // (Hopefully!) only ch32f103 - return false; - - // Try to flock (if this fails it is not a CH32 chip) - if (!ch32f1_has_fast_unlock(t)) - return false; - - t->part_id = device_id; - uint32_t signature = target_mem_read32(t, FLASHSIZE); - /* Some ch32f103c8t6 MCU's found on Blue Pill boards have a zero (0) in the flash memory capacity register */ - if (signature == 0) { - signature = 64; - DEBUG_WARN("CH32: FLASHSIZE = 0, assuming CH32F103C8T6 MCU, seting FLASHSIZE = 64\n"); - } - uint32_t flashSize = signature & 0xffffU; - - target_add_ram(t, 0x20000000, 0x5000); - ch32f1_add_flash(t, FLASH_BEGIN_ADDRESS_CH32, flashSize * 1024U, 128); - target_add_commands(t, stm32f1_cmd_list, "STM32 LD/MD/VL-LD/VL-MD"); - t->driver = "CH32F1 medium density (stm32f1 clone)"; - return true; -} - -/* Fast erase of CH32 devices */ -bool ch32f1_flash_erase(target_flash_s *f, target_addr_t addr, size_t len) -{ - volatile uint32_t sr, magic; - target_s *t = f->t; - DEBUG_INFO("CH32: flash erase \n"); - - if (!ch32f1_flash_unlock(t)) { - DEBUG_ERROR("CH32: Unlock failed\n"); - return false; - } - // Fast Erase 128 bytes pages (ch32 mode) - while (len) { - SET_CR(FLASH_CR_FTER_CH32); // CH32 PAGE_ER - /* Write address to FMA */ - target_mem_write32(t, FLASH_AR, addr); - /* Flash page erase start instruction */ - SET_CR(FLASH_CR_STRT); - WAIT_EOP(); - CLEAR_EOP(); - CLEAR_CR(FLASH_CR_STRT); - // Magic - MAGIC(addr); - if (len > 128U) - len -= 128U; - else - len = 0; - addr += 128U; - } - sr = target_mem_read32(t, FLASH_SR); - ch32f1_flash_lock(t); - if (sr & SR_ERROR_MASK) - DEBUG_ERROR("ch32f1 flash erase error 0x%" PRIx32 "\n", sr); - return !(sr & SR_ERROR_MASK); -} - -/* - * Wait a bit for the previous operation to finish. - * As per test result we need a time similar to 10 read operation over SWD - * We do 32 to have a bit of headroom, then we check we read ffff (erased flash) - * NB: Just reading fff is not enough as it could be a transient previous operation value - */ -static bool ch32f1_wait_flash_ready(target_s *t, uint32_t addr) -{ - uint32_t flash_val = 0; - /* Certain ch32f103c8t6 MCU's found on Blue Pill boards need some uninterrupted time (no SWD link activity) */ - platform_delay(2); - for (size_t cnt = 0; cnt < 32U && flash_val != 0xffffffffU; ++cnt) - flash_val = target_mem_read32(t, addr); - if (flash_val != 0xffffffffU) { - DEBUG_ERROR("ch32f1 Not erased properly at %" PRIx32 " or flash access issue\n", addr); - return false; - } - return true; -} - -/* Fast flash for ch32. Load 128 bytes chunk and then write them */ -static int ch32f1_upload(target_s *t, uint32_t dest, const void *src, uint32_t offset) -{ - volatile uint32_t sr, magic; - const uint32_t *ss = (const uint32_t *)(src + offset); - uint32_t dd = dest + offset; - - SET_CR(FLASH_CR_FTPG_CH32); - target_mem_write32(t, dd + 0, ss[0]); - target_mem_write32(t, dd + 4U, ss[1]); - target_mem_write32(t, dd + 8U, ss[2]); - target_mem_write32(t, dd + 12U, ss[3]); - SET_CR(FLASH_CR_BUF_LOAD_CH32); /* BUF LOAD */ - WAIT_EOP(); - CLEAR_EOP(); - CLEAR_CR(FLASH_CR_FTPG_CH32); - MAGIC(dest + offset); - return 0; -} - -/* Clear the write buffer */ -static int ch32f1_buffer_clear(target_s *t) -{ - volatile uint32_t sr; - SET_CR(FLASH_CR_FTPG_CH32); // Fast page program 4- - SET_CR(FLASH_CR_BUF_RESET_CH32); // BUF_RESET 5- - WAIT_BUSY(); // 6- - CLEAR_CR(FLASH_CR_FTPG_CH32); // Fast page program 4- - return 0; -} - -/* - * CH32 implementation of Flash write using the CH32-specific fast write - */ -static bool ch32f1_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len) -{ - volatile uint32_t sr; - target_s *t = f->t; - size_t length = len; -#ifdef CH32_VERIFY - target_addr_t org_dest = dest; - const void *org_src = src; -#endif - DEBUG_INFO("CH32: flash write 0x%" PRIx32 " ,size=%" PRIu32 "\n", dest, (uint32_t)len); - - while (length > 0) { - if (!ch32f1_flash_unlock(t)) { - DEBUG_ERROR("ch32f1 cannot fast unlock\n"); - return false; - } - WAIT_BUSY(); - - // Buffer reset... - ch32f1_buffer_clear(t); - // Load 128 bytes to buffer - if (!ch32f1_wait_flash_ready(t, dest)) - return false; - - for (size_t i = 0; i < 8U; i++) { - if (ch32f1_upload(t, dest, src, i * 16U)) { - DEBUG_ERROR("Cannot upload to buffer\n"); - return false; - } - } - // write buffer - SET_CR(FLASH_CR_FTPG_CH32); - target_mem_write32(t, FLASH_AR, dest); // 10 - SET_CR(FLASH_CR_STRT); // 11 Start - WAIT_EOP(); // 12 - CLEAR_EOP(); - CLEAR_CR(FLASH_CR_FTPG_CH32); - - uint32_t magic; - MAGIC(dest); - - // next - if (length > 128U) - length -= 128U; - else - length = 0; - dest += 128U; - src += 128U; - - sr = target_mem_read32(t, FLASH_SR); // 13 - ch32f1_flash_lock(t); - if (sr & SR_ERROR_MASK) { - DEBUG_ERROR("ch32f1 flash write error 0x%" PRIx32 "\n", sr); - return false; - } - } - -#ifdef CH32_VERIFY - DEBUG_INFO("Verifying\n"); - for (size_t i = 0; i < len; i += 4U) { - const uint32_t expected = *(uint32_t *)(org_src + i); - const uint32_t actual = target_mem_read32(t, org_dest + i); - if (expected != actual) { - DEBUG_ERROR(">>>>write mismatch at address 0x%" PRIx32 "\n", org_dest + i); - DEBUG_ERROR(">>>>expected: 0x%" PRIx32 "\n", expected); - DEBUG_ERROR(">>>> actual: 0x%" PRIx32 "\n", actual); - return false; - } - } -#endif - - return true; -} diff --git a/src/target/ch32f1x.c b/src/target/ch32f1x.c new file mode 100644 index 00000000000..1431256ff7e --- /dev/null +++ b/src/target/ch32f1x.c @@ -0,0 +1,153 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by mean00 + * Modified 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 CH32F1x target specific functions. */ + +#include "general.h" +#include "target.h" +#include "target_internal.h" +#include "cortexm.h" +#include "buffer_utils.h" +#include "ch32_flash.h" + +/* IDCODE register */ +#define CH32F1X_IDCODE 0xe0042000U +#define CH32F1X_IDCODE_REVISION_ID_OFFSET 16U +#define CH32F1X_IDCODE_REVIDION_ID_MASK (0xffffU << CH32F1X_IDCODE_REVISION_ID_OFFSET) +#define CH32F1X_IDCODE_DEVICE_ID_MASK 0xfffU + +#define CH32F103X_DEVICE_ID 0x410U /* Matches CH32F103, CKS32F103, APM32F103 */ +#define CH32F103X_REVISION_ID 0x2000U /* Only matches CH32F103 (hopefully!) */ + +/* Electronic Signature (ESIG) registers */ +#define CH32F1X_ESIG_BASE 0x1ffff7e0U /* Electronic signature base address */ +#define CH32F1X_ESIG_FLASH_CAP (CH32F1X_ESIG_BASE + 0x00U) /* Flash capacity register, 16 bits, KiB units */ +#define CH32F1X_ESIG_UID1 (CH32F1X_ESIG_BASE + 0x08U) /* Unique ID register, bits 0:31 */ +#define CH32F1X_ESIG_UID2 (CH32F1X_ESIG_BASE + 0x0cU) /* Unique ID register, bits 32:63 */ +#define CH32F1X_ESIG_UID3 (CH32F1X_ESIG_BASE + 0x10U) /* Unique ID register, bits 64:95 */ + +/* Memory mapping */ +#define CH32F103X_FLASH_MEMORY_ADDR 0x08000000U +#define CH32F103X_SRAM_ADDR 0x20000000U + +static bool ch32f1x_uid_cmd(target_s *target, int argc, const char **argv); + +const command_s ch32f1x_cmd_list[] = { + {"uid", ch32f1x_uid_cmd, "Prints 96 bit unique id"}, + {"option", stm32_option_bytes_cmd, "Manipulate option bytes"}, + {NULL, NULL, NULL}, +}; + +/* Check if the FPEC has the CH32 fast mode extension */ +static bool ch32f1x_has_fast_mode_extension(target_s *const target) +{ + const uint32_t fpec_base = CH32_FPEC_BASE; + + /* Start with reset state flash and fast mode locked */ + ch32_flash_lock(target, fpec_base); + + /* Check if the flash and fast mode are locked */ + if (!stm32_flash_locked(target, fpec_base, 0U) || ch32_flash_fast_mode_locked(target, fpec_base)) + return false; + + /* Try to unlock the flash and fast mode, if this fails the fast mode extension is not available */ + const bool result = stm32_flash_unlock(target, fpec_base, 0U) && ch32_flash_fast_mode_unlock(target, fpec_base); + + /* Lock the flash again */ + ch32_flash_lock(target, fpec_base); + + return result; +} + +/* Reads the flash capacity in KiB */ +static size_t ch32f1x_read_flash_capacity(target_s *const target) +{ + /* Get flash capacity from ESIG register */ + const size_t flash_capacity = target_mem_read16(target, CH32F1X_ESIG_FLASH_CAP); + if (flash_capacity == 0U) { + /* + * Some CH32F103C8T6 MCUs seem to have an errata, having zero (0) in the flash capacity ESIG register + * If CH32F103C6xx can be affected this fixup is wrong, as they only have 32KiB of flash + */ + DEBUG_WARN("CH32F1x errata? ESIG_FLASH_CAP = 0, assuming CH32F103C8T6 with 64 KiB flash!\n"); + return 64U; /* 64KiB */ + } + return flash_capacity; +} + +/* Reads the 96 bit unique id */ +static void ch32f1x_read_uid(target_s *const target, uint8_t *const uid) +{ + for (size_t uid_reg_offset = 0; uid_reg_offset < 3U; uid_reg_offset++) + write_be4(uid, uid_reg_offset, target_mem_read32(target, CH32F1X_ESIG_UID1 + (uid_reg_offset << 2U))); +} + +/* Try to identify CH32F1x chip family */ +bool ch32f1x_probe(target_s *const target) +{ + if ((target->cpuid & CORTEX_CPUID_PARTNO_MASK) != CORTEX_M3) + return false; + + const uint32_t idcode = target_mem_read32(target, CH32F1X_IDCODE); + const uint16_t device_id = idcode & CH32F1X_IDCODE_DEVICE_ID_MASK; + const uint16_t revision_id = (idcode & CH32F1X_IDCODE_REVIDION_ID_MASK) >> CH32F1X_IDCODE_REVISION_ID_OFFSET; + + DEBUG_INFO("%s IDCODE 0x%" PRIx32 ", Device ID 0x%03" PRIx16 ", Revision ID 0x%04" PRIx16 " \n", __func__, idcode, + device_id, revision_id); + + if (device_id != CH32F103X_DEVICE_ID || revision_id != CH32F103X_REVISION_ID) + return false; + + /* Check if the FPEC has the CH32 fast mode extension, if not this isn't a CH32F1 */ + if (!ch32f1x_has_fast_mode_extension(target)) + return false; + + target->part_id = device_id; + target->driver = "CH32F103x"; + + const size_t flash_capacity = ch32f1x_read_flash_capacity(target); + const size_t ram_capacity = flash_capacity == 32U ? 10U : 20U; /* 10KiB or 20KiB */ + + /* KiB to bytes */ + target_add_ram(target, CH32F103X_SRAM_ADDR, ram_capacity << 10U); + ch32f1x_add_flash(target, CH32F103X_FLASH_MEMORY_ADDR, flash_capacity << 10U); + + target_add_commands(target, ch32f1x_cmd_list, target->driver); + + return true; +} + +/* Reads the 96 bit unique id */ +static bool ch32f1x_uid_cmd(target_s *const target, const int argc, const char **const argv) +{ + (void)argc; + (void)argv; + + uint8_t uid[12U]; + ch32f1x_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/ch32v3x.c b/src/target/ch32v3x.c new file mode 100644 index 00000000000..cfd6aae98ac --- /dev/null +++ b/src/target/ch32v3x.c @@ -0,0 +1,205 @@ +/* + * 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 CH32V3x target specific functions + * Macros named CH32FV2X_V3X are shared between CH32F2x, CH32V2x and CH32V3x + */ + +#include "general.h" +#include "target.h" +#include "target_internal.h" +#include "buffer_utils.h" +#include "ch32_flash.h" + +/* IDCODE register */ +#define CH32FV2X_V3X_IDCODE 0x1ffff704U +#define CH32FV2X_V3X_IDCODE_REVISION_ID_OFFSET 16U +#define CH32FV2X_V3X_IDCODE_REVIDION_ID_MASK (0xffffU << CH32FV2X_V3X_IDCODE_REVISION_ID_OFFSET) +#define CH32FV2X_V3X_IDCODE_DEVICE_ID_MASK 0xfffU + +/* + * Known IDCODE values: + * CH32V303CBT6: 0x303 3 0 5x4 + * CH32V303RBT6: 0x303 2 0 5x4 + * CH32V303RCT6: 0x303 1 0 5x4 + * CH32V303VCT6: 0x303 0 0 5x4 + * CH32V305FBP6: 0x305 2 0 5x8 + * CH32V305RBT6: 0x305 0 0 5x8 + * CH32V307WCU6: 0x307 3 0 5x8 + * CH32V307FBP6: 0x307 2 0 5x8 + * CH32V307RCT6: 0x307 1 0 5x8 + * CH32V307VCT6: 0x307 0 0 5x8 + */ +#define CH32FV2X_V3X_DEVICE_ID_MASK 0xf0fU /* Helper to glob families (FIXME: verify) */ +#define CH32V303X_DEVICE_ID 0x504U /* Matches CH32V303x */ +#define CH32V305X_7X_DEVICE_ID 0x508U /* Matches CH32V305x and CH32V307x */ + +#define CH32V2X_3X_REVISION_ID_FAMILY_OFFSET 4U /* Helper to extract family code */ +#define CH32V2X_3X_REVISION_ID_FAMILY_MASK (0xfffU << CH32V2X_3X_REVISION_ID_FAMILY_OFFSET) +#define CH32V203X_REVISION_ID_FAMILY 0x203U +#define CH32V208X_REVISION_ID_FAMILY 0x208U +#define CH32V305X_REVISION_ID_FAMILY 0x305U +#define CH32V303X_REVISION_ID_FAMILY 0x303U +#define CH32V307X_REVISION_ID_FAMILY 0x307U + +/* Electronic Signature (ESIG) registers */ +#define CH32FV2X_V3X_ESIG_BASE 0x1ffff7e0U /* Electronic signature base address */ +#define CH32FV2X_V3X_ESIG_FLASH_CAP (CH32FV2X_V3X_ESIG_BASE + 0x00U) /* Flash capacity register, 16 bits, KiB units */ +#define CH32FV2X_V3X_ESIG_UID1 (CH32FV2X_V3X_ESIG_BASE + 0x08U) /* Unique ID register, bits 0:31 */ +#define CH32FV2X_V3X_ESIG_UID2 (CH32FV2X_V3X_ESIG_BASE + 0x0cU) /* Unique ID register, bits 32:63 */ +#define CH32FV2X_V3X_ESIG_UID3 (CH32FV2X_V3X_ESIG_BASE + 0x10U) /* Unique ID register, bits 64:95 */ + +/* Memory mapping */ +#define CH32FV2X_V3X_FLASH_MEMORY_ADDR 0x08000000U +#define CH32FV2X_V3X_SRAM_ADDR 0x20000000U + +static bool ch32fv2x_v3x_uid_cmd(target_s *target, int argc, const char **argv); + +const command_s ch32fv2x_v3x_cmd_list[] = { + {"uid", ch32fv2x_v3x_uid_cmd, "Prints 96 bit unique id"}, + {"option", stm32_option_bytes_cmd, "Manipulate option bytes"}, + {NULL, NULL, NULL}, +}; + +/* Reads the 96 bit unique id */ +static void ch32fv2x_v3x_read_uid(target_s *const target, uint8_t *const uid) +{ + for (size_t uid_reg_offset = 0; uid_reg_offset < 3U; uid_reg_offset++) + write_be4(uid, uid_reg_offset, target_mem_read32(target, CH32FV2X_V3X_ESIG_UID1 + (uid_reg_offset << 2U))); +} + +/* Reads the flash capacity in KiB */ +static inline size_t ch32fv2x_v3x_read_flash_capacity(target_s *const target) +{ + return target_mem_read16(target, CH32FV2X_V3X_ESIG_FLASH_CAP); +} + +/* Returns RAM capacity based on the flash capacity */ +static inline size_t ch32fv2x_v3x_get_ram_capacity(const uint16_t family, const size_t flash_capacity) +{ + /* + * With exception of CH32FV208x all lines follow the same pattern: + * ┌────────────┬─────────┬────────┐ + * │ Family │ Flash │ RAM │ + * ├────────────┴─────────┴────────┤ + * │ Low/medium-density line │ + * ├────────────┬─────────┬────────┤ + * │ CH32FV203x │ 32 KiB │ 10KiB │ + * │ CH32FV203x │ 64 KiB │ 20KiB │ + * ├────────────┴─────────┴────────┤ + * │ High-density general line │ + * ├────────────┬─────────┬────────┤ + * │ CH32F203x │ 128 KiB │ 32 KiB │ + * │ CH32F203x │ 256 KiB │ 64 KiB │ + * │ CH32V303x │ 128 KiB │ 32 KiB │ + * │ CH32V303x │ 256 KiB │ 64 KiB │ + * ├────────────┴─────────┴────────┤ + * │ Connectivity line │ + * ├────────────┬─────────┬────────┤ + * │ CH32F205x │ 128 KiB │ 32 KiB │ + * │ CH32V305x │ 128 KiB │ 32 KiB │ + * ├────────────┴─────────┴────────┤ + * │ Interconnectivity line │ + * ├────────────┬─────────┬────────┤ + * │ CH32F207x │ 256 KiB │ 64 KiB │ + * │ CH32V307x │ 256 KiB │ 64 KiB │ + * ├────────────┴─────────┴────────┤ + * │ Wireless line │ + * ├────────────┬─────────┬────────┤ + * │ CH32FV208x │ 128 KiB │ 64 KiB │ + * └────────────┴─────────┴────────┘ + */ + + /* FIXME: CH32*F*208x may not share this family code */ + if (family == CH32V208X_REVISION_ID_FAMILY) + return 64U; /* 64 KiB */ + else if (flash_capacity <= 32U) + return 10U; /* 10 KiB */ + else if (flash_capacity <= 64U) + return 20U; /* 20 KiB */ + else if (flash_capacity <= 128U) + return 32U; /* 32 KiB */ + else + return 64U; /* 64 KiB */ +} + +/* Probe for Risc-V CH32V3x family */ +bool ch32v3x_probe(target_s *const target) +{ + const uint32_t idcode = target_mem_read32(target, CH32FV2X_V3X_IDCODE); + const uint16_t device_id = idcode & CH32FV2X_V3X_IDCODE_DEVICE_ID_MASK; + const uint16_t revision_id = + (idcode & CH32FV2X_V3X_IDCODE_REVIDION_ID_MASK) >> CH32FV2X_V3X_IDCODE_REVISION_ID_OFFSET; + const uint16_t family = (revision_id & CH32V2X_3X_REVISION_ID_FAMILY_MASK) >> CH32V2X_3X_REVISION_ID_FAMILY_OFFSET; + + DEBUG_INFO("%s IDCODE 0x%" PRIx32 ", Device ID 0x%03" PRIx16 ", Revision ID 0x%04" PRIx16 ", Family 0x%03" PRIx16 + " \n", + __func__, idcode, device_id, revision_id, family); + + if (device_id != CH32V303X_DEVICE_ID && device_id != CH32V305X_7X_DEVICE_ID) + return false; + + switch (family) { + case CH32V305X_REVISION_ID_FAMILY: + target->driver = "CH32V305x"; + break; + case CH32V303X_REVISION_ID_FAMILY: + target->driver = "CH32V303x"; + break; + case CH32V307X_REVISION_ID_FAMILY: + target->driver = "CH32V307x"; + break; + default: + return false; + } + + target->part_id = idcode; + + const size_t flash_capacity = ch32fv2x_v3x_read_flash_capacity(target); + const size_t ram_capacity = ch32fv2x_v3x_get_ram_capacity(family, flash_capacity); + + DEBUG_INFO("%s Flash size: %zu KiB, RAM size: %zu KiB\n", __func__, flash_capacity, ram_capacity); + + /* KiB to bytes */ + target_add_ram(target, CH32FV2X_V3X_SRAM_ADDR, ram_capacity << 10U); + ch32fv2x_v3x_add_flash(target, CH32FV2X_V3X_FLASH_MEMORY_ADDR, flash_capacity << 10U); + + target_add_commands(target, ch32fv2x_v3x_cmd_list, target->driver); + + return true; +} + +/* Reads the 96 bit unique id */ +static bool ch32fv2x_v3x_uid_cmd(target_s *const target, const int argc, const char **const argv) +{ + (void)argc; + (void)argv; + + uint8_t uid[12U]; + ch32fv2x_v3x_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/cortexm.c b/src/target/cortexm.c index 3c4f1d2698f..cc597cf82fa 100644 --- a/src/target/cortexm.c +++ b/src/target/cortexm.c @@ -483,25 +483,25 @@ const char *cortexm_regs_description(target_s *t) bool cortexm_probe(adiv5_access_port_s *ap) { - target_s *t = target_new(); - if (!t) + target_s *target = target_new(); + if (!target) return false; adiv5_ap_ref(ap); if (ap->dp->version >= 2 && ap->dp->target_designer_code != 0) { /* Use TARGETID register to identify target */ - t->designer_code = ap->dp->target_designer_code; - t->part_id = ap->dp->target_partno; + target->designer_code = ap->dp->target_designer_code; + target->part_id = ap->dp->target_partno; } else { /* Use AP DESIGNER and AP PARTNO to identify target */ - t->designer_code = ap->designer_code; - t->part_id = ap->partno; + target->designer_code = ap->designer_code; + target->part_id = ap->partno; } /* MM32F5xxx: part designer code is Arm China, target designer code uses forbidden continuation code */ - if (t->designer_code == JEP106_MANUFACTURER_ERRATA_ARM_CHINA && + if (target->designer_code == JEP106_MANUFACTURER_ERRATA_ARM_CHINA && ap->dp->designer_code == JEP106_MANUFACTURER_ARM_CHINA) - t->designer_code = JEP106_MANUFACTURER_ARM_CHINA; + target->designer_code = JEP106_MANUFACTURER_ARM_CHINA; cortexm_priv_s *priv = calloc(1, sizeof(*priv)); if (!priv) { /* calloc failed: heap exhaustion */ @@ -509,48 +509,48 @@ bool cortexm_probe(adiv5_access_port_s *ap) return false; } - t->priv = priv; - t->priv_free = cortex_priv_free; + target->priv = priv; + target->priv_free = cortex_priv_free; priv->base.ap = ap; priv->base.base_addr = CORTEXM_SCS_BASE; - t->check_error = cortex_check_error; - t->mem_read = cortexm_mem_read; - t->mem_write = cortexm_mem_write; + target->check_error = cortex_check_error; + target->mem_read = cortexm_mem_read; + target->mem_write = cortexm_mem_write; - t->driver = "ARM Cortex-M"; + target->driver = "ARM Cortex-M"; - cortex_read_cpuid(t); + cortex_read_cpuid(target); - t->attach = cortexm_attach; - t->detach = cortexm_detach; + target->attach = cortexm_attach; + target->detach = cortexm_detach; /* Probe for FP extension. */ - uint32_t cpacr = target_mem_read32(t, CORTEXM_CPACR); + uint32_t cpacr = target_mem_read32(target, CORTEXM_CPACR); cpacr |= 0x00f00000U; /* CP10 = 0b11, CP11 = 0b11 */ - target_mem_write32(t, CORTEXM_CPACR, cpacr); - bool is_cortexmf = target_mem_read32(t, CORTEXM_CPACR) == cpacr; + target_mem_write32(target, CORTEXM_CPACR, cpacr); + bool is_cortexmf = target_mem_read32(target, CORTEXM_CPACR) == cpacr; - t->regs_description = cortexm_regs_description; - t->regs_read = cortexm_regs_read; - t->regs_write = cortexm_regs_write; - t->reg_read = cortexm_reg_read; - t->reg_write = cortexm_reg_write; + target->regs_description = cortexm_regs_description; + target->regs_read = cortexm_regs_read; + target->regs_write = cortexm_regs_write; + target->reg_read = cortexm_reg_read; + target->reg_write = cortexm_reg_write; - t->reset = cortexm_reset; - t->halt_request = cortexm_halt_request; - t->halt_poll = cortexm_halt_poll; - t->halt_resume = cortexm_halt_resume; - t->regs_size = sizeof(uint32_t) * CORTEXM_GENERAL_REG_COUNT; + target->reset = cortexm_reset; + target->halt_request = cortexm_halt_request; + target->halt_poll = cortexm_halt_poll; + target->halt_resume = cortexm_halt_resume; + target->regs_size = sizeof(uint32_t) * CORTEXM_GENERAL_REG_COUNT; - t->breakwatch_set = cortexm_breakwatch_set; - t->breakwatch_clear = cortexm_breakwatch_clear; + target->breakwatch_set = cortexm_breakwatch_set; + target->breakwatch_clear = cortexm_breakwatch_clear; - target_add_commands(t, cortexm_cmd_list, t->driver); + target_add_commands(target, cortexm_cmd_list, target->driver); if (is_cortexmf) { - t->target_options |= CORTEXM_TOPT_FLAVOUR_V7MF; - t->regs_size += sizeof(uint32_t) * CORTEX_FLOAT_REG_COUNT; + target->target_options |= CORTEXM_TOPT_FLAVOUR_V7MF; + target->regs_size += sizeof(uint32_t) * CORTEX_FLOAT_REG_COUNT; } /* Default vectors to catch */ @@ -568,15 +568,15 @@ bool cortexm_probe(adiv5_access_port_s *ap) conn_reset = true; /* Request halt when reset is de-asseted */ - target_mem_write32(t, CORTEXM_DEMCR, priv->demcr); + target_mem_write32(target, CORTEXM_DEMCR, priv->demcr); /* Force a halt */ - cortexm_halt_request(t); + cortexm_halt_request(target); /* Release reset */ platform_nrst_set_val(false); /* Poll for release from reset */ platform_timeout_s timeout; platform_timeout_set(&timeout, 1000); - while (target_mem_read32(t, CORTEXM_DHCSR) & CORTEXM_DHCSR_S_RESET_ST) { + while (target_mem_read32(target, CORTEXM_DHCSR) & CORTEXM_DHCSR_S_RESET_ST) { if (platform_timeout_is_expired(&timeout)) { DEBUG_ERROR("Error releasing from reset\n"); /* Go on and try to detect the target anyways */ @@ -587,18 +587,18 @@ bool cortexm_probe(adiv5_access_port_s *ap) } /* Check cache type */ - const uint32_t cache_type = target_mem_read32(t, CORTEXM_CTR); + const uint32_t cache_type = target_mem_read32(target, CORTEXM_CTR); if (cache_type >> CORTEX_CTR_FORMAT_SHIFT == CORTEX_CTR_FORMAT_ARMv7) { priv->base.icache_line_length = CORTEX_CTR_ICACHE_LINE(cache_type); priv->base.dcache_line_length = CORTEX_CTR_DCACHE_LINE(cache_type); } else - target_check_error(t); + target_check_error(target); /* If we set the interrupt catch vector earlier, clear it. */ if (conn_reset) - target_mem_write32(t, CORTEXM_DEMCR, 0); + target_mem_write32(target, CORTEXM_DEMCR, 0); - switch (t->designer_code) { + switch (target->designer_code) { case JEP106_MANUFACTURER_FREESCALE: PROBE(imxrt_probe); PROBE(kinetis_probe); @@ -649,7 +649,7 @@ bool cortexm_probe(adiv5_access_port_s *ap) PROBE(renesas_probe); break; case JEP106_MANUFACTURER_NXP: - if ((t->cpuid & CORTEX_CPUID_PARTNO_MASK) == CORTEX_M33) + if ((target->cpuid & CORTEX_CPUID_PARTNO_MASK) == CORTEX_M33) PROBE(lpc55xx_probe); else DEBUG_WARN("Unhandled NXP device\n"); @@ -662,22 +662,22 @@ bool cortexm_probe(adiv5_access_port_s *ap) * All of these have braces as a brake from the standard so they're completely * consistent and easier to add new probe calls to. */ - if (t->part_id == 0x4c0U) { /* Cortex-M0+ ROM */ - PROBE(lpc11xx_probe); /* LPC8 */ - PROBE(hc32l110_probe); /* HDSC HC32L110 */ - } else if (t->part_id == 0x4c1U) { /* NXP Cortex-M0+ ROM */ - PROBE(lpc11xx_probe); /* newer LPC11U6x */ - } else if (t->part_id == 0x4c3U) { /* Cortex-M3 ROM */ + if (target->part_id == 0x4c0U) { /* Cortex-M0+ ROM */ + PROBE(lpc11xx_probe); /* LPC8 */ + PROBE(hc32l110_probe); /* HDSC HC32L110 */ + } else if (target->part_id == 0x4c1U) { /* NXP Cortex-M0+ ROM */ + PROBE(lpc11xx_probe); /* newer LPC11U6x */ + } else if (target->part_id == 0x4c3U) { /* Cortex-M3 ROM */ PROBE(lmi_probe); PROBE(ch32f1_probe); - PROBE(stm32f1_probe); /* Care for other STM32F1 clones (?) */ - PROBE(lpc15xx_probe); /* Thanks to JojoS for testing */ - PROBE(mm32f3xx_probe); /* MindMotion MM32 */ - } else if (t->part_id == 0x471U) { /* Cortex-M0 ROM */ - PROBE(lpc11xx_probe); /* LPC24C11 */ + PROBE(stm32f1_probe); /* Care for other STM32F1 clones (?) */ + PROBE(lpc15xx_probe); /* Thanks to JojoS for testing */ + PROBE(mm32f3xx_probe); /* MindMotion MM32 */ + } else if (target->part_id == 0x471U) { /* Cortex-M0 ROM */ + PROBE(lpc11xx_probe); /* LPC24C11 */ PROBE(lpc43xx_probe); - PROBE(mm32l0xx_probe); /* MindMotion MM32 */ - } else if (t->part_id == 0x4c4U) { /* Cortex-M4 ROM */ + PROBE(mm32l0xx_probe); /* MindMotion MM32 */ + } else if (target->part_id == 0x4c4U) { /* Cortex-M4 ROM */ PROBE(sam3x_probe); PROBE(lmi_probe); /* @@ -694,8 +694,8 @@ bool cortexm_probe(adiv5_access_port_s *ap) PROBE(kinetis_probe); /* Older K-series */ PROBE(at32fxx_probe); PROBE(msp432e4_probe); - } else if (t->part_id == 0x4cbU) { /* Cortex-M23 ROM */ - PROBE(gd32f1_probe); /* GD32E23x uses GD32F1 peripherals */ + } else if (target->part_id == 0x4cbU) { /* Cortex-M23 ROM */ + PROBE(gd32f1_probe); /* GD32E23x uses GD32F1 peripherals */ } break; case ASCII_CODE_FLAG: @@ -710,9 +710,10 @@ bool cortexm_probe(adiv5_access_port_s *ap) break; } #if PC_HOSTED == 0 - gdb_outf("Please report unknown device with Designer 0x%x Part ID 0x%x\n", t->designer_code, t->part_id); + gdb_outf("Please report unknown device with Designer 0x%x Part ID 0x%x\n", target->designer_code, target->part_id); #else - DEBUG_WARN("Please report unknown device with Designer 0x%x Part ID 0x%x\n", t->designer_code, t->part_id); + DEBUG_WARN( + "Please report unknown device with Designer 0x%x Part ID 0x%x\n", target->designer_code, target->part_id); #endif return true; } diff --git a/src/target/efm32.c b/src/target/efm32.c index dbfcaa56c32..9fffb478e70 100644 --- a/src/target/efm32.c +++ b/src/target/efm32.c @@ -47,7 +47,7 @@ static bool efm32_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); static bool efm32_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len); -static bool efm32_mass_erase(target_s *t); +static bool efm32_mass_erase(target_s *target, platform_timeout_s *print_progess); static const uint16_t efm32_flash_write_stub[] = { #include "flashstub/efm32.stub" @@ -666,55 +666,53 @@ static bool efm32_flash_write(target_flash_s *f, target_addr_t dest, const void } /* Uses the MSC ERASEMAIN0/1 command to erase the entire flash */ -static bool efm32_mass_erase(target_s *t) +static bool efm32_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { - efm32_priv_s *priv_storage = (efm32_priv_s *)t->target_storage; + efm32_priv_s *priv_storage = (efm32_priv_s *)target->target_storage; if (!priv_storage || !priv_storage->device) return false; if (priv_storage->device->family_id == 71 || priv_storage->device->family_id == 73) { /* original Gecko and Tiny Gecko families don't support mass erase */ - tc_printf(t, "This device does not support mass erase through MSC.\n"); + tc_printf(target, "This device does not support mass erase through MSC.\n"); return false; } uint32_t msc = priv_storage->device->msc_addr; - uint16_t flash_kib = efm32_read_flash_size(t, priv_storage->di_version); + uint16_t flash_kib = efm32_read_flash_size(target, priv_storage->di_version); /* Set WREN bit to enable MSC write and erase functionality */ - target_mem_write32(t, EFM32_MSC_WRITECTRL(msc), 1); + target_mem_write32(target, EFM32_MSC_WRITECTRL(msc), 1); /* Unlock mass erase */ - target_mem_write32(t, EFM32_MSC_MASSLOCK(msc), EFM32_MSC_MASSLOCK_LOCKKEY); + target_mem_write32(target, EFM32_MSC_MASSLOCK(msc), EFM32_MSC_MASSLOCK_LOCKKEY); /* Erase operation */ - target_mem_write32(t, EFM32_MSC_WRITECMD(msc), EFM32_MSC_WRITECMD_ERASEMAIN0); + target_mem_write32(target, EFM32_MSC_WRITECMD(msc), EFM32_MSC_WRITECMD_ERASEMAIN0); - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); /* Poll MSC Busy */ - while ((target_mem_read32(t, EFM32_MSC_STATUS(msc)) & EFM32_MSC_STATUS_BUSY)) { - if (target_check_error(t)) + while ((target_mem_read32(target, EFM32_MSC_STATUS(msc)) & EFM32_MSC_STATUS_BUSY)) { + if (target_check_error(target)) return false; - target_print_progress(&timeout); + target_print_progress(print_progess); } /* Parts with >= 512 kiB flash have 2 mass erase regions */ - if (flash_kib >= 512) { + if (flash_kib >= 512U) { /* Erase operation */ - target_mem_write32(t, EFM32_MSC_WRITECMD(msc), EFM32_MSC_WRITECMD_ERASEMAIN1); + target_mem_write32(target, EFM32_MSC_WRITECMD(msc), EFM32_MSC_WRITECMD_ERASEMAIN1); /* Poll MSC Busy */ - while ((target_mem_read32(t, EFM32_MSC_STATUS(msc)) & EFM32_MSC_STATUS_BUSY)) { - if (target_check_error(t)) + while ((target_mem_read32(target, EFM32_MSC_STATUS(msc)) & EFM32_MSC_STATUS_BUSY)) { + if (target_check_error(target)) return false; - target_print_progress(&timeout); + target_print_progress(print_progess); } } /* Relock mass erase */ - target_mem_write32(t, EFM32_MSC_MASSLOCK(msc), 0); + target_mem_write32(target, EFM32_MSC_MASSLOCK(msc), 0); return true; } @@ -926,7 +924,7 @@ static bool efm32_cmd_bootloader(target_s *t, int argc, const char **argv) #define CMDKEY 0xcfacc118U -static bool efm32_aap_mass_erase(target_s *t); +static bool efm32_aap_mass_erase(target_s *target, platform_timeout_s *print_progess); /* AAP Probe */ typedef struct efm32_aap_priv { @@ -967,9 +965,9 @@ bool efm32_aap_probe(adiv5_access_port_s *ap) return true; } -static bool efm32_aap_mass_erase(target_s *t) +static bool efm32_aap_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { - adiv5_access_port_s *ap = t->priv; + adiv5_access_port_s *const ap = cortex_ap(target); uint32_t status; /* Read status */ @@ -984,14 +982,12 @@ static bool efm32_aap_mass_erase(target_s *t) DEBUG_INFO("EFM32: Issuing DEVICEERASE...\n"); adiv5_ap_write(ap, AAP_CMDKEY, CMDKEY); - adiv5_ap_write(ap, AAP_CMD, 1); + adiv5_ap_write(ap, AAP_CMD, 1U); - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); /* Read until 0, probably should have a timeout here... */ do { status = adiv5_ap_read(ap, AAP_STATUS); - target_print_progress(&timeout); + target_print_progress(print_progess); } while (status & AAP_STATUS_ERASEBUSY); /* Read status */ diff --git a/src/target/esp32c3.c b/src/target/esp32c3.c new file mode 100644 index 00000000000..5b7122940b1 --- /dev/null +++ b/src/target/esp32c3.c @@ -0,0 +1,535 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rachel Mant + * 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. + */ + +#include "general.h" +#include "target_internal.h" +#include "target_probe.h" +#include "riscv_debug.h" +#include "sfdp.h" + +#define ESP32_C3_ARCH_ID 0x80000001U +#define ESP32_C3_IMPL_ID 0x00000001U + +#define ESP32_C3_DBUS_SRAM1_BASE 0x3fc80000U +#define ESP32_C3_DBUS_SRAM1_SIZE 0x00060000U +#define ESP32_C3_IBUS_SRAM0_BASE 0x4037c000U +#define ESP32_C3_IBUS_SRAM0_SIZE 0x00004000U +#define ESP32_C3_IBUS_SRAM1_BASE 0x40380000U +#define ESP32_C3_IBUS_SRAM1_SIZE 0x00060000U +#define ESP32_C3_RTC_SRAM_BASE 0x50000000U +#define ESP32_C3_RTC_SRAM_SIZE 0x00002000U + +#define ESP32_C3_IBUS_FLASH_BASE 0x42000000U +#define ESP32_C3_IBUS_FLASH_SIZE 0x00800000U + +#define ESP32_C3_SPI1_BASE 0x60002000U +#define ESP32_C3_SPI1_CMD (ESP32_C3_SPI1_BASE + 0x000U) +#define ESP32_C3_SPI1_ADDR (ESP32_C3_SPI1_BASE + 0x004U) +#define ESP32_C3_SPI1_USER0 (ESP32_C3_SPI1_BASE + 0x018U) +#define ESP32_C3_SPI1_USER1 (ESP32_C3_SPI1_BASE + 0x01cU) +#define ESP32_C3_SPI1_USER2 (ESP32_C3_SPI1_BASE + 0x020U) +#define ESP32_C3_SPI1_DATA_OUT_LEN (ESP32_C3_SPI1_BASE + 0x024U) +#define ESP32_C3_SPI1_DATA_IN_LEN (ESP32_C3_SPI1_BASE + 0x028U) +#define ESP32_C3_SPI1_MISC (ESP32_C3_SPI1_BASE + 0x034U) +#define ESP32_C3_SPI1_DATA (ESP32_C3_SPI1_BASE + 0x058U) + +/* These define the various stages of a SPI transaction that we can choose to enable */ +#define ESP32_C3_SPI_CMD_EXEC_XFER 0x00040000U +#define ESP32_C3_SPI_USER0_CMD 0x80000000U +#define ESP32_C3_SPI_USER0_ADDR 0x40000000U +#define ESP32_C3_SPI_USER0_DUMMY 0x20000000U +#define ESP32_C3_SPI_USER0_DATA_IN 0x10000000U +#define ESP32_C3_SPI_USER0_DATA_OUT 0x08000000U +#define ESP32_C3_SPI_MISC_CS_HOLD 0x00000400U + +/* These define the various bit ranges used to store the cycle counts for the enabled stages */ +#define ESP32_C3_SPI_USER2_CMD_LEN_MASK 0xf0000000U +#define ESP32_C3_SPI_USER2_CMD_LEN_SHIFT 28U +#define ESP32_C3_SPI_USER2_CMD_LEN(x) ((((x)-1U) << ESP32_C3_SPI_USER2_CMD_LEN_SHIFT) & ESP32_C3_SPI_USER2_CMD_LEN_MASK) +#define ESP32_C3_SPI_USER1_ADDR_LEN_MASK 0xfc000000U +#define ESP32_C3_SPI_USER1_ADDR_LEN_SHIFT 26U +#define ESP32_C3_SPI_USER1_ADDR_LEN(x) \ + ((((x)-1U) << ESP32_C3_SPI_USER1_ADDR_LEN_SHIFT) & ESP32_C3_SPI_USER1_ADDR_LEN_MASK) +#define ESP32_C3_SPI_USER1_DUMMY_LEN_MASK 0x0000003fU +#define ESP32_C3_SPI_USER1_DUMMY_LEN(x) (((x)-1U) & ESP32_C3_SPI_USER1_DUMMY_LEN_MASK) +#define ESP32_C3_SPI_DATA_BIT_LEN_MASK 0x000003ffU +#define ESP32_C3_SPI_DATA_BIT_LEN(x) ((((x)*8U) - 1U) & ESP32_C3_SPI_DATA_BIT_LEN_MASK) + +#define ESP32_C3_RTC_BASE 0x60008000U +#define ESP32_C3_RTC_WDT_CONFIG0 (ESP32_C3_RTC_BASE + 0x090U) +#define ESP32_C3_RTC_WDT_FEED (ESP32_C3_RTC_BASE + 0x0a4U) +#define ESP32_C3_RTC_WDT_WRITE_PROT (ESP32_C3_RTC_BASE + 0x0a8U) +#define ESP32_C3_RTC_SWD_CONFIG (ESP32_C3_RTC_BASE + 0x0acU) +#define ESP32_C3_RTC_SWD_WRITE_PROT (ESP32_C3_RTC_BASE + 0x0b0U) + +#define ESP32_C3_WDT_WRITE_PROT_KEY 0x50d83aa1U +#define ESP32_C3_RTC_SWD_WRITE_PROT_KEY 0x8f1d312aU +#define ESP32_C3_RTC_SWD_CONFIG_DISABLE 0x40000002U +#define ESP32_C3_RTC_SWD_CONFIG_FEED 0x60000002U + +#define ESP32_C3_TIMG0_BASE 0x6001f000U +#define ESP32_C3_TIMG0_WDT_CONFIG0 (ESP32_C3_TIMG0_BASE + 0x048U) +#define ESP32_C3_TIMG0_WDT_FEED (ESP32_C3_TIMG0_BASE + 0x060U) +#define ESP32_C3_TIMG0_WDT_WRITE_PROT (ESP32_C3_TIMG0_BASE + 0x064U) + +#define ESP32_C3_TIMG1_BASE 0x60020000U +#define ESP32_C3_TIMG1_WDT_CONFIG0 (ESP32_C3_TIMG1_BASE + 0x048U) +#define ESP32_C3_TIMG1_WDT_FEED (ESP32_C3_TIMG1_BASE + 0x060U) +#define ESP32_C3_TIMG1_WDT_WRITE_PROT (ESP32_C3_TIMG1_BASE + 0x064U) + +#define ESP32_C3_EXTMEM_BASE 0x600c4000U +#define ESP32_C3_EXTMEM_ICACHE_SYNC_CTRL (ESP32_C3_EXTMEM_BASE + 0x028U) +#define ESP32_C3_EXTMEM_ICACHE_SYNC_ADDR (ESP32_C3_EXTMEM_BASE + 0x02cU) +#define ESP32_C3_EXTMEM_ICACHE_SYNC_SIZE (ESP32_C3_EXTMEM_BASE + 0x030U) +#define ESP32_C3_EXTMEM_ICACHE_PRELOAD_CTRL (ESP32_C3_EXTMEM_BASE + 0x034U) +#define ESP32_C3_EXTMEM_ICACHE_PRELOAD_ADDR (ESP32_C3_EXTMEM_BASE + 0x038U) +#define ESP32_C3_EXTMEM_ICACHE_PRELOAD_SIZE (ESP32_C3_EXTMEM_BASE + 0x03cU) + +#define ESP32_C3_EXTMEM_ICACHE_INVALIDATE 0x00000001U +#define ESP32_C3_EXTMEM_ICACHE_SYNC_DONE 0x00000002U +#define ESP32_C3_EXTMEM_ICACHE_PRELOAD 0x00000001U +#define ESP32_C3_EXTMEM_ICACHE_PRELOAD_DONE 0x00000002U + +#define ESP32_C3_SPI_FLASH_OPCODE_MASK 0x000000ffU +#define ESP32_C3_SPI_FLASH_OPCODE(x) ((x)&ESP32_C3_SPI_FLASH_OPCODE_MASK) +#define ESP32_C3_SPI_FLASH_DUMMY_MASK 0x0000ff00U +#define ESP32_C3_SPI_FLASH_DUMMY_SHIFT 8U +#define ESP32_C3_SPI_FLASH_DUMMY_LEN(x) (((x) << ESP32_C3_SPI_FLASH_DUMMY_SHIFT) & ESP32_C3_SPI_FLASH_DUMMY_MASK) +#define ESP32_C3_SPI_FLASH_OPCODE_MODE_MASK 0x00010000U +#define ESP32_C3_SPI_FLASH_OPCODE_ONLY (0U << 16U) +#define ESP32_C3_SPI_FLASH_OPCODE_3B_ADDR (1U << 16U) +#define ESP32_C3_SPI_FLASH_DATA_IN (0U << 17U) +#define ESP32_C3_SPI_FLASH_DATA_OUT (1U << 17U) +#define ESP32_C3_SPI_FLASH_COMMAND_MASK 0x0003ffffU + +#define SPI_FLASH_OPCODE_SECTOR_ERASE 0x20U +#define SPI_FLASH_CMD_WRITE_ENABLE \ + (ESP32_C3_SPI_FLASH_OPCODE_ONLY | ESP32_C3_SPI_FLASH_DUMMY_LEN(0) | ESP32_C3_SPI_FLASH_OPCODE(0x06U)) +#define SPI_FLASH_CMD_PAGE_PROGRAM \ + (ESP32_C3_SPI_FLASH_OPCODE_3B_ADDR | ESP32_C3_SPI_FLASH_DATA_OUT | ESP32_C3_SPI_FLASH_DUMMY_LEN(0) | \ + ESP32_C3_SPI_FLASH_OPCODE(0x02)) +#define SPI_FLASH_CMD_SECTOR_ERASE (ESP32_C3_SPI_FLASH_OPCODE_3B_ADDR | ESP32_C3_SPI_FLASH_DUMMY_LEN(0)) +#define SPI_FLASH_CMD_CHIP_ERASE \ + (ESP32_C3_SPI_FLASH_OPCODE_ONLY | ESP32_C3_SPI_FLASH_DUMMY_LEN(0) | ESP32_C3_SPI_FLASH_OPCODE(0x60U)) +#define SPI_FLASH_CMD_READ_STATUS \ + (ESP32_C3_SPI_FLASH_OPCODE_ONLY | ESP32_C3_SPI_FLASH_DATA_IN | ESP32_C3_SPI_FLASH_DUMMY_LEN(0) | \ + ESP32_C3_SPI_FLASH_OPCODE(0x05U)) +#define SPI_FLASH_CMD_READ_JEDEC_ID \ + (ESP32_C3_SPI_FLASH_OPCODE_ONLY | ESP32_C3_SPI_FLASH_DATA_IN | ESP32_C3_SPI_FLASH_DUMMY_LEN(0) | \ + ESP32_C3_SPI_FLASH_OPCODE(0x9fU)) +#define SPI_FLASH_CMD_READ_SFDP \ + (ESP32_C3_SPI_FLASH_OPCODE_3B_ADDR | ESP32_C3_SPI_FLASH_DATA_IN | ESP32_C3_SPI_FLASH_DUMMY_LEN(8) | \ + ESP32_C3_SPI_FLASH_OPCODE(0x5aU)) + +#define SPI_FLASH_STATUS_BUSY 0x01U +#define SPI_FLASH_STATUS_WRITE_ENABLED 0x02U + +typedef struct esp32c3_priv { + uint32_t wdt_config[4]; + target_addr_t last_invalidated_sector; +} esp32c3_priv_s; + +typedef struct esp32c3_spi_flash { + target_flash_s flash; + uint32_t page_size; + uint8_t sector_erase_opcode; +} esp32c3_spi_flash_s; + +static void esp32c3_disable_wdts(target_s *target); +static void esp32c3_restore_wdts(target_s *target); +static void esp32c3_halt_request(target_s *target); +static void esp32c3_halt_resume(target_s *target, bool step); +static target_halt_reason_e esp32c3_halt_poll(target_s *target, target_addr_t *watch); + +static void esp32c3_spi_read(target_s *target, uint32_t command, target_addr_t address, void *buffer, size_t length); +static void esp32c3_spi_write( + target_s *target, uint32_t command, target_addr_t address, const void *buffer, size_t length); + +static bool esp32c3_mass_erase(target_s *target, platform_timeout_s *print_progess); +static bool esp32c3_enter_flash_mode(target_s *target); +static bool esp32c3_exit_flash_mode(target_s *target); +static bool esp32c3_spi_flash_erase(target_flash_s *flash, target_addr_t addr, size_t length); +static bool esp32c3_spi_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t length); + +static void esp32c3_spi_read_sfdp( + target_s *const target, const uint16_t command, const uint32_t address, void *const buffer, const size_t length) +{ + esp32c3_spi_read(target, command, address, buffer, length); +} + +static void esp32c3_add_flash(target_s *const target) +{ + esp32c3_spi_flash_s *const spi_flash = calloc(1, sizeof(*spi_flash)); + if (!spi_flash) { /* calloc failed: heap exhaustion */ + DEBUG_ERROR("calloc: failed in %s\n", __func__); + return; + } + + spi_parameters_s spi_parameters; + if (!sfdp_read_parameters(target, &spi_parameters, esp32c3_spi_read_sfdp)) { + /* SFDP readout failed, so read the JTAG ID of the device next */ + spi_flash_id_s flash_id; + esp32c3_spi_read(target, SPI_FLASH_CMD_READ_JEDEC_ID, 0, &flash_id, sizeof(flash_id)); + const uint32_t capacity = 1U << flash_id.capacity; + + /* Now make some assumptions and hope for the best. */ + spi_parameters.page_size = 256U; + spi_parameters.sector_size = 4096U; + spi_parameters.capacity = MIN(capacity, ESP32_C3_IBUS_FLASH_SIZE); + spi_parameters.sector_erase_opcode = SPI_FLASH_OPCODE_SECTOR_ERASE; + } + + target_flash_s *const flash = &spi_flash->flash; + flash->start = ESP32_C3_IBUS_FLASH_BASE; + flash->length = MIN(spi_parameters.capacity, ESP32_C3_IBUS_FLASH_SIZE); + flash->blocksize = spi_parameters.sector_size; + flash->write = esp32c3_spi_flash_write; + flash->erase = esp32c3_spi_flash_erase; + flash->erased = 0xffU; + target_add_flash(target, flash); + + spi_flash->page_size = spi_parameters.page_size; + spi_flash->sector_erase_opcode = spi_parameters.sector_erase_opcode; +} + +bool esp32c3_probe(target_s *const target) +{ + const riscv_hart_s *const hart = riscv_hart_struct(target); + /* Seems that the best we can do is check the marchid and mimplid register values */ + if (hart->archid != ESP32_C3_ARCH_ID || hart->implid != ESP32_C3_IMPL_ID) + return false; + + esp32c3_priv_s *const priv = calloc(1, sizeof(esp32c3_priv_s)); + if (!priv) { /* calloc failed: heap exhaustion */ + DEBUG_ERROR("calloc: failed in %s\n", __func__); + return false; + } + target->target_storage = priv; + target->driver = "ESP32-C3"; + esp32c3_disable_wdts(target); + + /* We have to provide our own halt/resume functions to take care of the WDTs as they cause Problems */ + target->halt_request = esp32c3_halt_request; + target->halt_resume = esp32c3_halt_resume; + target->halt_poll = esp32c3_halt_poll; + /* Provide an implementation of the mass erase command */ + target->mass_erase = esp32c3_mass_erase; + /* Special care must be taken during Flash programming */ + target->enter_flash_mode = esp32c3_enter_flash_mode; + target->exit_flash_mode = esp32c3_exit_flash_mode; + + /* Establish the target RAM mappings */ + target_add_ram(target, ESP32_C3_IBUS_SRAM0_BASE, ESP32_C3_IBUS_SRAM0_SIZE); + target_add_ram(target, ESP32_C3_IBUS_SRAM1_BASE, ESP32_C3_IBUS_SRAM1_SIZE); + target_add_ram(target, ESP32_C3_DBUS_SRAM1_BASE, ESP32_C3_DBUS_SRAM1_SIZE); + target_add_ram(target, ESP32_C3_RTC_SRAM_BASE, ESP32_C3_RTC_SRAM_SIZE); + + /* Establish the target Flash mappings */ + esp32c3_add_flash(target); + return true; +} + +static void esp32c3_disable_wdts(target_s *const target) +{ + esp32c3_priv_s *const priv = (esp32c3_priv_s *)target->target_storage; + /* Disable Timer Group 0's WDT */ + target_mem_write32(target, ESP32_C3_TIMG0_WDT_WRITE_PROT, ESP32_C3_WDT_WRITE_PROT_KEY); + priv->wdt_config[0] = target_mem_read32(target, ESP32_C3_TIMG0_WDT_CONFIG0); + target_mem_write32(target, ESP32_C3_TIMG0_WDT_CONFIG0, 0U); + /* Disable Timer Group 1's WDT */ + target_mem_write32(target, ESP32_C3_TIMG1_WDT_WRITE_PROT, ESP32_C3_WDT_WRITE_PROT_KEY); + priv->wdt_config[1] = target_mem_read32(target, ESP32_C3_TIMG1_WDT_CONFIG0); + target_mem_write32(target, ESP32_C3_TIMG1_WDT_CONFIG0, 0U); + /* Disable the RTC WDT */ + target_mem_write32(target, ESP32_C3_RTC_WDT_WRITE_PROT, ESP32_C3_WDT_WRITE_PROT_KEY); + priv->wdt_config[2] = target_mem_read32(target, ESP32_C3_RTC_WDT_CONFIG0); + target_mem_write32(target, ESP32_C3_RTC_WDT_CONFIG0, 0U); + /* Disable the "super" WDT */ + target_mem_write32(target, ESP32_C3_RTC_SWD_WRITE_PROT, ESP32_C3_RTC_SWD_WRITE_PROT_KEY); + priv->wdt_config[3] = target_mem_read32(target, ESP32_C3_RTC_SWD_CONFIG); + target_mem_write32(target, ESP32_C3_RTC_SWD_CONFIG, ESP32_C3_RTC_SWD_CONFIG_DISABLE); +} + +static void esp32c3_restore_wdts(target_s *const target) +{ + esp32c3_priv_s *const priv = (esp32c3_priv_s *)target->target_storage; + /* Restore Timger Group 0's WDT */ + target_mem_write32(target, ESP32_C3_TIMG0_WDT_CONFIG0, priv->wdt_config[0]); + target_mem_write32(target, ESP32_C3_TIMG0_WDT_WRITE_PROT, 0U); + /* Restore Timger Group 1's WDT */ + target_mem_write32(target, ESP32_C3_TIMG1_WDT_CONFIG0, priv->wdt_config[1]); + target_mem_write32(target, ESP32_C3_TIMG1_WDT_WRITE_PROT, 0U); + /* Restore the RTC WDT */ + target_mem_write32(target, ESP32_C3_RTC_WDT_CONFIG0, priv->wdt_config[2]); + target_mem_write32(target, ESP32_C3_RTC_WDT_WRITE_PROT, 0U); + /* Restore the "super" WDT */ + target_mem_write32(target, ESP32_C3_RTC_SWD_CONFIG, priv->wdt_config[2]); + target_mem_write32(target, ESP32_C3_RTC_SWD_WRITE_PROT, 0U); +} + +static void esp32c3_halt_request(target_s *const target) +{ + riscv_halt_request(target); + esp32c3_disable_wdts(target); +} + +static void esp32c3_halt_resume(target_s *const target, const bool step) +{ + if (!step) + esp32c3_restore_wdts(target); + riscv_halt_resume(target, step); +} + +static target_halt_reason_e esp32c3_halt_poll(target_s *const target, target_addr_t *const watch) +{ + const target_halt_reason_e reason = riscv_halt_poll(target, watch); + if (reason == TARGET_HALT_BREAKPOINT) + esp32c3_disable_wdts(target); + return reason; +} + +static uint32_t esp32c3_spi_config( + target_s *const target, const uint32_t command, const target_addr_t address, size_t length) +{ + uint32_t enabled_stages = ESP32_C3_SPI_USER0_CMD; + uint32_t user1_value = 0; + + /* Set up the command phase */ + const uint8_t spi_command = command & ESP32_C3_SPI_FLASH_OPCODE_MASK; + target_mem_write32(target, ESP32_C3_SPI1_USER2, ESP32_C3_SPI_USER2_CMD_LEN(8) | spi_command); + + /* Configure the address to send */ + if ((command & ESP32_C3_SPI_FLASH_OPCODE_MODE_MASK) == ESP32_C3_SPI_FLASH_OPCODE_3B_ADDR) { + enabled_stages |= ESP32_C3_SPI_USER0_ADDR; + target_mem_write32(target, ESP32_C3_SPI1_ADDR, address); + user1_value |= ESP32_C3_SPI_USER1_ADDR_LEN(24U); + } + + /* Configure the number of dummy cycles required */ + if (command & ESP32_C3_SPI_FLASH_DUMMY_MASK) { + enabled_stages |= ESP32_C3_SPI_USER0_DUMMY; + uint8_t dummy_cycles = (command & ESP32_C3_SPI_FLASH_DUMMY_MASK) >> ESP32_C3_SPI_FLASH_DUMMY_SHIFT; + user1_value |= ESP32_C3_SPI_USER1_DUMMY_LEN(dummy_cycles); + } + + /* Configure the data phase */ + if (length) { + if (command & ESP32_C3_SPI_FLASH_DATA_OUT) + enabled_stages |= ESP32_C3_SPI_USER0_DATA_OUT; + else + enabled_stages |= ESP32_C3_SPI_USER0_DATA_IN; + } + + /* Now we've defined all the information needed for user0 and user1, send it */ + target_mem_write32(target, ESP32_C3_SPI1_USER1, user1_value); + return enabled_stages; +} + +static void esp32c3_spi_wait_complete(target_s *const target) +{ + /* Now trigger the configured transaction */ + target_mem_write32(target, ESP32_C3_SPI1_CMD, ESP32_C3_SPI_CMD_EXEC_XFER); + /* And wait for the transaction to complete */ + while (target_mem_read32(target, ESP32_C3_SPI1_CMD) & ESP32_C3_SPI_CMD_EXEC_XFER) + continue; +} + +static void esp32c3_spi_read( + target_s *const target, const uint32_t command, const target_addr_t address, void *const buffer, size_t length) +{ + /* Start by setting up the common components of the transaction */ + const uint32_t enabled_stages = esp32c3_spi_config(target, command, address, length); + uint8_t *const data = (uint8_t *)buffer; + const uint32_t misc_reg = target_mem_read32(target, ESP32_C3_SPI1_MISC) & ~ESP32_C3_SPI_MISC_CS_HOLD; + /* + * The transfer has to proceed in no more than 64 bytes at a time because that's + * how many data registers are available in the SPI peripheral + */ + for (size_t offset = 0U; offset < length; offset += 64U) { + const uint32_t amount = MIN(length - offset, 64U); + /* Tell the controller how many bytes we want received in this transaction */ + target_mem_write32(target, ESP32_C3_SPI1_DATA_IN_LEN, ESP32_C3_SPI_DATA_BIT_LEN(amount)); + /* Configure which transaction stages to use */ + if (offset) + target_mem_write32(target, ESP32_C3_SPI1_USER0, ESP32_C3_SPI_USER0_DATA_IN); + else + target_mem_write32(target, ESP32_C3_SPI1_USER0, enabled_stages); + + /* On the final transfer, clear the chip select hold bit, otherwise set it */ + if (length - offset == amount) + target_mem_write32(target, ESP32_C3_SPI1_MISC, misc_reg); + else + target_mem_write32(target, ESP32_C3_SPI1_MISC, misc_reg | ESP32_C3_SPI_MISC_CS_HOLD); + + /* Run the transaction */ + esp32c3_spi_wait_complete(target); + + /* Extract and unpack the received data */ + target_mem_read(target, data + offset, ESP32_C3_SPI1_DATA, amount); + } +} + +static void esp32c3_spi_write(target_s *const target, const uint32_t command, const target_addr_t address, + const void *const buffer, const size_t length) +{ + /* Start by setting up the common components of the transaction */ + const uint32_t enabled_stages = esp32c3_spi_config(target, command, address, length); + const uint8_t *const data = (const uint8_t *)buffer; + const uint32_t misc_reg = target_mem_read32(target, ESP32_C3_SPI1_MISC) & ~ESP32_C3_SPI_MISC_CS_HOLD; + + /* + * The transfer has to proceed in no more than 64 bytes at a time because that's + * how many data registers are available in the SPI peripheral + */ + for (size_t offset = 0U; offset < length; offset += 64U) { + const uint32_t amount = MIN(length - offset, 64U); + /* Tell the controller how many bytes we want sent in this transaction */ + target_mem_write32(target, ESP32_C3_SPI1_DATA_OUT_LEN, ESP32_C3_SPI_DATA_BIT_LEN(amount)); + /* Configure which transaction stages to use */ + if (offset) + target_mem_write32(target, ESP32_C3_SPI1_USER0, ESP32_C3_SPI_USER0_DATA_OUT); + else + target_mem_write32(target, ESP32_C3_SPI1_USER0, enabled_stages); + + /* On the final transfer, clear the chip select hold bit, otherwise set it */ + if (length - offset == amount) + target_mem_write32(target, ESP32_C3_SPI1_MISC, misc_reg); + else + target_mem_write32(target, ESP32_C3_SPI1_MISC, misc_reg | ESP32_C3_SPI_MISC_CS_HOLD); + + /* Pack and stage the data to transmit */ + target_mem_write(target, ESP32_C3_SPI1_DATA, data + offset, amount); + + /* Run the transaction */ + esp32c3_spi_wait_complete(target); + } +} + +static inline uint8_t esp32c3_spi_read_status(target_s *const target) +{ + uint8_t status = 0; + esp32c3_spi_read(target, SPI_FLASH_CMD_READ_STATUS, 0, &status, sizeof(status)); + return status; +} + +static inline void esp32c3_spi_run_command(target_s *const target, const uint32_t command, const target_addr_t address) +{ + /* Start by setting up the common components of the transaction */ + const uint32_t enabled_stages = esp32c3_spi_config(target, command, address, 0U); + /* Write the stages to execute and run the transaction */ + target_mem_write32(target, ESP32_C3_SPI1_USER0, enabled_stages); + esp32c3_spi_wait_complete(target); +} + +static bool esp32c3_mass_erase(target_s *const target, platform_timeout_s *const print_progess) +{ + esp32c3_spi_run_command(target, SPI_FLASH_CMD_WRITE_ENABLE, 0U); + if (!(esp32c3_spi_read_status(target) & SPI_FLASH_STATUS_WRITE_ENABLED)) + return false; + + esp32c3_spi_run_command(target, SPI_FLASH_CMD_CHIP_ERASE, 0U); + while (esp32c3_spi_read_status(target) & SPI_FLASH_STATUS_BUSY) + target_print_progress(print_progess); + + return true; +} + +static bool esp32c3_enter_flash_mode(target_s *const target) +{ + esp32c3_disable_wdts(target); + return true; +} + +static bool esp32c3_exit_flash_mode(target_s *const target) +{ + esp32c3_priv_s *const priv = (esp32c3_priv_s *)target->target_storage; + /* Calculate the length of the region to invalidate and reload */ + const uint32_t region_length = priv->last_invalidated_sector - ESP32_C3_IBUS_FLASH_BASE; + /* Invalidate the i-cache for the required length */ + target_mem_write32(target, ESP32_C3_EXTMEM_ICACHE_SYNC_ADDR, ESP32_C3_IBUS_FLASH_BASE); + target_mem_write32(target, ESP32_C3_EXTMEM_ICACHE_SYNC_SIZE, region_length); + target_mem_write32(target, ESP32_C3_EXTMEM_ICACHE_SYNC_CTRL, ESP32_C3_EXTMEM_ICACHE_INVALIDATE); + /* Wait for invalidation to complete */ + while (!(target_mem_read32(target, ESP32_C3_EXTMEM_ICACHE_SYNC_CTRL) & ESP32_C3_EXTMEM_ICACHE_SYNC_DONE)) + continue; + /* Now preload the cache with the new data */ + target_mem_write32(target, ESP32_C3_EXTMEM_ICACHE_PRELOAD_ADDR, ESP32_C3_IBUS_FLASH_BASE); + target_mem_write32(target, ESP32_C3_EXTMEM_ICACHE_PRELOAD_SIZE, region_length); + target_mem_write32(target, ESP32_C3_EXTMEM_ICACHE_PRELOAD_CTRL, ESP32_C3_EXTMEM_ICACHE_PRELOAD); + /* Wait for preload to complete */ + while (!(target_mem_read32(target, ESP32_C3_EXTMEM_ICACHE_PRELOAD_CTRL) & ESP32_C3_EXTMEM_ICACHE_PRELOAD_DONE)) + continue; + target_reset(target); + return true; +} + +static bool esp32c3_spi_flash_erase(target_flash_s *const flash, const target_addr_t addr, const size_t length) +{ + target_s *const target = flash->t; + const esp32c3_spi_flash_s *const spi_flash = (esp32c3_spi_flash_s *)flash; + const target_addr_t begin = addr - flash->start; + for (size_t offset = 0; offset < length; offset += flash->blocksize) { + esp32c3_spi_run_command(target, SPI_FLASH_CMD_WRITE_ENABLE, 0U); + if (!(esp32c3_spi_read_status(target) & SPI_FLASH_STATUS_WRITE_ENABLED)) + return false; + + esp32c3_spi_run_command(target, + SPI_FLASH_CMD_SECTOR_ERASE | ESP32_C3_SPI_FLASH_OPCODE(spi_flash->sector_erase_opcode), begin + offset); + while (esp32c3_spi_read_status(target) & SPI_FLASH_STATUS_BUSY) + continue; + } + /* Update the address of the last invalidated sector so we can correctly invalidate the i-cache and reload it */ + esp32c3_priv_s *const priv = (esp32c3_priv_s *)target->target_storage; + priv->last_invalidated_sector = addr + length; + return true; +} + +static bool esp32c3_spi_flash_write( + target_flash_s *const flash, const target_addr_t dest, const void *const src, const size_t length) +{ + target_s *const target = flash->t; + // const esp32c3_spi_flash_s *const spi_flash = (esp32c3_spi_flash_s *)flash; + const target_addr_t begin = dest - flash->start; + const char *const buffer = (const char *)src; + for (size_t offset = 0; offset < length; offset += 64U) { + esp32c3_spi_run_command(target, SPI_FLASH_CMD_WRITE_ENABLE, 0U); + if (!(esp32c3_spi_read_status(target) & SPI_FLASH_STATUS_WRITE_ENABLED)) + return false; + + const size_t amount = MIN(length - offset, 64U); + esp32c3_spi_write(target, SPI_FLASH_CMD_PAGE_PROGRAM, begin + offset, buffer + offset, amount); + while (esp32c3_spi_read_status(target) & SPI_FLASH_STATUS_BUSY) + continue; + } + return true; +} diff --git a/src/target/hc32l110.c b/src/target/hc32l110.c index fa3364c0334..490952a3a83 100644 --- a/src/target/hc32l110.c +++ b/src/target/hc32l110.c @@ -78,7 +78,7 @@ static bool hc32l110_flash_prepare(target_flash_s *flash); static bool hc32l110_flash_done(target_flash_s *flash); static bool hc32l110_flash_erase(target_flash_s *flash, target_addr_t addr, size_t length); static bool hc32l110_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t length); -static bool hc32l110_mass_erase(target_s *target); +static bool hc32l110_mass_erase(target_s *target, platform_timeout_s *print_progess); static void hc32l110_add_flash(target_s *target, const uint32_t flash_size) { @@ -130,7 +130,8 @@ static void hc32l110_flash_cr_unlock(target_s *const target) target_mem_write32(target, HC32L110_FLASH_BYPASS, 0xa5a5U); } -static bool hc32l110_check_flash_completion(target_s *const target, const uint32_t timeout_ms) +static bool hc32l110_check_flash_completion( + target_s *const target, const uint32_t timeout_ms, platform_timeout_s *const print_progess) { platform_timeout_s timeout; platform_timeout_set(&timeout, timeout_ms); @@ -139,6 +140,8 @@ static bool hc32l110_check_flash_completion(target_s *const target, const uint32 status = target_mem_read32(target, HC32L110_FLASH_CR); if (target_check_error(target) || platform_timeout_is_expired(&timeout)) return false; + if (print_progess) + target_print_progress(print_progess); } return true; } @@ -181,11 +184,17 @@ static bool hc32l110_flash_prepare(target_flash_s *const flash) case FLASH_OPERATION_ERASE: target_mem_write32(flash->t, HC32L110_FLASH_CR, HC32L110_FLASH_CR_OP_ERASE_SECTOR); break; + case FLASH_OPERATION_MASS_ERASE: + target_mem_write32(flash->t, HC32L110_FLASH_CR, HC32L110_FLASH_CR_OP_ERASE_CHIP); + break; default: DEBUG_WARN("unsupported operation %u", flash->operation); return false; } + if (!hc32l110_check_flash_completion(flash->t, 500U, NULL)) + return false; + hc32l110_slock_unlock_all(flash->t); return true; } @@ -200,8 +209,8 @@ static bool hc32l110_flash_erase(target_flash_s *const flash, const target_addr_ { (void)length; /* The Flash controller automatically erases the whole sector after one write operation */ - target_mem_write32(flash->t, addr, 0); - return hc32l110_check_flash_completion(flash->t, 1000); + target_mem_write32(flash->t, addr, 0U); + return hc32l110_check_flash_completion(flash->t, 1000U, NULL); } static bool hc32l110_flash_write( @@ -209,24 +218,12 @@ static bool hc32l110_flash_write( { (void)length; target_mem_write32(flash->t, dest, *(const uint32_t *)src); - return hc32l110_check_flash_completion(flash->t, 1000); + return hc32l110_check_flash_completion(flash->t, 1000U, NULL); } -static bool hc32l110_mass_erase(target_s *target) +static bool hc32l110_mass_erase(target_s *target, platform_timeout_s *const print_progess) { - hc32l110_enter_flash_mode(target); - - hc32l110_flash_cr_unlock(target); - target_mem_write32(target, HC32L110_FLASH_CR, HC32L110_FLASH_CR_OP_ERASE_CHIP); - if (!hc32l110_check_flash_completion(target, 500)) - return false; - - hc32l110_slock_unlock_all(target); - // The Flash controller automatically erases the whole Flash after one write operation - target_mem_write32(target, 0, 0); - const bool result = hc32l110_check_flash_completion(target, 4000); - - hc32l110_slock_lock_all(target); - return result; + target_mem_write32(target, 0x0U, 0U); + return hc32l110_check_flash_completion(target, 4000U, print_progess); } diff --git a/src/target/imxrt.c b/src/target/imxrt.c index eeba517c0fa..5b819655833 100644 --- a/src/target/imxrt.c +++ b/src/target/imxrt.c @@ -253,7 +253,6 @@ bool imxrt_probe(target_s *const target) spi_flash_id_s flash_id; imxrt_spi_read(target, SPI_FLASH_CMD_READ_JEDEC_ID, 0, &flash_id, sizeof(flash_id)); - target->mass_erase = bmp_spi_mass_erase; target->enter_flash_mode = imxrt_enter_flash_mode; target->exit_flash_mode = imxrt_exit_flash_mode; diff --git a/src/target/jep106.h b/src/target/jep106.h index 83317b9f9dd..8c349308eb5 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 */ @@ -68,6 +81,17 @@ #define JEP106_MANUFACTURER_RASPBERRY 0x913U /* Raspberry Pi */ #define JEP106_MANUFACTURER_RENESAS 0x423U /* Renesas */ #define JEP106_MANUFACTURER_XILINX 0x309U /* Xilinx */ +#define JEP106_MANUFACTURER_ESPRESSIF 0xc12U /* Espressif */ + +/* 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. + */ +#define JEP106_MANUFACTURER_RV_GIGADEVICE 0x61eU /* * This code is not listed in the JEP106 standard, but is used by some stm32f1 clones diff --git a/src/target/jtag_devs.c b/src/target/jtag_devs.c index a395ff35c75..7b38ad390fc 100644 --- a/src/target/jtag_devs.c +++ b/src/target/jtag_devs.c @@ -22,6 +22,7 @@ #include "general.h" #include "jtag_scan.h" #include "adiv5.h" +#include "riscv_debug.h" #include "jtag_devs.h" const jtag_dev_descr_s dev_descr[] = { @@ -119,9 +120,21 @@ const jtag_dev_descr_s dev_descr[] = { }, #ifdef ENABLE_DEBUG { - .idcode = 0x0000063dU, - .idmask = 0x00000fffU, - .descr = "Xambala: RVDBG013.", + .idcode = 0x0000563dU, + .idmask = 0x0fffffffU, + .descr = "RISC-V debug v0.13.", + .handler = riscv_jtag_dtm_handler, + }, + { + .idcode = 0x00005c25U, + .idmask = 0x0fffffffU, + .descr = "RISC-V debug v0.13.", + .handler = riscv_jtag_dtm_handler, + .ir_quirks = + { + .ir_length = 5, + .ir_value = 0x0005U, + }, }, { .idcode = 0x000007a3U, diff --git a/src/target/kinetis.c b/src/target/kinetis.c index 1e5c5853772..8c6bae94883 100644 --- a/src/target/kinetis.c +++ b/src/target/kinetis.c @@ -516,7 +516,7 @@ static bool kinetis_flash_done(target_flash_s *const f) * device. This provides a fake target to allow a monitor command interface */ -static bool kinetis_mdm_mass_erase(target_s *t); +static bool kinetis_mdm_mass_erase(target_s *t, platform_timeout_s *const print_progess); static bool kinetis_mdm_cmd_ke04_mode(target_s *t, int argc, const char **argv); const command_s kinetis_mdm_cmd_list[] = { @@ -570,52 +570,50 @@ static bool kinetis_mdm_cmd_ke04_mode(target_s *t, int argc, const char **argv) return true; } -static bool kinetis_mdm_mass_erase(target_s *t) +static bool kinetis_mdm_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { - adiv5_access_port_s *ap = t->priv; + adiv5_access_port_s *ap = target->priv; /* Keep the MCU in reset as stated in KL25PxxM48SF0RM */ - if (t->ke04_mode) - adiv5_ap_write(ap, MDM_CONTROL, MDM_CONTROL_SYS_RESET); + if (target->ke04_mode) + adiv5_ap_write(ap, MDM_CONTROL, MDM_CONTROL_SYS_RESET); /* FIXME: move this to enter_flash_mode? */ uint32_t status = adiv5_ap_read(ap, MDM_STATUS); uint32_t control = adiv5_ap_read(ap, MDM_CONTROL); - tc_printf(t, "Requesting mass erase (status = 0x%" PRIx32 ")\n", status); + tc_printf(target, "Requesting mass erase (status = 0x%" PRIx32 ")\n", status); /* This flag does not exist on KE04 */ - if (!(status & MDM_STATUS_MASS_ERASE_ENABLED) && !t->ke04_mode) { - tc_printf(t, "ERROR: Mass erase disabled!\n"); + if (!(status & MDM_STATUS_MASS_ERASE_ENABLED) && !target->ke04_mode) { + tc_printf(target, "ERROR: Mass erase disabled!\n"); return false; } /* Flag is not persistent */ - t->ke04_mode = false; + target->ke04_mode = false; if (!(status & MDM_STATUS_FLASH_READY)) { - tc_printf(t, "ERROR: Flash not ready!\n"); + tc_printf(target, "ERROR: Flash not ready!\n"); return false; } if (status & MDM_STATUS_MASS_ERASE_ACK) { - tc_printf(t, "ERROR: Mass erase already in progress!\n"); + tc_printf(target, "ERROR: Mass erase already in progress!\n"); return false; } adiv5_ap_write(ap, MDM_CONTROL, MDM_CONTROL_MASS_ERASE); - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); do { status = adiv5_ap_read(ap, MDM_STATUS); - target_print_progress(&timeout); + target_print_progress(print_progess); } while (!(status & MDM_STATUS_MASS_ERASE_ACK)); - tc_printf(t, "Mass erase acknowledged\n"); + tc_printf(target, "Mass erase acknowledged\n"); do { control = adiv5_ap_read(ap, MDM_CONTROL); - target_print_progress(&timeout); + target_print_progress(print_progess); } while (!(control & MDM_CONTROL_MASS_ERASE)); - tc_printf(t, "Mass erase complete\n"); + tc_printf(target, "Mass erase complete\n"); return true; } diff --git a/src/target/lmi.c b/src/target/lmi.c index d54265be761..ed27284237b 100644 --- a/src/target/lmi.c +++ b/src/target/lmi.c @@ -64,7 +64,6 @@ static bool lmi_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); static bool lmi_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len); -static bool lmi_mass_erase(target_s *t); static const char lmi_driver_str[] = "TI Stellaris/Tiva"; @@ -107,7 +106,6 @@ bool lm3s_probe(target_s *const t, const uint16_t did1) t->driver = driver; return false; } - t->mass_erase = lmi_mass_erase; return true; } @@ -138,7 +136,6 @@ bool tm4c_probe(target_s *const t, const uint16_t did1) t->driver = driver; return false; } - t->mass_erase = lmi_mass_erase; return true; } @@ -196,8 +193,3 @@ static bool lmi_flash_write(target_flash_s *f, target_addr_t dest, const void *s return cortexm_run_stub(t, SRAM_BASE, dest, STUB_BUFFER_BASE, len, 0) == 0; } - -static bool lmi_mass_erase(target_s *t) -{ - return lmi_flash_erase(t->flash, t->flash->start, t->flash->length); -} diff --git a/src/target/lpc17xx.c b/src/target/lpc17xx.c index 95dd381089c..5dc661bf2a7 100644 --- a/src/target/lpc17xx.c +++ b/src/target/lpc17xx.c @@ -65,8 +65,9 @@ typedef struct lpc17xx_priv { static void lpc17xx_extended_reset(target_s *target); static bool lpc17xx_enter_flash_mode(target_s *target); static bool lpc17xx_exit_flash_mode(target_s *target); -static bool lpc17xx_mass_erase(target_s *target); -iap_status_e lpc17xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e cmd, ...); +static bool lpc17xx_mass_erase(target_s *target, platform_timeout_s *print_progess); +iap_status_e lpc17xx_iap_call( + target_s *target, iap_result_s *result, platform_timeout_s *print_progess, iap_cmd_e cmd, ...); static void lpc17xx_add_flash(target_s *target, uint32_t addr, size_t len, size_t erasesize, uint8_t base_sector) { @@ -103,7 +104,7 @@ bool lpc17xx_probe(target_s *target) lpc17xx_enter_flash_mode(target); /* Read the Part ID */ iap_result_s result; - lpc17xx_iap_call(target, &result, IAP_CMD_PARTID); + lpc17xx_iap_call(target, &result, NULL, IAP_CMD_PARTID); /* Transition back to normal mode and resume the target */ lpc17xx_exit_flash_mode(target); target_halt_resume(target, false); @@ -168,31 +169,28 @@ static bool lpc17xx_exit_flash_mode(target_s *const target) return true; } -static bool lpc17xx_mass_erase(target_s *target) +static bool lpc17xx_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { iap_result_s result; - lpc17xx_enter_flash_mode(target); - if (lpc17xx_iap_call(target, &result, IAP_CMD_PREPARE, 0, FLASH_NUM_SECTOR - 1U)) { + if (lpc17xx_iap_call(target, &result, print_progess, IAP_CMD_PREPARE, 0, FLASH_NUM_SECTOR - 1U)) { lpc17xx_exit_flash_mode(target); DEBUG_ERROR("lpc17xx_cmd_erase: prepare failed %" PRIu32 "\n", result.return_code); return false; } - if (lpc17xx_iap_call(target, &result, IAP_CMD_ERASE, 0, FLASH_NUM_SECTOR - 1U, CPU_CLK_KHZ)) { + if (lpc17xx_iap_call(target, &result, print_progess, IAP_CMD_ERASE, 0, FLASH_NUM_SECTOR - 1U, CPU_CLK_KHZ)) { lpc17xx_exit_flash_mode(target); DEBUG_ERROR("lpc17xx_cmd_erase: erase failed %" PRIu32 "\n", result.return_code); return false; } - if (lpc17xx_iap_call(target, &result, IAP_CMD_BLANKCHECK, 0, FLASH_NUM_SECTOR - 1U)) { + if (lpc17xx_iap_call(target, &result, print_progess, IAP_CMD_BLANKCHECK, 0, FLASH_NUM_SECTOR - 1U)) { lpc17xx_exit_flash_mode(target); DEBUG_ERROR("lpc17xx_cmd_erase: blankcheck failed %" PRIu32 "\n", result.return_code); return false; } - lpc17xx_exit_flash_mode(target); - tc_printf(target, "Erase OK.\n"); return true; } @@ -223,7 +221,8 @@ static size_t lpc17xx_iap_params(const iap_cmd_e cmd) } } -iap_status_e lpc17xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e cmd, ...) +iap_status_e lpc17xx_iap_call(target_s *const target, iap_result_s *const result, + platform_timeout_s *const print_progess, const iap_cmd_e cmd, ...) { /* Set up our IAP frame with the break opcode and command to run */ iap_frame_s frame = { @@ -238,7 +237,7 @@ iap_status_e lpc17xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e for (size_t i = 0; i < params_count; ++i) frame.config.params[i] = va_arg(params, uint32_t); va_end(params); - for (size_t i = params_count; i < 4; ++i) + for (size_t i = params_count; i < 4U; ++i) frame.config.params[i] = 0U; /* Copy the structure to RAM */ @@ -251,7 +250,7 @@ iap_status_e lpc17xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e /* Point r0 to the start of the config block */ regs[0] = iap_params_addr; /* And r1 to the same so we re-use the same memory for the results */ - regs[1] = iap_params_addr; + regs[1U] = iap_params_addr; /* Set the top of stack to the top of the RAM block we're using */ regs[CORTEX_REG_MSP] = IAP_RAM_BASE + MIN_RAM_SIZE; /* Point the return address to our breakpoint opcode (thumb mode) */ @@ -261,12 +260,12 @@ iap_status_e lpc17xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e target_regs_write(target, regs); platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); + platform_timeout_set(&timeout, 500U); /* Start the target and wait for it to halt again */ target_halt_resume(target, false); while (!target_halt_poll(target, NULL)) { - if (cmd == IAP_CMD_ERASE) - target_print_progress(&timeout); + if (print_progess) + target_print_progress(print_progess); else if (cmd == IAP_CMD_PARTID && platform_timeout_is_expired(&timeout)) { target_halt_request(target); return IAP_STATUS_INVALID_COMMAND; diff --git a/src/target/lpc40xx.c b/src/target/lpc40xx.c index 82ffa9c4a99..725e47cdc82 100644 --- a/src/target/lpc40xx.c +++ b/src/target/lpc40xx.c @@ -67,8 +67,9 @@ typedef struct lpc40xx_priv { static void lpc40xx_extended_reset(target_s *target); static bool lpc40xx_enter_flash_mode(target_s *target); static bool lpc40xx_exit_flash_mode(target_s *target); -static bool lpc40xx_mass_erase(target_s *target); -iap_status_e lpc40xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e cmd, ...); +static bool lpc40xx_mass_erase(target_s *target, platform_timeout_s *print_progess); +iap_status_e lpc40xx_iap_call( + target_s *target, iap_result_s *result, platform_timeout_s *print_progess, iap_cmd_e cmd, ...); static void lpc40xx_add_flash(target_s *target, uint32_t addr, size_t len, size_t erasesize, uint8_t base_sector) { @@ -106,7 +107,7 @@ bool lpc40xx_probe(target_s *target) lpc40xx_enter_flash_mode(target); /* Read the Part ID */ iap_result_s result; - lpc40xx_iap_call(target, &result, IAP_CMD_PARTID); + lpc40xx_iap_call(target, &result, NULL, IAP_CMD_PARTID); /* Transition back to normal mode and resume the target */ lpc40xx_exit_flash_mode(target); target_halt_resume(target, false); @@ -162,31 +163,28 @@ static bool lpc40xx_exit_flash_mode(target_s *const target) return true; } -static bool lpc40xx_mass_erase(target_s *target) +static bool lpc40xx_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { iap_result_s result; - lpc40xx_enter_flash_mode(target); - if (lpc40xx_iap_call(target, &result, IAP_CMD_PREPARE, 0, FLASH_NUM_SECTOR - 1U)) { + if (lpc40xx_iap_call(target, &result, print_progess, IAP_CMD_PREPARE, 0, FLASH_NUM_SECTOR - 1U)) { lpc40xx_exit_flash_mode(target); DEBUG_ERROR("lpc40xx_cmd_erase: prepare failed %" PRIu32 "\n", result.return_code); return false; } - if (lpc40xx_iap_call(target, &result, IAP_CMD_ERASE, 0, FLASH_NUM_SECTOR - 1U, CPU_CLK_KHZ)) { + if (lpc40xx_iap_call(target, &result, print_progess, IAP_CMD_ERASE, 0, FLASH_NUM_SECTOR - 1U, CPU_CLK_KHZ)) { lpc40xx_exit_flash_mode(target); DEBUG_ERROR("lpc40xx_cmd_erase: erase failed %" PRIu32 "\n", result.return_code); return false; } - if (lpc40xx_iap_call(target, &result, IAP_CMD_BLANKCHECK, 0, FLASH_NUM_SECTOR - 1U)) { + if (lpc40xx_iap_call(target, &result, print_progess, IAP_CMD_BLANKCHECK, 0, FLASH_NUM_SECTOR - 1U)) { lpc40xx_exit_flash_mode(target); DEBUG_ERROR("lpc40xx_cmd_erase: blankcheck failed %" PRIu32 "\n", result.return_code); return false; } - lpc40xx_exit_flash_mode(target); - tc_printf(target, "Erase OK.\n"); return true; } @@ -217,7 +215,8 @@ static size_t lpc40xx_iap_params(const iap_cmd_e cmd) } } -iap_status_e lpc40xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e cmd, ...) +iap_status_e lpc40xx_iap_call(target_s *const target, iap_result_s *const result, + platform_timeout_s *const print_progess, const iap_cmd_e cmd, ...) { /* Set up our IAP frame with the break opcode and command to run */ iap_frame_s frame = { @@ -232,7 +231,7 @@ iap_status_e lpc40xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e for (size_t i = 0; i < params_count; ++i) frame.config.params[i] = va_arg(params, uint32_t); va_end(params); - for (size_t i = params_count; i < 4; ++i) + for (size_t i = params_count; i < 4U; ++i) frame.config.params[i] = 0U; /* Copy the structure to RAM */ @@ -245,22 +244,22 @@ iap_status_e lpc40xx_iap_call(target_s *target, iap_result_s *result, iap_cmd_e /* Point r0 to the start of the config block */ regs[0] = iap_params_addr; /* And r1 to the same so we re-use the same memory for the results */ - regs[1] = iap_params_addr; + regs[1U] = iap_params_addr; /* Set the top of stack to the top of the RAM block we're using */ regs[CORTEX_REG_MSP] = IAP_RAM_BASE + MIN_RAM_SIZE; /* Point the return address to our breakpoint opcode (thumb mode) */ - regs[CORTEX_REG_LR] = IAP_RAM_BASE | 1; + regs[CORTEX_REG_LR] = IAP_RAM_BASE | 1U; /* And set the program counter to the IAP ROM entrypoint */ regs[CORTEX_REG_PC] = IAP_ENTRYPOINT; target_regs_write(target, regs); platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); + platform_timeout_set(&timeout, 500U); /* Start the target and wait for it to halt again */ target_halt_resume(target, false); while (!target_halt_poll(target, NULL)) { - if (cmd == IAP_CMD_ERASE) - target_print_progress(&timeout); + if (print_progess) + target_print_progress(print_progess); else if (cmd == IAP_CMD_PARTID && platform_timeout_is_expired(&timeout)) { target_halt_request(target); return IAP_STATUS_INVALID_COMMAND; diff --git a/src/target/lpc43xx.c b/src/target/lpc43xx.c index fc38f80f551..36472f83d9e 100644 --- a/src/target/lpc43xx.c +++ b/src/target/lpc43xx.c @@ -285,7 +285,7 @@ static bool lpc43xx_iap_init(target_flash_s *flash); static lpc43xx_partid_s lpc43xx_iap_read_partid(target_s *t); static bool lpc43xx_enter_flash_mode(target_s *t); static bool lpc43xx_iap_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); -static bool lpc43xx_iap_mass_erase(target_s *t); +static bool lpc43xx_iap_mass_erase(target_s *target, platform_timeout_s *print_progess); static void lpc43xx_wdt_set_period(target_s *t); static void lpc43xx_wdt_kick(target_s *t); @@ -504,7 +504,6 @@ bool lpc43xx_probe(target_s *const t) if (part_id.part == LPC43xx_PARTID_INVALID) return false; - t->mass_erase = bmp_spi_mass_erase; t->enter_flash_mode = lpc43x0_enter_flash_mode; t->exit_flash_mode = lpc43x0_exit_flash_mode; lpc43x0_detect(t, part_id); @@ -1000,19 +999,19 @@ static bool lpc43xx_iap_flash_erase(target_flash_s *f, const target_addr_t addr, return lpc_flash_erase(f, addr, len); } -static bool lpc43xx_iap_mass_erase(target_s *t) +static bool lpc43xx_iap_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { - lpc43xx_priv_s *const priv = (lpc43xx_priv_s *)t->target_storage; - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); - lpc43xx_iap_init(t->flash); + lpc43xx_priv_s *const priv = (lpc43xx_priv_s *)target->target_storage; + + lpc43xx_iap_init(target->flash); + /* FIXME: since this is looking like bank mass erases, maybe this should be in flash->mass_erase */ for (size_t bank = 0; bank < priv->flash_banks; ++bank) { - lpc_flash_s *const f = (lpc_flash_s *)t->flash; - if (lpc_iap_call(f, NULL, IAP_CMD_PREPARE, 0, FLASH_NUM_SECTOR - 1U, bank) || - lpc_iap_call(f, NULL, IAP_CMD_ERASE, 0, FLASH_NUM_SECTOR - 1U, CPU_CLK_KHZ, bank)) + lpc_flash_s *const flash = (lpc_flash_s *)target->flash; + if (lpc_iap_call(flash, NULL, IAP_CMD_PREPARE, 0, FLASH_NUM_SECTOR - 1U, bank) || + lpc_iap_call(flash, NULL, IAP_CMD_ERASE, 0, FLASH_NUM_SECTOR - 1U, CPU_CLK_KHZ, bank)) return false; - target_print_progress(&timeout); + target_print_progress(print_progess); } return true; diff --git a/src/target/lpc546xx.c b/src/target/lpc546xx.c index 694aebe9c50..98aa99f79c9 100644 --- a/src/target/lpc546xx.c +++ b/src/target/lpc546xx.c @@ -61,7 +61,6 @@ static bool lpc546xx_cmd_write_sector(target_s *t, int argc, const char **argv); static void lpc546xx_reset_attach(target_s *t); static bool lpc546xx_flash_init(target_s *t); static bool lpc546xx_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); -static bool lpc546xx_mass_erase(target_s *t); static void lpc546xx_wdt_set_period(target_s *t); static void lpc546xx_wdt_kick(target_s *t); @@ -152,7 +151,6 @@ bool lpc546xx_probe(target_s *t) */ sram123_size = device->sram123_kbytes * 1024U; - t->mass_erase = lpc546xx_mass_erase; lpc546xx_add_flash(t, IAP_ENTRYPOINT_LOCATION, 0, 0x0, flash_size, 0x8000); /* @@ -182,20 +180,13 @@ static void lpc546xx_reset_attach(target_s *t) cortexm_attach(t); } -static bool lpc546xx_mass_erase(target_s *t) -{ - const int result = lpc546xx_flash_erase(t->flash, t->flash->start, t->flash->length); - if (result != 0) - tc_printf(t, "Error erasing flash: %d\n", result); - return result == 0; -} - -static bool lpc546xx_cmd_erase_sector(target_s *t, int argc, const char **argv) +static bool lpc546xx_cmd_erase_sector(target_s *const target, int argc, const char **argv) { + /* FIXME: is this command really needed? feels like it should at best be a generic target erase */ if (argc > 1) { uint32_t sector_addr = strtoul(argv[1], NULL, 0); - sector_addr *= t->flash->blocksize; - return lpc546xx_flash_erase(t->flash, sector_addr, 1U); + sector_addr *= target->flash->blocksize; + return target_flash_erase(target, sector_addr, 1U); } return true; } diff --git a/src/target/lpc55xx.c b/src/target/lpc55xx.c index 69f9d8a18ca..19d154c4d75 100644 --- a/src/target/lpc55xx.c +++ b/src/target/lpc55xx.c @@ -519,7 +519,7 @@ static const command_s lpc55xx_cmd_list[] = { }; static bool lpc55_dmap_cmd(adiv5_access_port_s *ap, uint32_t cmd); -static bool lpc55_dmap_mass_erase(target_s *target); +static bool lpc55_dmap_mass_erase(target_s *target, platform_timeout_s *print_progess); static void lpc55_dmap_ap_free(void *priv); void lpc55_dp_prepare(adiv5_debug_port_s *const dp) @@ -656,8 +656,9 @@ static bool lpc55_dmap_cmd(adiv5_access_port_s *const ap, const uint32_t cmd) } } -static bool lpc55_dmap_mass_erase(target_s *target) +static bool lpc55_dmap_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { + (void)print_progess; /* * TODO: This doesn't actually work at least on the LPC550x, there seems to be * a lot more to figure out about the debug mailbox before this code can work. diff --git a/src/target/msp432e4.c b/src/target/msp432e4.c index 619da8dd736..e43053eddd1 100644 --- a/src/target/msp432e4.c +++ b/src/target/msp432e4.c @@ -185,7 +185,7 @@ typedef struct msp432e4_flash { static bool msp432e4_flash_erase(target_flash_s *flash, target_addr_t addr, size_t length); static bool msp432e4_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t length); -static bool msp432e4_mass_erase(target_s *target); +static bool msp432e4_mass_erase(target_s *target, platform_timeout_s *print_progess); static void msp432e4_add_flash( target_s *const target, const uint32_t sector_size, const uint32_t base, const size_t length) @@ -298,15 +298,13 @@ static bool msp432e4_flash_write( } /* Mass erases the Flash */ -static bool msp432e4_mass_erase(target_s *const target) +static bool msp432e4_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { const msp432e4_flash_s *const flash = (msp432e4_flash_s *)target->flash; - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); /* Kick off the mass erase */ target_mem_write32(target, MSP432E4_FLASH_CTRL, (flash->flash_key << 16U) | MSP432E4_FLASH_CTRL_MASS_ERASE); /* Wait for the erase to complete, printing a '.' every so often to keep GDB happy */ while (target_mem_read32(target, MSP432E4_FLASH_CTRL) & MSP432E4_FLASH_CTRL_MASS_ERASE) - target_print_progress(&timeout); + target_print_progress(print_progess); return true; } diff --git a/src/target/nrf51.c b/src/target/nrf51.c index c010eeeec2b..fe3eea51929 100644 --- a/src/target/nrf51.c +++ b/src/target/nrf51.c @@ -27,11 +27,11 @@ #include "cortexm.h" #include "adiv5.h" -static bool nrf51_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); -static bool nrf51_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len); -static bool nrf51_flash_prepare(target_flash_s *f); -static bool nrf51_flash_done(target_flash_s *f); -static bool nrf51_mass_erase(target_s *t); +static bool nrf51_flash_erase(target_flash_s *flash, target_addr_t addr, size_t len); +static bool nrf51_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len); +static bool nrf51_flash_prepare(target_flash_s *flash); +static bool nrf51_flash_done(target_flash_s *flash); +static bool nrf51_mass_erase(target_s *target, platform_timeout_s *print_progess); static bool nrf51_cmd_erase_uicr(target_s *t, int argc, const char **argv); static bool nrf51_cmd_protect_flash(target_s *t, int argc, const char **argv); @@ -164,81 +164,85 @@ bool nrf51_probe(target_s *t) return true; } -static bool nrf51_wait_ready(target_s *const t, platform_timeout_s *const timeout) +static bool nrf51_wait_ready(target_s *const t, platform_timeout_s *const print_progress) { /* Poll for NVMC_READY */ while (target_mem_read32(t, NRF51_NVMC_READY) == 0) { if (target_check_error(t)) return false; - if (timeout) - target_print_progress(timeout); + if (print_progress) + target_print_progress(print_progress); } return true; } -static bool nrf51_flash_prepare(target_flash_s *f) +static bool nrf51_flash_prepare(target_flash_s *const flash) { - target_s *t = f->t; - /* If there is a buffer allocated, we're in the Flash write phase, otherwise it's erase */ - if (f->buf) + target_s *const target = flash->t; + + switch (flash->operation) { + case FLASH_OPERATION_WRITE: /* Enable write */ - target_mem_write32(t, NRF51_NVMC_CONFIG, NRF51_NVMC_CONFIG_WEN); - else + target_mem_write32(target, NRF51_NVMC_CONFIG, NRF51_NVMC_CONFIG_WEN); + break; + case FLASH_OPERATION_ERASE: /* Enable erase */ - target_mem_write32(t, NRF51_NVMC_CONFIG, NRF51_NVMC_CONFIG_EEN); - return nrf51_wait_ready(t, NULL); + target_mem_write32(target, NRF51_NVMC_CONFIG, NRF51_NVMC_CONFIG_EEN); + break; + default: + return false; /* Unsupported operation */ + } + + return nrf51_wait_ready(target, NULL); } -static bool nrf51_flash_done(target_flash_s *f) +static bool nrf51_flash_done(target_flash_s *const flash) { - target_s *t = f->t; + target_s *const target = flash->t; /* Return to read-only */ - target_mem_write32(t, NRF51_NVMC_CONFIG, NRF51_NVMC_CONFIG_REN); - return nrf51_wait_ready(t, NULL); + target_mem_write32(target, NRF51_NVMC_CONFIG, NRF51_NVMC_CONFIG_REN); + return nrf51_wait_ready(target, NULL); } -static bool nrf51_flash_erase(target_flash_s *f, target_addr_t addr, size_t len) +static bool nrf51_flash_erase(target_flash_s *const flash, const target_addr_t addr, const size_t len) { - target_s *t = f->t; + target_s *const target = flash->t; - for (size_t offset = 0; offset < len; offset += f->blocksize) { + for (size_t offset = 0; offset < len; offset += flash->blocksize) { /* If the address to erase is the UICR, we have to handle that separately */ if (addr + offset == NRF51_UICR) /* Write to the ERASE_UICR register to erase */ - target_mem_write32(t, NRF51_NVMC_ERASEUICR, 0x1U); + target_mem_write32(target, NRF51_NVMC_ERASEUICR, 0x1U); else /* Write address of first word in page to erase it */ - target_mem_write32(t, NRF51_NVMC_ERASEPAGE, addr + offset); + target_mem_write32(target, NRF51_NVMC_ERASEPAGE, addr + offset); - if (!nrf51_wait_ready(t, NULL)) + if (!nrf51_wait_ready(target, NULL)) return false; } return true; } -static bool nrf51_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len) +static bool nrf51_flash_write( + target_flash_s *const flash, const target_addr_t dest, const void *const src, const size_t len) { /* nrf51_flash_prepare() and nrf51_flash_done() top-and-tail this, just write the data to the target. */ - target_s *t = f->t; - target_mem_write(t, dest, src, len); - return nrf51_wait_ready(t, NULL); + target_s *const target = flash->t; + target_mem_write(target, dest, src, len); + return nrf51_wait_ready(target, NULL); } -static bool nrf51_mass_erase(target_s *t) +static bool nrf51_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { - target_reset(t); - /* Enable erase */ - target_mem_write32(t, NRF51_NVMC_CONFIG, NRF51_NVMC_CONFIG_EEN); - if (!nrf51_wait_ready(t, NULL)) + target_mem_write32(target, NRF51_NVMC_CONFIG, NRF51_NVMC_CONFIG_EEN); + if (!nrf51_wait_ready(target, NULL)) return false; - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500U); /* Erase all */ - target_mem_write32(t, NRF51_NVMC_ERASEALL, 1U); - return nrf51_wait_ready(t, &timeout); + target_mem_write32(target, NRF51_NVMC_ERASEALL, 1U); + return nrf51_wait_ready(target, print_progess); } static bool nrf51_cmd_erase_uicr(target_s *t, int argc, const char **argv) @@ -399,7 +403,7 @@ static bool nrf51_cmd_read(target_s *t, int argc, const char **argv) #define NRF52_MDM_IDR 0x02880000U -static bool nrf51_mdm_mass_erase(target_s *t); +static bool nrf51_mdm_mass_erase(target_s *target, platform_timeout_s *print_progess); #define MDM_POWER_EN ADIV5_DP_REG(0x01U) #define MDM_SELECT_AP ADIV5_DP_REG(0x02U) @@ -436,21 +440,19 @@ bool nrf51_mdm_probe(adiv5_access_port_s *ap) return true; } -static bool nrf51_mdm_mass_erase(target_s *t) +static bool nrf51_mdm_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { - adiv5_access_port_s *ap = t->priv; + adiv5_access_port_s *const ap = cortex_ap(target); uint32_t status = adiv5_ap_read(ap, MDM_STATUS); adiv5_dp_write(ap->dp, MDM_POWER_EN, 0x50000000U); adiv5_dp_write(ap->dp, MDM_SELECT_AP, 0x01000000U); adiv5_ap_write(ap, MDM_CONTROL, 0x00000001U); - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500U); // Read until 0, probably should have a timeout here... do { status = adiv5_ap_read(ap, MDM_STATUS); - target_print_progress(&timeout); + target_print_progress(print_progess); } while (status); // The second read will provide true prot status diff --git a/src/target/nxpke04.c b/src/target/nxpke04.c index f23e5bd2819..a78e1acb30a 100644 --- a/src/target/nxpke04.c +++ b/src/target/nxpke04.c @@ -118,7 +118,7 @@ static bool ke04_command(target_s *t, uint8_t cmd, uint32_t addr, const void *da static bool ke04_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); static bool ke04_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len); static bool ke04_flash_done(target_flash_s *f); -static bool ke04_mass_erase(target_s *t); +static bool ke04_mass_erase(target_s *target, platform_timeout_s *print_progess); /* Target specific commands */ static bool kinetis_cmd_unsafe(target_s *t, int argc, const char **argv); @@ -239,12 +239,15 @@ bool ke04_probe(target_s *t) return true; } -static bool ke04_mass_erase(target_s *t) +static bool ke04_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { + (void)print_progess; + /* FIXME: looks like this might belong to flash->mass erase */ + /* Erase and verify the whole flash */ - ke04_command(t, CMD_ERASE_ALL_BLOCKS, 0, NULL); + ke04_command(target, CMD_ERASE_ALL_BLOCKS, 0, NULL); /* Adjust security byte if needed */ - ke04_flash_done(t->flash); + ke04_flash_done(target->flash); return true; } diff --git a/src/target/riscv32.c b/src/target/riscv32.c new file mode 100644 index 00000000000..23b08ef4992 --- /dev/null +++ b/src/target/riscv32.c @@ -0,0 +1,607 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rachel Mant + * 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. + */ + +#include "general.h" +#include "target.h" +#include "target_internal.h" +#include "target_probe.h" +#include "jep106.h" +#include "riscv_debug.h" +#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 */ +#define RV32_MATCH_DMODE_DEBUG 0x08000000U +/* Match when the processor tries to execute the location */ +#define RV32_MATCH_EXECUTE 0x00000004U +/* Match when the processor tries to read the location */ +#define RV32_MATCH_READ 0x00000001U +/* Match when the processor tries to write the location */ +#define RV32_MATCH_WRITE 0x00000002U +/* Define that the match should occur in all/any mode */ +#define RV32_MATCH_ANY_MODE 0x00000058U +/* Set the match action to raise a breakpoint exception */ +#define RV32_MATCH_ACTION_EXCEPTION 0x00000000U +/* Set the match action to enter debug mode */ +#define RV32_MATCH_ACTION_DEBUG_MODE 0x00001000U +/* These two define whether the match should be performed on the address, or specific data */ +#define RV32_MATCH_ADDR 0x00000000U +#define RV32_MATCH_DATA 0x00080000U +/* These two define the match timing (before-or-after operation execution) */ +#define RV32_MATCH_BEFORE 0x00000000U +#define RV32_MATCH_AFTER 0x00040000U + +static ssize_t riscv32_reg_read(target_s *target, uint32_t c, void *data, size_t max); +static ssize_t riscv32_reg_write(target_s *target, uint32_t c, 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_addr_t src, size_t len); +static void riscv32_mem_write(target_s *target, target_addr_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) +{ + /* 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; + target->reg_read = riscv32_reg_read; + target->mem_read = riscv32_mem_read; + target->mem_write = riscv32_mem_write; + + target->breakwatch_set = riscv32_breakwatch_set; + target->breakwatch_clear = riscv32_breakwatch_clear; + + switch (target->designer_code) { + case JEP106_MANUFACTURER_RV_GIGADEVICE: + PROBE(gd32vf1_probe); + break; + case JEP106_MANUFACTURER_ESPRESSIF: + PROBE(esp32c3_probe); + break; + case NOT_JEP106_MANUFACTURER_WCH: + PROBE(ch32vx_probe); + break; + } +#if PC_HOSTED == 0 + gdb_outf("Please report unknown device with Designer 0x%x\n", target->designer_code); +#else + DEBUG_WARN("Please report unknown device with Designer 0x%x\n", target->designer_code); +#endif +#undef PROBE + return false; +} + +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; + 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]); + } + /* Special access to grab the program counter that would be executed on resuming the hart */ + riscv_csr_read(hart, RV_DPC, ®s->pc); +} + +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); + riscv32_regs_s *const regs = (riscv32_regs_s *)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]); + } + /* Special access to poke in the program counter that will be executed on resuming the hart */ + riscv_csr_write(hart, RV_DPC, ®s->pc); +} + +static inline ssize_t riscv32_bool_to_4(const bool ret) +{ + return ret ? 4 : -1; +} + +static ssize_t riscv32_reg_read(target_s *target, const uint32_t reg, void *data, const size_t max) +{ + if (max != 4) + return -1; + /* Grab the hart structure */ + riscv_hart_s *const hart = riscv_hart_struct(target); + if (reg < 32) + return riscv32_bool_to_4(riscv_csr_read(hart, RV_GPR_BASE + reg, data)); + if (reg == 32) + return riscv32_bool_to_4(riscv_csr_read(hart, RV_DPC, data)); + if (reg >= RV_CSR_GDB_OFFSET) + return riscv32_bool_to_4(riscv_csr_read(hart, reg - RV_CSR_GDB_OFFSET, data)); + return -1; +} + +static ssize_t riscv32_reg_write(target_s *const target, const uint32_t reg, const void *data, const size_t max) +{ + if (max != 4) + return -1; + /* Grab the hart structure */ + riscv_hart_s *const hart = riscv_hart_struct(target); + if (reg < 32) + return riscv32_bool_to_4(riscv_csr_write(hart, RV_GPR_BASE + reg, data)); + if (reg == 32) + return riscv32_bool_to_4(riscv_csr_write(hart, RV_DPC, data)); + if (reg >= RV_CSR_GDB_OFFSET) + return riscv32_bool_to_4(riscv_csr_write(hart, reg - RV_CSR_GDB_OFFSET, data)); + return -1; +} + +/* Takes in data from abstract command arg0 and, based on the access width, unpacks it to dest */ +void riscv32_unpack_data(void *const dest, const uint32_t data, const uint8_t access_width) +{ + switch (access_width) { + case RV_MEM_ACCESS_8_BIT: { + const uint8_t value = data & 0xffU; + memcpy(dest, &value, sizeof(value)); + break; + } + case RV_MEM_ACCESS_16_BIT: { + const uint16_t value = data & 0xffffU; + memcpy(dest, &value, sizeof(value)); + break; + } + case RV_MEM_ACCESS_32_BIT: + memcpy(dest, &data, sizeof(data)); + break; + } +} + +/* Takes in data from src, based on the access width, to be written to abstract command arg0 and packs it */ +uint32_t riscv32_pack_data(const void *const src, const uint8_t access_width) +{ + switch (access_width) { + case RV_MEM_ACCESS_8_BIT: { + uint8_t value = 0; + memcpy(&value, src, sizeof(value)); + return value; + } + case RV_MEM_ACCESS_16_BIT: { + uint16_t value = 0; + memcpy(&value, src, sizeof(value)); + return value; + } + case RV_MEM_ACCESS_32_BIT: { + uint32_t value = 0; + memcpy(&value, src, sizeof(value)); + return value; + } + } + return 0; +} + +static void riscv32_abstract_mem_read( + riscv_hart_s *const hart, void *const dest, const target_addr_t src, const size_t len) +{ + /* Figure out the maxmial 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); + } +} + +static void riscv32_abstract_mem_write( + riscv_hart_s *const hart, const target_addr_t dest, const void *const src, const size_t len) +{ + /* Figure out the maxmial 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 riscv_sysbus_check(riscv_hart_s *const hart) +{ + uint32_t status = 0; + /* Read back the system bus status */ + if (!riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, &status)) + return; + /* Store the result and reset the value in the control/status register */ + hart->status = (status >> 12U) & RISCV_HART_OTHER; + if (!riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, RISCV_HART_OTHER << 12U)) + return; + /* If something goes wrong, tell the user */ + if (hart->status != RISCV_HART_NO_ERROR) + DEBUG_WARN("memory access failed: %u\n", hart->status); +} + +static void riscv32_sysbus_mem_native_read(riscv_hart_s *const hart, void *const dest, const target_addr_t src, + const size_t len, const uint8_t access_width, const uint8_t access_length) +{ + DEBUG_TARGET("%s: %zu byte read at %08" PRIx32 " in %u byte blocks\n", __func__, len, src, access_length); + /* Build the access command */ + const uint32_t command = ((uint32_t)access_width << RV_SYSBUS_MEM_ACCESS_SHIFT) | RV_SYSBUS_MEM_READ_ON_ADDR | + (access_length < len ? RV_SYSBUS_MEM_ADDR_POST_INC | RV_SYSBUS_MEM_READ_ON_DATA : 0U); + /* + * Write the command setup to the access control register + * Then set up the read by writing the address to the address register + */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, command) || + !riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_ADDR0, src)) + return; + uint8_t *const data = (uint8_t *)dest; + for (size_t offset = 0; offset < len; offset += access_length) { + uint32_t status = RV_SYSBUS_STATUS_BUSY; + /* Wait for the current read cycle to complete */ + while (status & RV_SYSBUS_STATUS_BUSY) { + if (!riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, &status)) + return; + } + /* If this would be the last read, clean up the access control register */ + if (offset + access_length == len && (command & RV_SYSBUS_MEM_ADDR_POST_INC)) { + if (!riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, 0)) + return; + } + uint32_t value = 0; + /* Read back and unpack the data for this block */ + if (!riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_DATA0, &value)) + return; + riscv32_unpack_data(data + offset, value, access_width); + } + riscv_sysbus_check(hart); +} + +static void riscv32_sysbus_mem_adjusted_read(riscv_hart_s *const hart, void *const dest, const target_addr_t src, + const uint8_t access_width, const uint8_t native_access_width, const uint8_t access_length) +{ + DEBUG_PROBE("%s: %u read for %u bytes\n", __func__, access_length, 1U << access_width); + const target_addr_t alignment = ~(access_length - 1U); + /* + * On a 32-bit target the only possible widths are 8- 16- and 32-bit, so after the adjustment loop, + * there are only and exactly 2 possible cases to handle here: 16- and 32-bit access. + */ + switch (native_access_width) { + case RV_MEM_ACCESS_16_BIT: { + uint16_t value = 0; + riscv32_sysbus_mem_native_read( + hart, &value, src & alignment, access_length, RV_MEM_ACCESS_16_BIT, access_length); + adiv5_unpack_data(dest, src & 1U, value, access_width); + break; + } + case RV_MEM_ACCESS_32_BIT: { + uint32_t value = 0; + riscv32_sysbus_mem_native_read( + hart, &value, src & alignment, access_length, RV_MEM_ACCESS_32_BIT, access_length); + adiv5_unpack_data(dest, src & 3U, value, access_width); + break; + } + } +} + +static void riscv32_sysbus_mem_read( + riscv_hart_s *const hart, void *const dest, const target_addr_t src, const size_t len) +{ + /* Figure out the maxmial 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 = (uint8_t)(1U << access_width); + /* Check if the access is a natural/native width */ + if (hart->flags & access_length) { + riscv32_sysbus_mem_native_read(hart, dest, src, len, access_width, access_length); + return; + } + + /* If we were unable to do this using a native access, find the next largest supported access width */ + uint8_t native_access_width = access_width; + while (!((hart->flags >> native_access_width) & 1U) && native_access_width < RV_MEM_ACCESS_32_BIT) + ++native_access_width; + const uint8_t native_access_length = (uint8_t)(1U << native_access_width); + + /* Do the initial adjusted access */ + size_t remainder = len; + target_addr_t address = src; + uint8_t *data = (uint8_t *)dest; + riscv32_sysbus_mem_adjusted_read(hart, data, address, access_width, native_access_width, native_access_length); + + /* After doing the initial access, adjust the location of the next and do any follow-up accesses required */ + remainder -= access_length; + address += access_length; + data += access_length; + + /* + * Now we're aligned to the wider access width, do another set of reads if there's + * any remainder. Do this till we either reach nothing left, or we have another small left-over amount + */ + if (!remainder) + return; + const size_t amount = remainder & ~(native_access_length - 1U); + if (amount) + riscv32_sysbus_mem_native_read(hart, data, address, amount, native_access_width, native_access_length); + remainder -= amount; + address += (uint32_t)amount; + data += amount; + + /* If there's any data left to read, do another adjusted access to grab it */ + if (remainder) + riscv32_sysbus_mem_adjusted_read(hart, data, address, access_width, native_access_width, native_access_length); +} + +static void riscv32_sysbus_mem_native_write(riscv_hart_s *const hart, const target_addr_t dest, const void *const src, + const size_t len, const uint8_t access_width, const uint8_t access_length) +{ + DEBUG_TARGET("%s: %zu byte write at %08" PRIx32 " in %u byte blocks\n", __func__, len, dest, access_length); + /* Build the access command */ + const uint32_t command = ((uint32_t)access_width << RV_SYSBUS_MEM_ACCESS_SHIFT) | + (access_length < len ? RV_SYSBUS_MEM_ADDR_POST_INC : 0U); + /* + * Write the command setup to the access control register + * Then set up the write by writing the address to the address register + */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, command) || + !riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_ADDR0, dest)) + return; + const uint8_t *const data = (const uint8_t *)src; + for (size_t offset = 0; offset < len; offset += access_length) { + /* Pack the data for this block and write it */ + const uint32_t value = riscv32_pack_data(data + offset, access_width); + if (!riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_DATA0, value)) + return; + + uint32_t status = RV_SYSBUS_STATUS_BUSY; + /* Wait for the current write cycle to complete */ + while (status & RV_SYSBUS_STATUS_BUSY) { + if (!riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, &status)) + return; + } + } + riscv_sysbus_check(hart); +} + +static void riscv32_sysbus_mem_adjusted_write(riscv_hart_s *const hart, const target_addr_t dest, const void *const src, + const uint8_t access_width, const uint8_t native_access_width, const uint8_t access_length) +{ + DEBUG_PROBE("%s: %u write for %u bytes\n", __func__, access_length, 1U << access_width); + const target_addr_t alignment = ~(access_length - 1U); + /* + * On a 32-bit target the only possible widths are 8- 16- and 32-bit, so after the adjustment loop, + * there are only and exactly 2 possible cases to handle here: 16- and 32-bit access. + * The basic premise here is that we have to read to correctly write - to do a N bit write with a + * wider access primitive, we first have to read back what's at the target aligned location, replace + * the correct set of bits in the target value, then write the new combined value back + */ + switch (native_access_width) { + case RV_MEM_ACCESS_16_BIT: { + uint16_t value = 0; + /* Start by reading 16 bits */ + riscv32_sysbus_mem_native_read( + hart, &value, dest & alignment, access_length, RV_MEM_ACCESS_16_BIT, access_length); + /* Now replace the part to write (must be done on the widened version of the value) */ + uint32_t widened_value = value; + adiv5_pack_data(dest & 1U, src, &widened_value, access_width); + value = (uint16_t)widened_value; + /* And finally write the new value back */ + riscv32_sysbus_mem_native_write( + hart, dest & alignment, &value, access_length, RV_MEM_ACCESS_16_BIT, access_length); + break; + } + case RV_MEM_ACCESS_32_BIT: { + uint32_t value = 0; + /* Start by reading 32 bits */ + riscv32_sysbus_mem_native_read( + hart, &value, dest & alignment, access_length, RV_MEM_ACCESS_32_BIT, access_length); + /* Now replace the part to write */ + adiv5_pack_data(dest & 3U, src, &value, access_width); + /* And finally write the new value back */ + riscv32_sysbus_mem_native_write( + hart, dest & alignment, &value, access_length, RV_MEM_ACCESS_32_BIT, access_length); + break; + } + } +} + +static void riscv32_sysbus_mem_write( + riscv_hart_s *const hart, const target_addr_t dest, const void *const src, const size_t len) +{ + /* Figure out the maxmial 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; + /* Check if the access is a natural/native width */ + if (hart->flags & access_length) { + riscv32_sysbus_mem_native_write(hart, dest, src, len, access_width, access_length); + return; + } + + /* If we were unable to do this using a native access, find the next largest supported access width */ + uint8_t native_access_width = access_width; + while (!((hart->flags >> native_access_width) & 1U) && native_access_width < RV_MEM_ACCESS_32_BIT) + ++native_access_width; + const uint8_t native_access_length = (uint8_t)(1U << native_access_width); + + /* Do the initial adjusted access */ + size_t remainder = len; + target_addr_t address = dest; + const uint8_t *data = (const uint8_t *)src; + riscv32_sysbus_mem_adjusted_write(hart, address, data, access_width, native_access_width, native_access_length); + + /* After doing the initial access, adjust the location of the next and do any follow-up accesses required */ + remainder -= access_length; + address += access_length; + data += access_length; + + /* + * Now we're aligned to the wider access width, do another set of writes if there's + * any remainder. Do this till we either reach nothing left, or we have another small left-over amount + */ + if (!remainder) + return; + const size_t amount = remainder & ~(native_access_length - 1U); + if (amount) + riscv32_sysbus_mem_native_write(hart, address, data, amount, native_access_width, native_access_length); + remainder -= amount; + address += (uint32_t)amount; + data += amount; + + /* If there's any data left to write, do another adjusted access to perform it */ + if (remainder) + riscv32_sysbus_mem_adjusted_write(hart, address, data, access_width, native_access_width, native_access_length); +} + +static void riscv32_mem_read(target_s *const target, void *const dest, const target_addr_t src, const size_t len) +{ + DEBUG_TARGET("Performing %zu byte read of %08" PRIx32 "\n", len, src); + /* If we're asked to do a 0-byte read, do nothing */ + if (!len) + return; + + riscv_hart_s *const hart = riscv_hart_struct(target); + if (hart->flags & RV_HART_FLAG_MEMORY_SYSBUS) + return riscv32_sysbus_mem_read(hart, dest, src, len); + return riscv32_abstract_mem_read(hart, dest, src, len); +} + +static void riscv32_mem_write(target_s *const target, const target_addr_t dest, const void *const src, const size_t len) +{ + DEBUG_TARGET("Performing %zu byte write of %08" PRIx32 "\n", len, dest); + /* If we're asked to do a 0-byte read, do nothing */ + if (!len) + return; + + riscv_hart_s *const hart = riscv_hart_struct(target); + if (hart->flags & RV_HART_FLAG_MEMORY_SYSBUS) + return riscv32_sysbus_mem_write(hart, dest, src, len); + return riscv32_abstract_mem_write(hart, dest, src, len); +} + +/* + * The following can be used as a key for understanding the various return results from the breakwatch functions: + * 0 -> success + * 1 -> not supported + * -1 -> an error occured + */ + +static int riscv32_breakwatch_set(target_s *const target, breakwatch_s *const breakwatch) +{ + riscv_hart_s *const hart = riscv_hart_struct(target); + size_t trigger = 0; + /* Find the first unused trigger slot */ + for (; trigger < hart->triggers; ++trigger) { + const uint32_t trigger_use = hart->trigger_uses[trigger]; + /* Make sure it's unused and that it supports breakwatch mode */ + if ((trigger_use & RV_TRIGGER_MODE_MASK) == RISCV_TRIGGER_MODE_UNUSED && + (trigger_use & RV_TRIGGER_SUPPORT_BREAKWATCH)) + break; + } + /* If none was available, return an error */ + if (trigger == hart->triggers) + return -1; + + /* Build the mcontrol config for the requested breakwatch type */ + uint32_t config = RV32_MATCH_ADDR_DATA_TRIGGER | RV32_MATCH_DMODE_DEBUG | RV32_MATCH_ANY_MODE | + RV32_MATCH_ACTION_DEBUG_MODE | RV32_MATCH_ADDR | riscv_breakwatch_match_size(breakwatch->size); + // RV32_MATCH_DATA (bit 19) + riscv_trigger_state_e mode = RISCV_TRIGGER_MODE_WATCHPOINT; + switch (breakwatch->type) { + case TARGET_BREAK_HARD: + config |= RV32_MATCH_EXECUTE | RV32_MATCH_BEFORE; + mode = RISCV_TRIGGER_MODE_BREAKPOINT; + break; + case TARGET_WATCH_READ: + config |= RV32_MATCH_READ | RV32_MATCH_AFTER; + break; + case TARGET_WATCH_WRITE: + config |= RV32_MATCH_WRITE | RV32_MATCH_BEFORE; + break; + case TARGET_WATCH_ACCESS: + config |= RV32_MATCH_READ | RV32_MATCH_WRITE | RV32_MATCH_AFTER; + break; + default: + /* If the breakwatch type is not one of the above, tell the debugger we don't support it */ + return 1; + } + /* Grab the address to set the breakwatch on and configure the hardware */ + const uint32_t address = breakwatch->addr; + const bool result = riscv_config_trigger(hart, trigger, mode, &config, &address); + /* If configuration succeeds, store the trigger index in the breakwatch structure */ + if (result) + breakwatch->reserved[0] = trigger; + /* Return based on whether setting up the hardware worked or not */ + return result ? 0 : -1; +} + +static int riscv32_breakwatch_clear(target_s *const target, breakwatch_s *const breakwatch) +{ + riscv_hart_s *const hart = riscv_hart_struct(target); + const uint32_t config = RV32_MATCH_ADDR_DATA_TRIGGER; + const uint32_t address = 0; + return riscv_config_trigger(hart, breakwatch->reserved[0], RISCV_TRIGGER_MODE_UNUSED, &config, &address) ? 0 : -1; +} diff --git a/src/target/riscv64.c b/src/target/riscv64.c new file mode 100644 index 00000000000..2e25bd17b42 --- /dev/null +++ b/src/target/riscv64.c @@ -0,0 +1,142 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rachel Mant + * 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. + */ + +#include "general.h" +#include "target.h" +#include "target_internal.h" +#include "target_probe.h" +#include "riscv_debug.h" + +typedef struct riscv64_regs { + uint64_t gprs[32]; + uint64_t pc; +} riscv64_regs_s; + +static void riscv64_regs_read(target_s *target, void *data); +static void riscv64_regs_write(target_s *target, const void *data); +static void riscv64_mem_read(target_s *target, void *dest, target_addr_t src, size_t len); + +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_read = riscv64_regs_read; + target->regs_write = riscv64_regs_write; + target->mem_read = riscv64_mem_read; + + return false; +} + +static void riscv64_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); + riscv64_regs_s *const regs = (riscv64_regs_s *)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]); + } + /* Special access to grab the program counter that would be executed on resuming the hart */ + riscv_csr_read(hart, RV_DPC, ®s->pc); +} + +static void riscv64_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); + riscv64_regs_s *const regs = (riscv64_regs_s *)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]); + } + /* Special access to poke in the program counter that will be executed on resuming the hart */ + riscv_csr_write(hart, RV_DPC, ®s->pc); +} + +/* Takes in data from abstract command arg0 and, based on the access width, unpacks it to dest */ +void riscv64_unpack_data( + void *const dest, const uint32_t data_low, const uint32_t data_high, const uint8_t access_width) +{ + switch (access_width) { + /* If the access was 32-bit or less, ignore data_high and delegate to the RV32 version of this function */ + case RV_MEM_ACCESS_8_BIT: + case RV_MEM_ACCESS_16_BIT: + case RV_MEM_ACCESS_32_BIT: + riscv32_unpack_data(dest, data_low, access_width); + break; + case RV_MEM_ACCESS_64_BIT: { + /* Reconstruct the 64-bit value and copy it into the destination */ + uint64_t value = ((uint64_t)data_high << 32U) | data_low; + memcpy(dest, &value, sizeof(value)); + break; + } + } +} + +/* XXX: target_addr_t supports only 32-bit addresses, artificially limiting this function */ +static void riscv64_mem_read(target_s *const target, void *const dest, const target_addr_t src, const size_t len) +{ + DEBUG_TARGET("Performing %zu byte read of %08" PRIx32 "\n", len, src); + /* If we're asked to do a 0-byte read, do nothing */ + 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 */ + 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, src) || !riscv_dm_write(hart->dbg_module, RV_DM_DATA3, 0U)) + 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_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)) + 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 new file mode 100644 index 00000000000..c6c0688824c --- /dev/null +++ b/src/target/riscv_debug.c @@ -0,0 +1,1034 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rachel Mant + * 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. + */ + +#include "general.h" +#include "target_probe.h" +#include "target_internal.h" +#include "gdb_reg.h" +#include "riscv_debug.h" + +#include + +/* + * Links to exact specifications used in this code are listed here for ease: + * riscv-debug-spec v0.11 as used by SiFive: + * https://static.dev.sifive.com/riscv-debug-spec-0.11nov12.pdf + * riscv-debug-spec v0.13.2 (release): + * https://raw.githubusercontent.com/riscv/riscv-debug-spec/v0.13-release/riscv-debug-release.pdf + * riscv-debug-spec v1.0 (stable): + * 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_CTRL_ACTIVE 0x00000001U +#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_HALT_REQ 0x80000000U +#define RV_DM_CTRL_RESUME_REQ 0x40000000U +#define RV_DM_CTRL_HART_RESET 0x20000000U +#define RV_DM_CTRL_HART_ACK_RESET 0x10000000U +#define RV_DM_CTRL_SYSTEM_RESET 0x00000002U +#define RV_DM_CTRL_HARTSELLO_SHIFT 16U +#define RV_DM_CTRL_HARTSELHI_SHIFT 4U + +#define RV_DM_STAT_ALL_RESUME_ACK 0x00020000U +#define RV_DM_STAT_NON_EXISTENT 0x00004000U +#define RV_DM_STAT_ALL_HALTED 0x00000200U +#define RV_DM_STAT_ALL_RESET 0x00080000U + +#define RV_DM_ABST_STATUS_BUSY 0x00001000U +#define RV_DM_ABST_STATUS_DATA_COUNT 0x0000000fU + +#define RV_DM_SYSBUS_STATUS_ADDR_WIDTH_MASK 0x00000fe0U + +#define RV_CSR_FORCE_MASK 0xc000U +#define RV_CSR_FORCE_32_BIT 0x4000U +#define RV_CSR_FORCE_64_BIT 0x8000U + +/* The following is a set of CSR address definitions */ +/* misa -> The Hart's machine ISA register */ +#define RV_ISA 0x301U +/* dcsr -> Debug Control/Status Register */ +#define RV_DCSR 0x7b0U +/* mvendorid -> The JEP-106 code for the vendor implementing this Hart */ +#define RV_VENDOR_ID 0xf11U +/* marchid -> The RISC-V International architecture ID code */ +#define RV_ARCH_ID 0xf12U +/* mimplid -> Hart's processor implementation ID */ +#define RV_IMPL_ID 0xf13U +/* mhartid -> machine ID of the Hart */ +#define RV_HART_ID 0xf14U + +/* tselect -> Trigger selection register */ +#define RV_TRIG_SELECT 0x7a0U +/* tinfo -> selected trigger information register */ +#define RV_TRIG_INFO 0x7a4U +/* tdata1 -> selected trigger configuration register 1 */ +#define RV_TRIG_DATA_1 0x7a1U +/* tdata2 -> selected trigger configuration register 2 */ +#define RV_TRIG_DATA_2 0x7a2U + +#define RV_ISA_EXTENSIONS_MASK 0x03ffffffU + +#define RV_VENDOR_JEP106_CONT_MASK 0x7fffff80U +#define RV_VENDOR_JEP106_CODE_MASK 0x7fU + +#define RV_DCSR_STEP 0x00000004U +#define RV_DCSR_CAUSE_MASK 0x000001c0U +#define RV_DCSR_STEPIE 0x00000800U + +#define RV_GPRS_COUNT 32U + +/* This enum defines the set of currently known and valid halt causes */ +typedef enum riscv_halt_cause { + /* Halt was caused by an `ebreak` instruction executing */ + RV_HALT_CAUSE_EBREAK = (1U << 6U), + /* Halt was caused by a breakpoint or watchpoint (set in the trigger module) */ + RV_HALT_CAUSE_TRIGGER = (2U << 6U), + /* Halt was caused by debugger request (haltreq) */ + RV_HALT_CAUSE_REQUEST = (3U << 6U), + /* Halt was caused by single-step execution */ + RV_HALT_CAUSE_STEP = (4U << 6U), + /* Halt was caused by request out of reset (resethaltreq) */ + RV_HALT_CAUSE_RESET = (5U << 6U), +} riscv_halt_cause_e; + +// clang-format off +/* General-purpose register name strings */ +static const char *const riscv_gpr_names[RV_GPRS_COUNT] = { + "zero", "ra", "sp", "gp", + "tp", "t0", "t1", "t2", + "fp", "s1", "a0", "a1", + "a2", "a3", "a4", "a5", + "a6", "a7", "s2", "s3", + "s4", "s5", "s6", "s7", + "s8", "s9", "s10", "s11", + "t3", "t4", "t5", "t6", +}; + +typedef struct riscv_csr_descriptor { + const char *name; + const uint32_t csr_number; // fits in 16 bits actually (?) +} riscv_csr_descriptor_s; + +static const riscv_csr_descriptor_s riscv_csrs[]={ + {"mstatus",RV_CSR_STATUS}, + {"misa",RV_CSR_MISA}, + {"mie", RV_CSR_MIE}, + {"mtvec", RV_CSR_MTVEC}, + {"tscratch",RV_CSR_MSCRATCH}, + {"mepc", RV_CSR_MEPC}, + {"mcause",RV_CSR_MCAUSE}, + {"mtval", RV_CSR_MTVAL}, + {"mip", RV_CSR_MIP}, +}; + +// clang-format on + +/* General-purpose register types */ +static const gdb_reg_type_e riscv_gpr_types[RV_GPRS_COUNT] = { + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_CODE_PTR, + GDB_TYPE_DATA_PTR, + GDB_TYPE_DATA_PTR, + GDB_TYPE_DATA_PTR, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_DATA_PTR, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, + GDB_TYPE_UNSPECIFIED, +}; + +// clang-format off +static_assert(ARRAY_LENGTH(riscv_gpr_names) == ARRAY_LENGTH(riscv_gpr_types), + "GPR array length mismatch! GPR type array should have the same length as GPR name array." +); +// clang-format on + +static void riscv_dm_init(riscv_dm_s *dbg_module); +static bool riscv_hart_init(riscv_hart_s *hart); +static void riscv_hart_free(void *priv); +static bool riscv_dmi_read(riscv_dmi_s *dmi, uint32_t address, uint32_t *value); +static bool riscv_dmi_write(riscv_dmi_s *dmi, uint32_t address, uint32_t value); +static void riscv_dm_ref(riscv_dm_s *dbg_module); +static void riscv_dm_unref(riscv_dm_s *dbg_module); +static riscv_debug_version_e riscv_dm_version(uint32_t status); + +static uint32_t riscv_hart_discover_isa(riscv_hart_s *hart); +static void riscv_hart_discover_triggers(riscv_hart_s *hart); +static void riscv_hart_memory_access_type(riscv_hart_s *hart); + +static bool riscv_attach(target_s *target); +static void riscv_detach(target_s *target); + +static const char *riscv_target_description(target_s *target); + +static bool riscv_check_error(target_s *target); +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 */ + 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"); + return; + } + + /* Iterate through the possible DMs and probe implemented ones */ + /* The first DM is always at base address 0 */ + uint32_t base_addr = 0U; + do { + /* Read out the DM's status register */ + uint32_t dm_status = 0; + if (!riscv_dmi_read(dmi, base_addr + RV_DM_STATUS, &dm_status)) { + /* If we fail to read the status register, abort */ + break; + } + 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) { + 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__); + return; + } + /* Setup and try to discover the DM's Harts */ + dbg_module->dmi_bus = dmi; + dbg_module->base = base_addr; + dbg_module->version = dm_version; + riscv_dm_init(dbg_module); + /* If we failed to discover any Harts, free the structure */ + if (!dbg_module->ref_count) + free(dbg_module); + } + + /* Read out the address of the next DM */ + if (!riscv_dmi_read(dmi, base_addr + RV_DM_NEXT_DM, &base_addr)) { + /* If this fails then abort further scanning */ + DEBUG_INFO("Error while reading the next DM base address\n"); + break; + } + /* A new base address of 0 indicates this is the last one on the chain and we should stop. */ + } while (base_addr != 0U); +} + +static void riscv_dm_init(riscv_dm_s *const dbg_module) +{ + /* Attempt to activate the DM */ + if (!riscv_dm_write(dbg_module, RV_DM_CONTROL, RV_DM_CTRL_ACTIVE)) + return; + /* Now find out how many hartsel bits are present */ + uint32_t control = RV_DM_CTRL_ACTIVE | RV_DM_CTRL_HARTSEL_MASK; + if (!riscv_dm_write(dbg_module, RV_DM_CONTROL, control) || !riscv_dm_read(dbg_module, RV_DM_CONTROL, &control)) + return; + /* Extract the maximum number of harts present and iterate through the harts */ + const uint32_t harts_max = ((control & RV_DM_CTRL_HARTSELLO_MASK) >> RV_DM_CTRL_HARTSELLO_SHIFT) | + ((control & RV_DM_CTRL_HARTSELHI_MASK) << RV_DM_CTRL_HARTSELHI_SHIFT); + for (uint32_t hart_idx = 0; hart_idx <= harts_max; ++hart_idx) { + /* Select the hart */ + control = ((hart_idx << RV_DM_CTRL_HARTSELLO_SHIFT) & RV_DM_CTRL_HARTSELLO_MASK) | + ((hart_idx >> RV_DM_CTRL_HARTSELHI_SHIFT) & RV_DM_CTRL_HARTSELHI_MASK) | RV_DM_CTRL_ACTIVE; + uint32_t status = 0; + if (!riscv_dm_write(dbg_module, RV_DM_CONTROL, control) || !riscv_dm_read(dbg_module, RV_DM_STATUS, &status)) + return; + /* If the hart doesn't exist, the spec says to terminate scan */ + if (status & RV_DM_STAT_NON_EXISTENT) + break; + + riscv_hart_s *hart = calloc(1, sizeof(*hart)); + if (!hart) { /* calloc failed: heap exhaustion */ + DEBUG_WARN("calloc: failed in %s\n", __func__); + return; + } + /* Setup the hart structure and discover the target core */ + hart->dbg_module = dbg_module; + hart->hart_idx = hart_idx; + hart->hartsel = control; + if (!riscv_hart_init(hart)) + free(hart); + } +} + +static uint8_t riscv_isa_address_width(const uint32_t isa) +{ + switch (isa >> 30U) { + case 1: + return 32U; + case 2: + return 64U; + case 3: + return 128U; + } + DEBUG_INFO("Unknown address width, defaulting to 32\n"); + return 32U; +} + +static void riscv_hart_read_ids(riscv_hart_s *const hart) +{ + /* Read out the vendor ID */ + riscv_csr_read(hart, RV_VENDOR_ID | RV_CSR_FORCE_32_BIT, &hart->vendorid); + /* Adjust the value to fit our view of JEP-106 codes */ + hart->vendorid = + ((hart->vendorid & RV_VENDOR_JEP106_CONT_MASK) << 1U) | (hart->vendorid & RV_VENDOR_JEP106_CODE_MASK); + /* Depending on the bus width, read out the other IDs suitably */ + if (hart->access_width == 32U) { + riscv_csr_read(hart, RV_ARCH_ID, &hart->archid); + riscv_csr_read(hart, RV_IMPL_ID, &hart->implid); + riscv_csr_read(hart, RV_HART_ID, &hart->hartid); + } else if (hart->access_width == 64U) { + /* For now, on rv64, we just truncate these down after read */ + uint64_t ident = 0; + riscv_csr_read(hart, RV_ARCH_ID, &ident); + hart->archid = ident & 0xffffffffU; + riscv_csr_read(hart, RV_IMPL_ID, &ident); + hart->implid = ident & 0xffffffffU; + riscv_csr_read(hart, RV_HART_ID, &ident); + hart->hartid = ident & 0xffffffffU; + } + /* rv128 is unimpl. */ +} + +static bool riscv_hart_init(riscv_hart_s *const hart) +{ + /* Allocate a new target */ + target_s *target = target_new(); + if (!target) + return false; + + /* Grab a reference to the DMI and DM structurues and do preliminary setup of the target structure */ + riscv_dm_ref(hart->dbg_module); + target->driver = "RISC-V"; + target->priv = hart; + target->priv_free = riscv_hart_free; + + /* Request halt and read certain key registers */ + riscv_halt_request(target); + uint32_t isa = riscv_hart_discover_isa(hart); + hart->address_width = riscv_isa_address_width(isa); + hart->extensions = isa & RV_ISA_EXTENSIONS_MASK; + 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); + + /* 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; + } + + /* If the hart implements mvendorid, this gives us the JEP-106, otherwise use the DTM designer code */ + target->designer_code = hart->vendorid ? hart->vendorid : hart->dbg_module->dmi_bus->designer_code; + target->cpuid = hart->archid; + + riscv_hart_discover_triggers(hart); + riscv_hart_memory_access_type(hart); + + /* Setup core-agnostic target functions */ + target->attach = riscv_attach; + target->detach = riscv_detach; + + target->regs_description = riscv_target_description; + + target->check_error = riscv_check_error; + target->halt_request = riscv_halt_request; + target->halt_resume = riscv_halt_resume; + target->halt_poll = riscv_halt_poll; + target->reset = riscv_reset; + + if (hart->access_width == 32U) { + DEBUG_INFO("-> riscv32_probe\n"); + if (!riscv32_probe(target)) + DEBUG_INFO("Probing failed, please report unknown RISC-V 32 device\n"); + } + if (hart->access_width == 64U) { + DEBUG_INFO("-> riscv64_probe\n"); + if (!riscv64_probe(target)) + DEBUG_INFO("Probing failed, please report unknown RISC-V 64 device\n"); + } + riscv_halt_resume(target, false); + return true; +} + +riscv_hart_s *riscv_hart_struct(target_s *const target) +{ + return (riscv_hart_s *)target->priv; +} + +static void riscv_hart_free(void *const priv) +{ + riscv_dm_unref(((riscv_hart_s *)priv)->dbg_module); + free(priv); +} + +static bool riscv_dmi_read(riscv_dmi_s *const dmi, const uint32_t address, uint32_t *const value) +{ + bool result = false; + do { + result = dmi->read(dmi, address, value); + } while (dmi->fault == RV_DMI_TOO_SOON); + + if (result) + DEBUG_TARGET("%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_TARGET("%s: %08" PRIx32 " <- %08" PRIx32 "\n", __func__, 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) +{ + return riscv_dmi_read(dbg_module->dmi_bus, dbg_module->base + address, value); +} + +bool riscv_dm_write(riscv_dm_s *dbg_module, const uint8_t address, const uint32_t value) +{ + return riscv_dmi_write(dbg_module->dmi_bus, dbg_module->base + address, value); +} + +static riscv_debug_version_e riscv_dm_version(const uint32_t status) +{ + uint8_t version = status & RV_STATUS_VERSION_MASK; + switch (version) { + case 0: + return RISCV_DEBUG_UNIMPL; + case 1: + DEBUG_INFO("RISC-V debug v0.11 DM\n"); + return RISCV_DEBUG_0_11; + case 2: + DEBUG_INFO("RISC-V debug v0.13 DM\n"); + return RISCV_DEBUG_0_13; + case 3: + DEBUG_INFO("RISC-V debug v1.0 DM\n"); + return RISCV_DEBUG_1_0; + } + DEBUG_INFO("Please report part with unknown RISC-V debug DM version %x\n", version); + return RISCV_DEBUG_UNKNOWN; +} + +static inline void riscv_dmi_ref(riscv_dmi_s *const dmi) +{ + ++dmi->ref_count; +} + +static inline void riscv_dmi_unref(riscv_dmi_s *const dmi) +{ + --dmi->ref_count; + if (!dmi->ref_count) + free(dmi); +} + +static void riscv_dm_ref(riscv_dm_s *const dbg_module) +{ + if (!dbg_module->ref_count) + riscv_dmi_ref(dbg_module->dmi_bus); + ++dbg_module->ref_count; +} + +static void riscv_dm_unref(riscv_dm_s *const dbg_module) +{ + --dbg_module->ref_count; + if (!dbg_module->ref_count) { + riscv_dmi_unref(dbg_module->dmi_bus); + free(dbg_module); + } +} + +static uint32_t riscv_hart_discover_isa(riscv_hart_s *const hart) +{ + /* Read out the abstract command control/status register */ + uint32_t data_registers = 0; + if (!riscv_dm_read(hart->dbg_module, RV_DM_ABST_CTRLSTATUS, &data_registers)) + return 0U; + /* And use the data count bits to divine an initial guess on the platform width */ + data_registers &= RV_DM_ABST_STATUS_DATA_COUNT; + DEBUG_INFO("Hart has %" PRIu32 " data registers\n", data_registers); + /* Check we have at least enough data registers for arg0 */ + if (data_registers >= 4) + hart->access_width = 128U; + else if (data_registers >= 2) + hart->access_width = 64U; + else if (data_registers) + hart->access_width = 32U; + /* If the control/status register contains an invalid count, abort */ + else + return 0; + + do { + DEBUG_INFO("Attempting %u-bit read on misa\n", hart->access_width); + /* Try reading the register on the guessed width */ + uint32_t isa_data[4] = {}; + bool result = riscv_csr_read(hart, RV_ISA, isa_data); + if (result) { + if (hart->access_width == 128U) + return (isa_data[3] & 0xc0000000) | (isa_data[0] & 0x3fffffffU); + if (hart->access_width == 64U) + return (isa_data[1] & 0xc0000000) | (isa_data[0] & 0x3fffffffU); + return isa_data[0]; + } + /* If that failed, then find out why and instead try the next narrower width */ + if (hart->status != RISCV_HART_BUS_ERROR && hart->status != RISCV_HART_EXCEPTION && + hart->status != RISCV_HART_NOT_SUPP) + return 0; + if (hart->access_width == 32U) { + hart->access_width = 0U; + return 0; /* We are unable to read the misa register */ + } + if (hart->access_width == 64U) + hart->access_width = 32U; + if (hart->access_width == 128U) + hart->access_width = 64U; + } while (hart->access_width != 0U); + DEBUG_WARN("Unable to read misa register\n"); + /* If the above loop failed, we're done.. */ + return 0U; +} + +static uint32_t riscv_hart_access_width(uint8_t access_width) +{ + if (access_width == 128U) + return RV_REG_ACCESS_128_BIT; + if (access_width == 64U) + return RV_REG_ACCESS_64_BIT; + return RV_REG_ACCESS_32_BIT; +} + +static uint32_t riscv_csr_access_width(const uint16_t reg) +{ + const uint16_t access_width = reg & RV_CSR_FORCE_MASK; + if (access_width == RV_CSR_FORCE_32_BIT) + return 32U; + if (access_width == RV_CSR_FORCE_64_BIT) + return 64U; + return 128U; +} + +bool riscv_command_wait_complete(riscv_hart_s *const hart) +{ + uint32_t status = RV_DM_ABST_STATUS_BUSY; + while (status & RV_DM_ABST_STATUS_BUSY) { + if (!riscv_dm_read(hart->dbg_module, RV_DM_ABST_CTRLSTATUS, &status)) + return false; + } + /* Shift out and mask off the command status, then reset the status on the Hart */ + hart->status = (status >> 8U) & RISCV_HART_OTHER; + if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_CTRLSTATUS, RISCV_HART_OTHER << 8U)) + return false; + if (hart->status != RISCV_HART_NO_ERROR) + DEBUG_WARN("CSR access failed: %u\n", hart->status); + /* If the command failed, return the failure */ + return hart->status == RISCV_HART_NO_ERROR; +} + +bool riscv_csr_read(riscv_hart_s *const hart, const uint16_t reg, void *const data) +{ + const uint8_t access_width = (reg & RV_CSR_FORCE_MASK) ? riscv_csr_access_width(reg) : hart->access_width; + DEBUG_TARGET("Reading %u-bit CSR %03x\n", access_width, reg & ~RV_CSR_FORCE_MASK); + /* Set up the register read and wait for it to complete */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_COMMAND, + RV_DM_ABST_CMD_ACCESS_REG | RV_ABST_READ | RV_REG_XFER | riscv_hart_access_width(access_width) | + (reg & ~RV_CSR_FORCE_MASK)) || + !riscv_command_wait_complete(hart)) + return false; + 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))) + 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)) + return false; + /* Finally grab the last and lowest uint32_t */ + return riscv_dm_read(hart->dbg_module, RV_DM_DATA0, value); +} + +bool riscv_csr_write(riscv_hart_s *const hart, const uint16_t reg, const void *const data) +{ + const uint8_t access_width = (reg & RV_CSR_FORCE_MASK) ? riscv_csr_access_width(reg) : hart->access_width; + DEBUG_TARGET("Writing %u-bit CSR %03x\n", access_width, reg & ~RV_CSR_FORCE_MASK); + /* Set up the data registers based on the Hart native access size */ + const uint32_t *const value = (const uint32_t *)data; + if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA0, value[0])) + return false; + if (access_width >= 64U && !riscv_dm_write(hart->dbg_module, RV_DM_DATA1, value[1])) + return false; + 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]))) + return false; + /* Configure and run the write */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_ABST_COMMAND, + RV_DM_ABST_CMD_ACCESS_REG | RV_ABST_WRITE | RV_REG_XFER | riscv_hart_access_width(access_width) | + (reg & ~RV_CSR_FORCE_MASK))) + return false; + return riscv_command_wait_complete(hart); +} + +uint8_t riscv_mem_access_width(const riscv_hart_s *const hart, const target_addr_t address, const size_t length) +{ + /* Grab the Hart's most maxmimally aligned possible write width */ + uint8_t access_width = riscv_hart_access_width(hart->address_width) >> RV_ABST_MEM_ACCESS_SHIFT; + /* Convert the hart access width to a mask - for example, for 32-bit harts, this gives (1U << 2U) - 1U = 3U */ + uint8_t align_mask = (1U << access_width) - 1U; + /* Mask out the bottom bits of both the address and length - anything that determines the alignment */ + const uint8_t addr_bits = address & align_mask; + const uint8_t len_bits = length & align_mask; + /* bitwise-OR together the result so, for example, an odd address 2-byte read results in a pattern like 0bxx11 */ + const uint8_t align = addr_bits | len_bits; + /* Loop down through the possible access widths till we find one suitably aligned */ + for (; access_width; --access_width) { + if ((align & align_mask) == 0) + return access_width; + align_mask >>= 1U; + } + return access_width; +} + +static void riscv_hart_discover_triggers(riscv_hart_s *const hart) +{ + /* Discover how many breakpoints this hart supports */ + hart->triggers = UINT32_MAX; + if (!riscv_csr_write(hart, RV_TRIG_SELECT | RV_CSR_FORCE_32_BIT, &hart->triggers) || + !riscv_csr_read(hart, RV_TRIG_SELECT | RV_CSR_FORCE_32_BIT, &hart->triggers)) { + hart->triggers = 0; + return; + } + /* + * The value we read back will always be one less than the actual number supported + * as it represents the last valid index, rather than the last valid breakpoint. + */ + ++hart->triggers; + DEBUG_INFO("Hart has %" PRIu32 " trigger slots available\n", hart->triggers); + /* If the hardware supports more slots than we do, cap it. */ + if (hart->triggers > RV_TRIGGERS_MAX) + hart->triggers = RV_TRIGGERS_MAX; + + /* Next, go through each one and map what it supports out into the trigger_uses slots */ + for (uint32_t trigger = 0; trigger < hart->triggers; ++trigger) { + /* Select the trigger */ + riscv_csr_write(hart, RV_TRIG_SELECT | RV_CSR_FORCE_32_BIT, &trigger); + /* Try reading the trigger info */ + uint32_t info = 0; + /* Some chips reply ok but return 0 (WCH)*/ + if (!riscv_csr_read(hart, RV_TRIG_INFO | RV_CSR_FORCE_32_BIT, &info) || !info) { + /* + * If that fails, it's probably because the tinfo register isn't implemented, so read + * the tdata1 register instead and extract the type from the MSb and build the info bitset from that + */ + if (hart->access_width == 32U) { + uint32_t data = 0; + riscv_csr_read(hart, RV_TRIG_DATA_1, &data); + /* The last 4 bits contain the trigger info */ + info = data >> 28U; + } else { + uint64_t data = 0; + riscv_csr_read(hart, RV_TRIG_DATA_1, &data); + /* The last 4 bits contain the trigger info */ + info = data >> 60U; + } + /* Info now needs converting from a value from 0 to 15 to having the correct bit set */ + info = 1U << info; + } + /* If the 0th bit is set, this means the trigger is unsupported. Clear it to make testing easy */ + info &= RV_TRIGGER_SUPPORT_MASK; + /* Now info's bottom 16 bits contain the supported trigger modes, so write this info to the slot in the hart */ + hart->trigger_uses[trigger] = info; + DEBUG_TARGET("Hart trigger slot %" PRIu32 " modes: %04" PRIx32 "\n", trigger, info); + } +} + +static void riscv_hart_memory_access_type(riscv_hart_s *const hart) +{ + uint32_t sysbus_status; + hart->flags &= (uint8_t)~RV_HART_FLAG_MEMORY_SYSBUS; + /* + * 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); + /* 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); +} + +riscv_match_size_e riscv_breakwatch_match_size(const size_t size) +{ + switch (size) { + case 8U: + return RV_MATCH_SIZE_8_BIT; + case 16U: + return RV_MATCH_SIZE_16_BIT; + case 32U: + return RV_MATCH_SIZE_32_BIT; + case 48U: + return RV_MATCH_SIZE_48_BIT; + case 64U: + return RV_MATCH_SIZE_64_BIT; + case 80U: + return RV_MATCH_SIZE_80_BIT; + case 96U: + return RV_MATCH_SIZE_96_BIT; + case 112U: + return RV_MATCH_SIZE_112_BIT; + case 128U: + return RV_MATCH_SIZE_128_BIT; + } + return 0; +} + +bool riscv_config_trigger(riscv_hart_s *const hart, const uint32_t trigger, const riscv_trigger_state_e mode, + const void *const config, const void *const address) +{ + /* + * Select the trigger and write the new configuration to it provided by config. + * tdata1 (RV_TRIG_DATA_1) becomes mcontrol (match control) for this - + * see §5.2.9 pg53 of the RISC-V debug spec v0.13.2 for more details. + */ + const bool result = riscv_csr_write(hart, RV_TRIG_SELECT | RV_CSR_FORCE_32_BIT, &trigger) && + riscv_csr_write(hart, RV_TRIG_DATA_1, config) && riscv_csr_write(hart, RV_TRIG_DATA_2, address); + if (result) { + /* If that succeeds, update the slot with the new mode it's in */ + hart->trigger_uses[trigger] &= ~RV_TRIGGER_MODE_MASK; + hart->trigger_uses[trigger] |= mode; + } + return result; +} + +static bool riscv_attach(target_s *const target) +{ + riscv_hart_s *const hart = riscv_hart_struct(target); + /* If the DMI requires special preparation, do that first */ + if (hart->dbg_module->dmi_bus->prepare) + hart->dbg_module->dmi_bus->prepare(target); + /* We then also need to select the Hart again so we're poking with the right one on the target */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_CONTROL, hart->hartsel)) + return false; + /* We then need to halt the hart so the attach process can function */ + riscv_halt_request(target); + return true; +} + +static void riscv_detach(target_s *const target) +{ + riscv_hart_s *const hart = riscv_hart_struct(target); + /* Once we get done and the user's asked us to detach, we need to resume the hart */ + riscv_halt_resume(target, false); + /* If the DMI needs steps done to quiesce it, finsh up with that */ + if (hart->dbg_module->dmi_bus->quiesce) + hart->dbg_module->dmi_bus->quiesce(target); +} + +static bool riscv_check_error(target_s *const target) +{ + return riscv_hart_struct(target)->status != RISCV_HART_NO_ERROR; +} + +static bool riscv_dm_poll_state(riscv_dm_s *const dbg_module, const uint32_t state) +{ + platform_timeout_s timeout; + platform_timeout_set(&timeout, 500U); + /* Poll for the requested state to become set */ + uint32_t status = 0; + while (!(status & state)) { + if (!riscv_dm_read(dbg_module, RV_DM_STATUS, &status) || platform_timeout_is_expired(&timeout)) + return false; + } + return true; +} + +void riscv_halt_request(target_s *const target) +{ + riscv_hart_s *const hart = riscv_hart_struct(target); + /* Request the hart to halt */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_CONTROL, hart->hartsel | RV_DM_CTRL_HALT_REQ)) + return; + /* Poll for the hart to become halted */ + if (!riscv_dm_poll_state(hart->dbg_module, RV_DM_STAT_ALL_HALTED)) + return; + /* Clear the request now we've got it halted */ + (void)riscv_dm_write(hart->dbg_module, RV_DM_CONTROL, hart->hartsel); +} + +void riscv_halt_resume(target_s *const target, const bool step) +{ + riscv_hart_s *const hart = riscv_hart_struct(target); + /* Configure the debug controller for single-stepping as appropriate */ + uint32_t stepping_config = 0U; + if (!riscv_csr_read(hart, RV_DCSR | RV_CSR_FORCE_32_BIT, &stepping_config)) + return; + if (step) + stepping_config |= RV_DCSR_STEP | RV_DCSR_STEPIE; + else + stepping_config &= ~(RV_DCSR_STEP | RV_DCSR_STEPIE); + if (!riscv_csr_write(hart, RV_DCSR | RV_CSR_FORCE_32_BIT, &stepping_config)) + return; + /* Request the hart to resume */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_CONTROL, hart->hartsel | RV_DM_CTRL_RESUME_REQ)) + return; + /* Poll for the hart to become resumed */ + if (!riscv_dm_poll_state(hart->dbg_module, RV_DM_STAT_ALL_RESUME_ACK)) + return; + /* Clear the request now we've got it resumed */ + (void)riscv_dm_write(hart->dbg_module, RV_DM_CONTROL, hart->hartsel); +} + +target_halt_reason_e riscv_halt_poll(target_s *const target, target_addr_t *const watch) +{ + (void)watch; + riscv_hart_s *const hart = riscv_hart_struct(target); + uint32_t status = 0; + /* Check if the hart is currently halted */ + if (!riscv_dm_read(hart->dbg_module, RV_DM_STATUS, &status)) + return TARGET_HALT_ERROR; + /* If the hart is currently running, exit out early */ + if (!(status & RV_DM_STAT_ALL_HALTED)) + return TARGET_HALT_RUNNING; + /* Read out DCSR to find out why we're halted */ + if (!riscv_csr_read(hart, RV_DCSR, &status)) + return TARGET_HALT_ERROR; + status &= RV_DCSR_CAUSE_MASK; + /* Dispatch on the cause code */ + switch (status) { + case RV_HALT_CAUSE_TRIGGER: + /* XXX: Need to read out the triggers to find the one causing this, and grab the watch value */ + return TARGET_HALT_BREAKPOINT; + case RV_HALT_CAUSE_STEP: + return TARGET_HALT_STEPPING; + default: + break; + } + /* In the default case, assume it was by request (ebreak, haltreq, resethaltreq) */ + return TARGET_HALT_REQUEST; +} + +/* Do note that this can be used with a riscv_halt_request() call to initiate halt-on-reset debugging */ +static void riscv_reset(target_s *const target) +{ + riscv_hart_s *const hart = riscv_hart_struct(target); + bool has_reset = false; + /* If the target does not have the nRST pin inhibited, use that to initiate reset */ + if (!(target->target_options & RV_TOPT_INHIBIT_NRST)) { + platform_nrst_set_val(true); + has_reset = riscv_dm_poll_state(hart->dbg_module, RV_DM_STAT_ALL_RESET); + platform_nrst_set_val(false); + /* In theory we're done at this point and no debug state was perturbed */ + } + + /* + * Otherwise, if nRST is not usable (or failed), use instead reset via dmcontrol. In this case, + * when reset is requested, use the ndmreset bit to perform a system reset + */ + if (!has_reset) { + riscv_dm_write(hart->dbg_module, RV_DM_CONTROL, hart->hartsel | RV_DM_CTRL_SYSTEM_RESET); + riscv_dm_poll_state(hart->dbg_module, RV_DM_STAT_ALL_RESET); + /* Complete the reset by resetting ndmreset */ + riscv_dm_write(hart->dbg_module, RV_DM_CONTROL, hart->hartsel); + } + /* Acknowledge the reset */ + riscv_dm_write(hart->dbg_module, RV_DM_CONTROL, hart->hartsel | RV_DM_CTRL_HART_ACK_RESET); + riscv_halt_request(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 ""; +} + +/* + * This function creates the target description XML string for a RISC-V part. + * This is done this way to decrease string duplication and thus code size, making it + * unfortunately much less readable than the string literal it is equivalent to. + * + * This string it creates is the XML-equivalent to the following: + * "" + * "" + * "" + * " riscv:rv[address_width][exts]" + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * The following are only generated for an I core, not an E: + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * Both are then continued with: + * " " + * " " + * "" + */ +static size_t riscv_build_target_description( + char *const buffer, 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. */ + int offset = 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); + + const uint8_t gprs = 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) { + if (max_length != 0) + print_size = max_length - (size_t)offset; + + const char *const name = riscv_gpr_names[i]; + const gdb_reg_type_e type = riscv_gpr_types[i]; + + offset += snprintf(buffer + offset, print_size, "", name, address_width, + gdb_reg_type_strings[type], i == 0 ? " regnum=\"0\"" : ""); + } + + /* Then build the program counter register description, which has the same bitsize as the GPRs. */ + if (max_length != 0) + print_size = max_length - (size_t)offset; + offset += snprintf(buffer + offset, print_size, "", address_width, + gdb_reg_type_strings[GDB_TYPE_CODE_PTR]); + + /* XXX: TODO - implement generation of the FPU feature and registers */ + + /* Add main CSR registers*/ + if (max_length != 0) + print_size = max_length - (size_t)offset; + offset += snprintf(buffer + offset, print_size, ""); + for (size_t i = 0; i < ARRAY_LENGTH(riscv_csrs); i++) { + if (max_length != 0) + print_size = max_length - (size_t)offset; + offset += snprintf(buffer + offset, print_size, " ", + riscv_csrs[i].name, address_width, riscv_csrs[i].csr_number + RV_CSR_GDB_OFFSET, + gdb_reg_save_restore_strings[GDB_SAVE_RESTORE_NO]); + } + /* Add the closing tags required */ + if (max_length != 0) + print_size = max_length - (size_t)offset; + + offset += snprintf(buffer + offset, print_size, ""); + /* offset is now the total length of the string created, discard the sign and return it. */ + return (size_t)offset; +} + +static const char *riscv_target_description(target_s *const target) +{ + const riscv_hart_s *const hart = riscv_hart_struct(target); + const size_t description_length = + riscv_build_target_description(NULL, 0, hart->address_width, hart->extensions) + 1U; + char *const description = malloc(description_length); + if (description) + (void)riscv_build_target_description(description, description_length, hart->address_width, hart->extensions); + return description; +} diff --git a/src/target/riscv_debug.h b/src/target/riscv_debug.h new file mode 100644 index 00000000000..1ca145ce71a --- /dev/null +++ b/src/target/riscv_debug.h @@ -0,0 +1,247 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rachel Mant + * 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. + */ + +#ifndef TARGET_RISCV_DEBUG_H +#define TARGET_RISCV_DEBUG_H + +#include +#include +#include "target.h" + +typedef enum riscv_debug_version { + RISCV_DEBUG_UNKNOWN, + RISCV_DEBUG_UNIMPL, + RISCV_DEBUG_0_11, + RISCV_DEBUG_0_13, + RISCV_DEBUG_1_0, +} riscv_debug_version_e; + +/* This enum describes the Hart status (eg after a CSR read/write) */ +typedef enum riscv_hart_status { + /* The Hart is in a good state */ + RISCV_HART_NO_ERROR = 0, + /* The Hart was bust when the status was read */ + RISCV_HART_BUSY = 1, + /* The operation requested of the Hart was not supported */ + RISCV_HART_NOT_SUPP = 2, + /* An exception occured on the Hart while running the operation */ + RISCV_HART_EXCEPTION = 3, + /* The Hart is in the wrong state for the requested operation */ + RISCV_HART_WRONG_STATE = 4, + /* The operation triggered a Hart bus error (bad alignment, access size, or timeout) */ + RISCV_HART_BUS_ERROR = 5, + /* The operation failed for other (unknown) reasons */ + RISCV_HART_OTHER = 7, +} riscv_hart_status_e; + +/* This enum describes the current state of a trigger in the TM */ +typedef enum riscv_trigger_state { + RISCV_TRIGGER_MODE_UNUSED = 0x00000000U, + RISCV_TRIGGER_MODE_BREAKPOINT = 0x00010000U, + RISCV_TRIGGER_MODE_WATCHPOINT = 0x00020000U, +} riscv_trigger_state_e; + +/* The size bits are 22:21 + 17:16, but the upper 2 are only valid on rv64 */ +typedef enum riscv_match_size { + RV_MATCH_SIZE_8_BIT = 0x00010000U, + RV_MATCH_SIZE_16_BIT = 0x00020000U, + RV_MATCH_SIZE_32_BIT = 0x00030000U, + RV_MATCH_SIZE_48_BIT = 0x00200000U, + RV_MATCH_SIZE_64_BIT = 0x00210000U, + RV_MATCH_SIZE_80_BIT = 0x00220000U, + RV_MATCH_SIZE_96_BIT = 0x00230000U, + RV_MATCH_SIZE_112_BIT = 0x00400000U, + RV_MATCH_SIZE_128_BIT = 0x00410000U, +} riscv_match_size_e; + +/* These defines specify Hart-specific information such as which memory access style to use */ +#define RV_HART_FLAG_MEMORY_ABSTRACT 0x00U +#define RV_HART_FLAG_MEMORY_SYSBUS 0x10U +#define RV_HART_FLAG_ACCESS_WIDTH_MASK 0x0fU +#define RV_HART_FLAG_ACCESS_WIDTH_8BIT 0x01U +#define RV_HART_FLAG_ACCESS_WIDTH_16BIT 0x02U +#define RV_HART_FLAG_ACCESS_WIDTH_32BIT 0x04U +#define RV_HART_FLAG_ACCESS_WIDTH_64BIT 0x08U + +typedef struct riscv_dmi riscv_dmi_s; + +/* This structure represents a version-agnostic Debug Module Interface on a RISC-V device */ +struct riscv_dmi { + uint32_t ref_count; + + uint16_t designer_code; + riscv_debug_version_e version; + + uint8_t dev_index; + uint8_t idle_cycles; + uint8_t address_width; + uint8_t fault; + + void (*prepare)(target_s *target); + void (*quiesce)(target_s *target); + bool (*read)(riscv_dmi_s *dmi, uint32_t address, uint32_t *value); + bool (*write)(riscv_dmi_s *dmi, uint32_t address, uint32_t value); +}; + +/* This represents a specific Debug Module on the DMI bus */ +typedef struct riscv_dm { + uint32_t ref_count; + + riscv_dmi_s *dmi_bus; + uint32_t base; + riscv_debug_version_e version; +} riscv_dm_s; + +#define RV_TRIGGERS_MAX 8U + +/* This represents a specifc Hart on a DM */ +typedef struct riscv_hart { + riscv_dm_s *dbg_module; + uint32_t hart_idx; + uint32_t hartsel; + uint8_t access_width; + uint8_t address_width; + uint8_t flags; + riscv_hart_status_e status; + + uint32_t extensions; + uint32_t vendorid; + uint32_t archid; + uint32_t implid; + uint32_t hartid; + + uint32_t triggers; + uint32_t trigger_uses[RV_TRIGGERS_MAX]; +} riscv_hart_s; + +#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 +#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_DM_ABST_CMD_ACCESS_REG 0x00000000U +#define RV_DM_ABST_CMD_ACCESS_MEM 0x02000000U + +#define RV_ABST_READ 0x00000000U +#define RV_ABST_WRITE 0x00010000U +#define RV_REG_XFER 0x00020000U +#define RV_REG_ACCESS_32_BIT 0x00200000U +#define RV_REG_ACCESS_64_BIT 0x00300000U +#define RV_REG_ACCESS_128_BIT 0x00400000U + +#define RV_MEM_ACCESS_8_BIT 0x0U +#define RV_MEM_ACCESS_16_BIT 0x1U +#define RV_MEM_ACCESS_32_BIT 0x2U +#define RV_MEM_ACCESS_64_BIT 0x3U +#define RV_MEM_ACCESS_128_BIT 0x4U + +#define RV_ABST_MEM_ADDR_POST_INC 0x00080000U +#define RV_ABST_MEM_ACCESS_SHIFT 20U + +#define RV_SYSBUS_MEM_ADDR_POST_INC 0x00010000U +#define RV_SYSBUS_MEM_READ_ON_ADDR 0x00100000U +#define RV_SYSBUS_MEM_READ_ON_DATA 0x00008000U +#define RV_SYSBUS_STATUS_BUSY 0x00200000U +#define RV_SYSBUS_MEM_ACCESS_SHIFT 17U + +/* dpc -> Debug Program Counter */ +#define RV_DPC 0x7b1U +/* The GPR base defines the starting register space address for the CPU state registers */ +#define RV_GPR_BASE 0x1000U +/* 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 + +#define RV_TRIGGER_SUPPORT_MASK 0x0000fffeU +#define RV_TRIGGER_MODE_MASK 0xffff0000U +#define RV_TRIGGER_SUPPORT_BREAKWATCH 0x00000004U + +// The CSR id when reported by GDB is shifted by RV_CSR_GDB_OFFSET +// so they cannot collide with GPR registers, so you have to substract +// 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_TOPT_INHIBIT_NRST 0x00000001U + +void riscv_jtag_dtm_handler(uint8_t dev_index); +void riscv_dmi_init(riscv_dmi_s *dmi); +riscv_hart_s *riscv_hart_struct(target_s *target); + +bool riscv_dm_read(riscv_dm_s *dbg_module, uint8_t address, uint32_t *value); +bool riscv_dm_write(riscv_dm_s *dbg_module, uint8_t address, uint32_t value); +bool riscv_command_wait_complete(riscv_hart_s *hart); +bool riscv_csr_read(riscv_hart_s *hart, uint16_t reg, void *data); +bool riscv_csr_write(riscv_hart_s *hart, uint16_t reg, const void *data); +riscv_match_size_e riscv_breakwatch_match_size(size_t size); +bool riscv_config_trigger( + riscv_hart_s *hart, uint32_t trigger, riscv_trigger_state_e mode, const void *config, const void *address); + +uint8_t riscv_mem_access_width(const riscv_hart_s *hart, target_addr_t address, size_t length); +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 riscv_halt_request(target_s *target); +void riscv_halt_resume(target_s *target, bool step); +target_halt_reason_e riscv_halt_poll(target_s *target, target_addr_t *watch); + +#endif /*TARGET_RISCV_DEBUG_H*/ diff --git a/src/target/riscv_jtag_dtm.c b/src/target/riscv_jtag_dtm.c new file mode 100644 index 00000000000..bcaab116886 --- /dev/null +++ b/src/target/riscv_jtag_dtm.c @@ -0,0 +1,229 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2023 1BitSquared + * Written by Rachel Mant + * 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. + */ + +#include "general.h" +#include "jtag_scan.h" +#include "jtagtap.h" +#include "riscv_debug.h" +#include "adiv5.h" + +#define IR_DTMCS 0x10U +#define IR_DMI 0x11U +#define IR_BYPASS 0x1fU + +#define RV_DTMCS_NOOP 0x00000000U +#define RV_DTMCS_DMI_RESET 0x00010000U +#define RV_DTMCS_DMI_HARD_RESET 0x00020000U +#define RV_DTMCS_IDLE_CYCLES_MASK 0x00007000U +#define RV_DTMCS_IDLE_CYCLES_SHIFT 12U +#define RV_DTMCS_DMI_STATUS_MASK 0x00000c00U +#define RV_DTMCS_DMI_STATUS_SHIFT 10U +#define RV_DTMCS_ADDRESS_MASK 0x000003f0U +#define RV_DTMCS_ADDRESS_SHIFT 4U + +static void riscv_jtag_dtm_init(riscv_dmi_s *dmi); +static uint32_t riscv_shift_dtmcs(const riscv_dmi_s *dmi, uint32_t control); +static bool riscv_jtag_dmi_read(riscv_dmi_s *dmi, uint32_t address, uint32_t *value); +static bool riscv_jtag_dmi_write(riscv_dmi_s *dmi, uint32_t address, uint32_t value); +static riscv_debug_version_e riscv_dtmcs_version(uint32_t dtmcs); + +static void riscv_jtag_prepare(target_s *target); +static void riscv_jtag_quiesce(target_s *target); + +void riscv_jtag_dtm_handler(const uint8_t dev_index) +{ + riscv_dmi_s *dmi = calloc(1, sizeof(*dmi)); + if (!dmi) { /* calloc failed: heap exhaustion */ + DEBUG_WARN("calloc: failed in %s\n", __func__); + return; + } + + /* Setup and try to discover the DMI bus */ + dmi->dev_index = dev_index; + /* + * The code in JTAG_IDCODE_DESIGNER is in the form + * Bits 10:7 - JEP-106 Continuation code + * Bits 6:0 - JEP-106 Identity code + * here we convert it to our internal representation, See JEP-106 code list + */ + const uint16_t designer = + (jtag_devs[dev_index].jd_idcode & JTAG_IDCODE_DESIGNER_MASK) >> JTAG_IDCODE_DESIGNER_OFFSET; + dmi->designer_code = + (designer & JTAG_IDCODE_DESIGNER_JEP106_CONT_MASK) << 1U | (designer & JTAG_IDCODE_DESIGNER_JEP106_CODE_MASK); + + riscv_jtag_dtm_init(dmi); + /* If we failed to find any DMs or Harts, free the structure */ + if (!dmi->ref_count) + free(dmi); + + /* Reset the JTAG machinery back to bypass to scan the next device in the chain */ + jtag_dev_write_ir(dev_index, IR_BYPASS); +} + +static void riscv_jtag_dtm_init(riscv_dmi_s *const dmi) +{ + const uint32_t dtmcs = riscv_shift_dtmcs(dmi, RV_DTMCS_NOOP); + dmi->version = riscv_dtmcs_version(dtmcs); + /* Configure the TAP idle cylces based on what we've read */ + dmi->idle_cycles = (dtmcs & RV_DTMCS_IDLE_CYCLES_MASK) >> RV_DTMCS_IDLE_CYCLES_SHIFT; + /* And figure out how many address bits the DMI address space has */ + dmi->address_width = (dtmcs & RV_DTMCS_ADDRESS_MASK) >> RV_DTMCS_ADDRESS_SHIFT; + /* Switch into DMI access mode for speed */ + jtag_dev_write_ir(dmi->dev_index, IR_DMI); + + dmi->prepare = riscv_jtag_prepare; + dmi->quiesce = riscv_jtag_quiesce; + dmi->read = riscv_jtag_dmi_read; + dmi->write = riscv_jtag_dmi_write; + + riscv_dmi_init(dmi); +} + +/* Shift (read + write) the Debug Transport Module Control/Status (DTMCS) register */ +uint32_t riscv_shift_dtmcs(const riscv_dmi_s *const dmi, const uint32_t control) +{ + jtag_dev_write_ir(dmi->dev_index, IR_DTMCS); + uint32_t status = 0; + jtag_dev_shift_dr(dmi->dev_index, (uint8_t *)&status, (const uint8_t *)&control, 32); + return status; +} + +static void riscv_dmi_reset(const riscv_dmi_s *const dmi) +{ + riscv_shift_dtmcs(dmi, RV_DTMCS_DMI_RESET); + jtag_dev_write_ir(dmi->dev_index, IR_DMI); +} + +static uint8_t riscv_shift_dmi(riscv_dmi_s *const dmi, const uint8_t operation, const uint32_t address, + const uint32_t data_in, uint32_t *const data_out) +{ + jtag_dev_s *device = &jtag_devs[dmi->dev_index]; + jtagtap_shift_dr(); + jtag_proc.jtagtap_tdi_seq(false, ones, device->dr_prescan); + /* Shift out the 2 bits for the operation, and get the status bits for the previous back */ + uint8_t status = 0; + jtag_proc.jtagtap_tdi_tdo_seq(&status, false, &operation, 2); + /* Then the data component */ + if (data_out) + jtag_proc.jtagtap_tdi_tdo_seq((uint8_t *)data_out, false, (const uint8_t *)&data_in, 32); + else + jtag_proc.jtagtap_tdi_seq(false, (const uint8_t *)&data_in, 32); + /* And finally the address component */ + 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; +} + +static bool riscv_dmi_transfer(riscv_dmi_s *const dmi, const uint8_t operation, const uint32_t address, + const uint32_t data_in, uint32_t *const data_out) +{ + /* Try the transfer */ + uint8_t status = riscv_shift_dmi(dmi, operation, address, data_in, data_out); + + /* Handle status == 3 (RV_DMI_TOO_SOON) */ + if (status == RV_DMI_TOO_SOON) { + /* + * If we got RV_DMI_TOO_SOON and we're under 8 idle cycles, increase the number + * of idle cycles used to compensate and have the outer code re-run the transnfers + */ + if (dmi->idle_cycles < 8) + ++dmi->idle_cycles; + /* + * Otherwise we've hit 8 idle cycles, it doesn't matter if we get another + * RV_DMI_TOO_SOON, treat that as a hard error and bail out. + */ + else + status = RV_DMI_FAILURE; + } + + dmi->fault = status; + /* If we get straight failure, do a DMI reset */ + if (status == RV_DMI_FAILURE || status == RV_DMI_TOO_SOON) + riscv_dmi_reset(dmi); + return status == RV_DMI_SUCCESS; +} + +static 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, 0, NULL); + if (result) + /* If that worked, read back the value and check the operation status */ + result = riscv_dmi_transfer(dmi, RV_DMI_NOOP, 0, 0, value); + return result; +} + +static 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); + if (result) + /* If that worked, read back the operation status to ensure the write actually worked */ + result = riscv_dmi_transfer(dmi, RV_DMI_NOOP, 0, 0, NULL); + return result; +} + +static riscv_debug_version_e riscv_dtmcs_version(const uint32_t dtmcs) +{ + uint8_t version = dtmcs & RV_STATUS_VERSION_MASK; + switch (version) { + case 0: + DEBUG_INFO("RISC-V debug v0.11 DMI\n"); + return RISCV_DEBUG_0_11; + case 1: + /* The stable version of the spec (v1.0) does not currently provide a way to distinguish between */ + /* a device built against v0.13 of the spec or v1.0 of the spec. They use the same value here. */ + DEBUG_INFO("RISC-V debug v0.13/v1.0 DMI\n"); + return RISCV_DEBUG_0_13; + } + DEBUG_INFO("Please report part with unknown RISC-V debug DMI version %x\n", version); + return RISCV_DEBUG_UNKNOWN; +} + +static void riscv_jtag_prepare(target_s *const target) +{ + riscv_hart_s *const hart = riscv_hart_struct(target); + /* We put the TAP into bypass at the end of the JTAG handler, so put it back into DMI */ + jtag_dev_write_ir(hart->dbg_module->dmi_bus->dev_index, IR_DMI); +} + +static void riscv_jtag_quiesce(target_s *const target) +{ + riscv_hart_s *const hart = riscv_hart_struct(target); + /* On detaching, stick the TAP back into bypass */ + jtag_dev_write_ir(hart->dbg_module->dmi_bus->dev_index, IR_BYPASS); +} diff --git a/src/target/rp.c b/src/target/rp.c index 744f27b5e49..a38450dc23e 100644 --- a/src/target/rp.c +++ b/src/target/rp.c @@ -235,7 +235,6 @@ bool rp_probe(target_s *target) } target->target_storage = (void *)priv_storage; - target->mass_erase = bmp_spi_mass_erase; target->driver = "Raspberry RP2040"; target->target_options |= CORTEXM_TOPT_INHIBIT_NRST; target->attach = rp_attach; diff --git a/src/target/samd.c b/src/target/samd.c index 6beb4cd73e2..df9e05aa7bd 100644 --- a/src/target/samd.c +++ b/src/target/samd.c @@ -44,7 +44,7 @@ static bool samd_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); static bool samd_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len); /* NB: This is not marked static on purpose as it's used by samx5x.c. */ -bool samd_mass_erase(target_s *t); +bool samd_mass_erase(target_s *target, platform_timeout_s *print_progess); static bool samd_cmd_lock_flash(target_s *t, int argc, const char **argv); static bool samd_cmd_unlock_flash(target_s *t, int argc, const char **argv); @@ -594,15 +594,15 @@ static bool samd_wait_nvm_ready(target_s *t) return true; } -static bool samd_wait_dsu_ready(target_s *const t, uint32_t *const result, platform_timeout_s *const timeout) +static bool samd_wait_dsu_ready(target_s *const t, uint32_t *const result, platform_timeout_s *const print_progress) { uint32_t status = 0; while ((status & (SAMD_STATUSA_DONE | SAMD_STATUSA_PERR | SAMD_STATUSA_FAIL)) == 0) { status = target_mem_read32(t, SAMD_DSU_CTRLSTAT); if (target_check_error(t)) return false; - if (timeout) - target_print_progress(timeout); + if (print_progress) + target_print_progress(print_progress); } *result = status; return true; @@ -659,23 +659,21 @@ static bool samd_flash_write(target_flash_s *f, target_addr_t dest, const void * } /* Uses the Device Service Unit to erase the entire flash */ -bool samd_mass_erase(target_s *t) +bool samd_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { /* Clear the DSU status bits */ - target_mem_write32(t, SAMD_DSU_CTRLSTAT, SAMD_STATUSA_DONE | SAMD_STATUSA_PERR | SAMD_STATUSA_FAIL); + target_mem_write32(target, SAMD_DSU_CTRLSTAT, SAMD_STATUSA_DONE | SAMD_STATUSA_PERR | SAMD_STATUSA_FAIL); /* Erase all */ - target_mem_write32(t, SAMD_DSU_CTRLSTAT, SAMD_CTRL_CHIP_ERASE); + target_mem_write32(target, SAMD_DSU_CTRLSTAT, SAMD_CTRL_CHIP_ERASE); uint32_t status = 0; - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); - if (!samd_wait_dsu_ready(t, &status, &timeout)) + if (!samd_wait_dsu_ready(target, &status, print_progess)) return false; /* Test the protection error bit in Status A */ if (status & SAMD_STATUSA_PERR) { - tc_printf(t, "Erase failed due to a protection error.\n"); + tc_printf(target, "Erase failed due to a protection error.\n"); return true; } diff --git a/src/target/samx5x.c b/src/target/samx5x.c index bcf45e20b58..eff313bac5b 100644 --- a/src/target/samx5x.c +++ b/src/target/samx5x.c @@ -52,8 +52,7 @@ static bool samx5x_cmd_ssb(target_s *t, int argc, const char **argv); static bool samx5x_cmd_update_user_word(target_s *t, int argc, const char **argv); /* (The SAM D1x/2x implementation of erase_all is reused as it's identical)*/ -bool samd_mass_erase(target_s *t); -#define samx5x_mass_erase samd_mass_erase +extern bool samd_mass_erase(target_s *target, platform_timeout_s *print_progess); #ifdef SAMX5X_EXTRA_CMDS static bool samx5x_cmd_mbist(target_s *t, int argc, const char **argv); @@ -194,7 +193,6 @@ const command_s samx5x_cmd_list[] = { * (Reuses the SAM D1x/2x implementation as it is identical) */ extern void samd_reset(target_s *t); -#define samx5x_reset samd_reset /* * Overload the default cortexm attach for when the samd is protected. @@ -207,7 +205,6 @@ extern void samd_reset(target_s *t); * (Reuses the SAM D1x/2x implementation as it is identical) */ extern bool samd_protected_attach(target_s *t); -#define samx5x_protected_attach samd_protected_attach /* * Use the DSU Device Identification Register to populate a struct @@ -348,8 +345,8 @@ bool samx5x_probe(target_s *t) /* Setup Target */ t->driver = priv_storage->samx5x_variant_string; - t->reset = samx5x_reset; - t->mass_erase = samx5x_mass_erase; + t->reset = samd_reset; + t->mass_erase = samd_mass_erase; /* * Overload the default cortexm attach when the samx5x is protected. @@ -357,7 +354,7 @@ bool samx5x_probe(target_s *t) * can rescue the device. */ if (protected) - t->attach = samx5x_protected_attach; + t->attach = samd_protected_attach; switch (samx5x.mem) { default: diff --git a/src/target/spi.c b/src/target/spi.c index 63d38dbe8b2..0e968d9edec 100644 --- a/src/target/spi.c +++ b/src/target/spi.c @@ -129,8 +129,10 @@ spi_flash_s *bmp_spi_add_flash(target_s *const target, const target_addr_t begin flash->start = begin; flash->length = spi_parameters.capacity; flash->blocksize = spi_parameters.sector_size; + /* FIXME: this can be optimized with prepare done! */ flash->write = bmp_spi_flash_write; flash->erase = bmp_spi_flash_erase; + flash->mass_erase = bmp_spi_mass_erase; flash->erased = 0xffU; target_add_flash(target, flash); @@ -142,29 +144,24 @@ spi_flash_s *bmp_spi_add_flash(target_s *const target, const target_addr_t begin return spi_flash; } -/* Note: These routines assume that the first Flash registered on the target is a SPI Flash device */ -bool bmp_spi_mass_erase(target_s *const target) +bool bmp_spi_mass_erase(target_flash_s *const flash, platform_timeout_s *const print_progess) { - /* Extract the Flash structure and set up timeouts */ - const spi_flash_s *const flash = (spi_flash_s *)target->flash; - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); - DEBUG_TARGET("Running %s\n", __func__); - /* Go into Flash mode and tell the Flash to enable writing */ - target->enter_flash_mode(target); - flash->run_command(target, SPI_FLASH_CMD_WRITE_ENABLE, 0U); - if (!(bmp_spi_read_status(target, flash) & SPI_FLASH_STATUS_WRITE_ENABLED)) { - target->exit_flash_mode(target); + target_s *const target = flash->t; + const spi_flash_s *const spi_flash = (spi_flash_s *)flash; + + /* Tell the Flash to enable writing */ + spi_flash->run_command(target, SPI_FLASH_CMD_WRITE_ENABLE, 0U); + if (!(bmp_spi_read_status(target, spi_flash) & SPI_FLASH_STATUS_WRITE_ENABLED)) return false; - } - /* Execute a full chip erase and wait for the operatoin to complete */ - flash->run_command(target, SPI_FLASH_CMD_CHIP_ERASE, 0U); - while (bmp_spi_read_status(target, flash) & SPI_FLASH_STATUS_BUSY) - target_print_progress(&timeout); + /* Execute a full chip erase and wait for the operation to complete */ + spi_flash->run_command(target, SPI_FLASH_CMD_CHIP_ERASE, 0U); + while (bmp_spi_read_status(target, spi_flash) & SPI_FLASH_STATUS_BUSY) { + if (print_progess) + target_print_progress(print_progess); + } - /* Finally, leave Flash mode to conclude business */ - return target->exit_flash_mode(target); + return true; } static bool bmp_spi_flash_erase(target_flash_s *const flash, const target_addr_t addr, const size_t length) diff --git a/src/target/spi.h b/src/target/spi.h index 410841c3403..b4a2e4f73ae 100644 --- a/src/target/spi.h +++ b/src/target/spi.h @@ -92,6 +92,6 @@ void bmp_spi_run_command(spi_bus_e bus, uint8_t device, uint16_t command, target spi_flash_s *bmp_spi_add_flash(target_s *target, target_addr_t begin, size_t length, spi_read_func spi_read, spi_write_func spi_write, spi_run_command_func spi_run_command); -bool bmp_spi_mass_erase(target_s *target); +bool bmp_spi_mass_erase(target_flash_s *flash, platform_timeout_s *print_progess); #endif /* TARGET_SPI_H */ diff --git a/src/target/stm32_flash.c b/src/target/stm32_flash.c new file mode 100644 index 00000000000..5a22b3bf2c3 --- /dev/null +++ b/src/target/stm32_flash.c @@ -0,0 +1,443 @@ +/* + * 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 "stm32_flash.h" +#include "cortexm.h" + +/* Code flash routines */ + +bool stm32_flash_locked(target_s *const target, const uint32_t fpec_base, const uint8_t bank) +{ + const uint32_t ctrl = target_mem_read32(target, STM32_FLASH_CR_BANK(fpec_base, bank)); + return ctrl & STM32_FLASH_CR_LOCK; +} + +bool stm32_flash_unlock(target_s *const target, const uint32_t fpec_base, const uint8_t bank) +{ + target_mem_write32(target, STM32_FLASH_KEYR_BANK(fpec_base, bank), STM32_FLASH_KEY1); + target_mem_write32(target, STM32_FLASH_KEYR_BANK(fpec_base, bank), STM32_FLASH_KEY2); + + /* Check that the bank is unlocked */ + if (stm32_flash_locked(target, fpec_base, bank)) { + DEBUG_ERROR("stm32 flash unlock failed\n"); + return false; + } + return true; +} + +void stm32_flash_lock(target_s *const target, const uint32_t fpec_base, const uint8_t bank) +{ + const uint32_t ctrl = target_mem_read32(target, STM32_FLASH_CR_BANK(fpec_base, bank)); + /* Retain the EOPIE, ERRIE and OPTWRE bits, and set the LOCK bit */ + target_mem_write32(target, STM32_FLASH_CR_BANK(fpec_base, bank), + (ctrl & (STM32_FLASH_CR_EOPIE | STM32_FLASH_CR_ERRIE | STM32_FLASH_CR_OPTWRE)) | STM32_FLASH_CR_LOCK); +} + +void stm32_flash_clear_status(target_s *const target, const uint32_t fpec_base, const uint8_t bank) +{ + /* EOP, WRPRTERR and PGERR are reset by writing 1 */ + target_mem_write32(target, STM32_FLASH_SR_BANK(fpec_base, bank), + STM32_FLASH_SR_EOP | STM32_FLASH_SR_PGERR | STM32_FLASH_SR_WRPRTERR); +} + +bool stm32_flash_busy_wait( + target_s *const target, const uint32_t fpec_base, const uint8_t bank, platform_timeout_s *const print_progess) +{ + /* Read FLASH_SR to poll for BSY bit */ + uint32_t status = STM32_FLASH_SR_BSY; + /* + * Note that checking EOP here is only legal ifevery operation is preceded by a call to + * stm32_flash_clear_status, without it the flag could be set from a previous operation. + * + * See §3.4 Flash status register (FLASH_SR) in PM0068/PM0075 Programming manual + */ + while (!(status & STM32_FLASH_SR_EOP) && (status & STM32_FLASH_SR_BSY)) { + status = target_mem_read32(target, STM32_FLASH_SR_BANK(fpec_base, bank)); + if (target_check_error(target)) { + DEBUG_ERROR("Lost communications with target"); + return false; + } + if (print_progess) + target_print_progress(print_progess); + }; + + /* Check for errors */ + const uint32_t error = status & (STM32_FLASH_SR_PGERR | STM32_FLASH_SR_WRPRTERR); + if (error) + DEBUG_ERROR("stm32 flash error 0x%" PRIx32 "\n", error); + return error == 0U; +} + +static bool stm32_flash_prepare(target_flash_s *const flash) +{ + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* Unlock the flash bank if required */ + if (stm32_flash_locked(target, stm32_flash->fpec_base, stm32_flash->bank) && + !stm32_flash_unlock(target, stm32_flash->fpec_base, stm32_flash->bank)) + return false; + + /* Ensure no operation is ongoing */ + if (target_mem_read32(target, STM32_FLASH_SR_BANK(stm32_flash->fpec_base, stm32_flash->bank)) & + STM32_FLASH_SR_BSY) { + DEBUG_ERROR("stm32 flash unexpectedly busy\n"); + return false; /* FIXME: should we re-lock here? */ + } + + /* Clear any previous operation status */ + stm32_flash_clear_status(target, stm32_flash->fpec_base, stm32_flash->bank); + + /* Set the instruction to the control register */ + uint32_t ctrl_instruction = 0U; + switch (flash->operation) { + case FLASH_OPERATION_WRITE: + /* Set flash programming instruction */ + ctrl_instruction = STM32_FLASH_CR_PG; + break; + case FLASH_OPERATION_ERASE: + /* Set flash page erase instruction */ + ctrl_instruction = STM32_FLASH_CR_PER_SER; + break; + case FLASH_OPERATION_MASS_ERASE: + /* Set flash bank mass erase instruction */ + ctrl_instruction = STM32_FLASH_CR_MER; + break; + default: + return false; /* Unsupported operation */ + } + /* + * This will clear EOPIE, ERRIE and OPTWRE, but we don't care about them and expect them cleared anyway + * note that we don't read-modify-write the control register after this, meaning we need to set the instruction + * allways, this is to avoid the extra overhead of reading the register since we know what bits should be set. + */ + target_mem_write32(target, STM32_FLASH_CR_BANK(stm32_flash->fpec_base, stm32_flash->bank), ctrl_instruction); + + return true; +} + +static bool stm32_flash_done(target_flash_s *const flash) +{ + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* Lock the flash bank */ + stm32_flash_lock(target, stm32_flash->fpec_base, stm32_flash->bank); + + return true; +} + +static bool stm32_flash_erase(target_flash_s *const flash, const target_addr_t addr, const size_t length) +{ + (void)length; + + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* See §2.3.4 Flash memory erase in PM0068/PM0075 Programming manual */ + + /* Clear any previous operation status */ + stm32_flash_clear_status(target, stm32_flash->fpec_base, stm32_flash->bank); + + /* Write page address to address register */ + target_mem_write32(target, STM32_FLASH_AR_BANK(stm32_flash->fpec_base, stm32_flash->bank), addr); + + /* Start flash page erase instruction */ + target_mem_write32(target, STM32_FLASH_CR_BANK(stm32_flash->fpec_base, stm32_flash->bank), + STM32_FLASH_CR_STRT | STM32_FLASH_CR_PER_SER); + + /* Wait for completion or an error, return false on error */ + return stm32_flash_busy_wait(target, stm32_flash->fpec_base, stm32_flash->bank, NULL); +} + +static bool stm32_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len) +{ + (void)len; + + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* See §2.3.3 Main Flash memory programming in PM0068/PM0075 Programming manual */ + + /* Clear any previous operation status */ + stm32_flash_clear_status(target, stm32_flash->fpec_base, stm32_flash->bank); + + /* + * The operatio is started on a half-word write into a main Flash memory address. + * Any attempt to write data that are not half-word long will result in a bus error response from the FPEC. + */ + target_mem_write16( + target, dest, *(const uint16_t *)src); // target_mem_write_aligned(target, dest, src, len, ALIGN_HALFWORD); + + return stm32_flash_busy_wait(target, stm32_flash->fpec_base, stm32_flash->bank, NULL); +} + +bool stm32_flash_mass_erase(target_flash_s *const flash, platform_timeout_s *const print_progess) +{ + stm32_flash_s *const stm32_flash = (stm32_flash_s *)flash; + target_s *const target = flash->t; + + /* Start flash bank mass erase instruction */ + target_mem_write32(target, STM32_FLASH_CR_BANK(stm32_flash->fpec_base, stm32_flash->bank), + STM32_FLASH_CR_STRT | STM32_FLASH_CR_MER); + + /* Wait for completion or an error, return false on error */ + return stm32_flash_busy_wait(target, stm32_flash->fpec_base, stm32_flash->bank, print_progess); +} + +static void stm32_add_flash_bank(target_s *const target, target_flash_s *const flash, const target_addr_t addr, + const size_t length, const size_t block_size) +{ + flash->start = addr; + flash->length = length; + flash->blocksize = block_size; + flash->writesize = 2U; /* The smallest write size is 16 bits, in the interest of speed we might want to bump this */ + flash->erased = 0xffU; + flash->erase = stm32_flash_erase; + flash->mass_erase = stm32_flash_mass_erase; + flash->write = stm32_flash_write; + flash->prepare = stm32_flash_prepare; + flash->done = stm32_flash_done; + + target_add_flash(target, flash); +} + +void stm32_add_flash(target_s *target, target_addr_t addr, size_t length, uint32_t fpec_base, size_t block_size) +{ + stm32_flash_s *const stm32_flash = calloc(1U, sizeof(*stm32_flash)); + if (!stm32_flash) { /* calloc failed: heap exhaustion */ + DEBUG_ERROR("calloc: failed in %s\n", __func__); + return; + } + stm32_flash->fpec_base = fpec_base; + + stm32_add_flash_bank(target, &stm32_flash->flash, addr, length, block_size); +} + +void stm32_add_banked_flash(target_s *target, target_addr_t addr, size_t length, target_addr_t bank_split_addr, + uint32_t fpec_base, size_t block_size) +{ + /* Allocate two sequential flash structures, one for each bank */ + stm32_flash_s *const stm32_flash = calloc(2U, sizeof(*stm32_flash)); + if (!stm32_flash) { /* calloc failed: heap exhaustion */ + DEBUG_ERROR("calloc: failed in %s\n", __func__); + return; + } + + /* Add the two flash banks */ + for (size_t bank = 0; bank < 2U; bank++) { + stm32_flash[bank].fpec_base = fpec_base; + stm32_flash[bank].bank = bank; + + const target_addr_t bank_addr = bank == 0U ? addr : bank_split_addr; + const size_t bank_length = bank == 0U ? bank_split_addr - addr : length - (bank_split_addr - addr); + + stm32_add_flash_bank(target, &stm32_flash[bank].flash, bank_addr, bank_length, block_size); + } +} + +/* Option byte flash routines */ + +bool stm32_option_bytes_locked(target_s *const target, const uint32_t fpec_base) +{ + const uint32_t ctrl = target_mem_read32(target, STM32_FLASH_CR(fpec_base)); + return !(ctrl & STM32_FLASH_CR_OPTWRE); +} + +bool stm32_option_bytes_unlock(target_s *const target, const uint32_t fpec_base) +{ + target_mem_write32(target, STM32_FLASH_OPTKEYR(fpec_base), STM32_FLASH_KEY1); + target_mem_write32(target, STM32_FLASH_OPTKEYR(fpec_base), STM32_FLASH_KEY2); + + /* Check that the bank is unlocked */ + if (stm32_option_bytes_locked(target, fpec_base)) { + DEBUG_ERROR("stm32 option bytes unlock failed\n"); + return false; + } + return true; +} + +static bool stm32_option_bytes_erase(target_s *const target, const uint32_t fpec_base) +{ + /* Clear any previous operation status */ + stm32_flash_clear_status(target, fpec_base, 0U); + + /* Set option byte erase instruction */ + target_mem_write32(target, STM32_FLASH_CR(fpec_base), STM32_FLASH_CR_OPTWRE | STM32_FLASH_CR_OPTER); + + /* Start option byte erase instruction */ + target_mem_write32( + target, STM32_FLASH_CR(fpec_base), STM32_FLASH_CR_OPTWRE | STM32_FLASH_CR_OPTER | STM32_FLASH_CR_STRT); + + /* Wait for completion or an error, return false on error */ + return stm32_flash_busy_wait(target, fpec_base, 0U, NULL); +} + +static bool stm32_option_bytes_write( + target_s *const target, const uint32_t fpec_base, const size_t offset, const uint16_t value) +{ + if (value == 0xffffU) + return true; + + /* Clear any previous operation status */ + stm32_flash_clear_status(target, fpec_base, 0U); + + /* Set option byte programming instruction */ + target_mem_write32(target, STM32_FLASH_CR(fpec_base), STM32_FLASH_CR_OPTWRE | STM32_FLASH_CR_OPTPG); + + const uint32_t addr = STM32_FLASH_OPT_ADDR + (offset * 2U); + + /* + * Write changed values, taking into account if we can use 32- or have to use 16-bit writes. + * GD32E230 is a special case as target_mem_write16 does not work + */ + const bool write16_broken = target->part_id == 0x410U && (target->cpuid & CORTEX_CPUID_PARTNO_MASK) == CORTEX_M23; + if (write16_broken) + target_mem_write32(target, addr, 0xffff0000U | value); + else + target_mem_write16(target, addr, value); + + /* Wait for completion or an error, return false on error */ + const bool result = stm32_flash_busy_wait(target, fpec_base, 0U, NULL); + if (offset != 0U || result) + return result; + /* + * FIXME: I don't follow the explanation here, clarify + * In the case that the write failed and we're handling option byte 0 (RDP), + * check if we got a status of "Program Error" in FLASH_SR, indicating the target + * refused to erase the read protection option bytes (and turn it into a truthy return). + */ + const bool programming_error = !!(target_mem_read32(target, STM32_FLASH_SR(fpec_base)) & STM32_FLASH_SR_PGERR); + return programming_error; +} + +static bool stm32_option_bytes_read_modify_write( + target_s *const target, const uint32_t fpec_base, const uint32_t addr, const uint16_t value) +{ + const uint32_t index = (addr - STM32_FLASH_OPT_ADDR) >> 1U; + /* If index would be negative, the high most bit is set, so we get a giant positive number. */ + if (index > 7U) + return false; + + uint16_t option_bytes[8U]; + + /* Retrieve old values */ + target_mem_read(target, option_bytes, STM32_FLASH_OPT_ADDR, sizeof(option_bytes)); + + if (option_bytes[index] == value) + return true; + + /* Check for erased value */ + if (option_bytes[index] != 0xffffU && !stm32_option_bytes_erase(target, fpec_base)) + return false; + + option_bytes[index] = value; + + /* Write the modified option bytes */ + for (size_t byte_index = 0U; byte_index < 8U; ++byte_index) { + if (!stm32_option_bytes_write(target, fpec_base, byte_index, option_bytes[byte_index])) + return false; + } + + return true; +} + +static inline uint16_t stm32_option_bytes_read_protect_key(const target_s *const target) +{ + /* FIXME: there must be a better way */ + switch (target->part_id) { + case 0x422U: /* STM32F30x */ + case 0x432U: /* STM32F37x */ + case 0x438U: /* STM32F303x6/8 and STM32F328 */ + case 0x440U: /* STM32F0 */ + case 0x446U: /* STM32F303xD/E and STM32F398xE */ + case 0x445U: /* STM32F04 RM0091 Rev.7, STM32F070x6 RM0360 Rev. 4*/ + case 0x448U: /* STM32F07 RM0091 Rev.7, STM32F070xb RM0360 Rev. 4*/ + case 0x442U: /* STM32F09 RM0091 Rev.7, STM32F030xc RM0360 Rev. 4*/ + return STM32F3X_FLASH_RDPRT; + } + return STM32F10X_FLASH_RDPRT; +} + +bool stm32_option_bytes_cmd(target_s *const target, int argc, const char **argv) +{ + const uint32_t fpec_base = ((stm32_flash_s *)target->flash)->fpec_base; /* Get the FPEC base from a target flash */ + + /* Fast-exit if the Flash is not readable and the user didn't ask us to erase the option bytes */ + const bool erase_requested = argc == 2 && strcmp(argv[1], "erase") == 0; + if (!erase_requested) { + const bool read_protected = + !!(target_mem_read32(target, STM32_FLASH_OBR(fpec_base)) & STM32F10X_FLASH_OBR_RDPRT); + + if (read_protected) { + tc_printf(target, "Device is Read Protected\nUse `monitor option erase` to unprotect and erase device\n"); + return true; + } + } + + /* Unlock the flash if required */ + if (stm32_flash_locked(target, fpec_base, 0U) && !stm32_flash_unlock(target, fpec_base, 0U)) + return false; + + /* Unlock the option bytes if required */ + if (stm32_option_bytes_locked(target, fpec_base) && !stm32_option_bytes_unlock(target, fpec_base)) + return false; + + if (erase_requested) { + /* When the user asks us to erase the option bytes, kick of an erase */ + if (!stm32_option_bytes_erase(target, fpec_base)) + return false; + + /* Write the option bytes Flash readable key */ + if (!stm32_option_bytes_write(target, fpec_base, 0U, stm32_option_bytes_read_protect_key(target))) + return false; + } else if (argc == 3) { + /* If 3 arguments are given, assume the second is an address, and the third a value */ + const uint32_t addr = strtoul(argv[1], NULL, 0); + const uint32_t value = strtoul(argv[2], NULL, 0); + + /* Try and program the new option value to the requested option byte */ + if (!stm32_option_bytes_read_modify_write(target, fpec_base, addr, value)) + return false; + } else + tc_printf(target, "usage: monitor option erase\nusage: monitor option \n"); + + /* When all gets said and done, display the current option bytes values */ + for (size_t offset = 0U; offset < 16U; offset += 4U) { + const uint32_t addr = STM32_FLASH_OPT_ADDR + offset; + const uint32_t val = target_mem_read32(target, addr); + tc_printf(target, "0x%08X: 0x%04X\n", addr, val & 0xffffU); + tc_printf(target, "0x%08X: 0x%04X\n", addr + 2U, val >> 16U); + } + + return true; +} diff --git a/src/target/stm32_flash.h b/src/target/stm32_flash.h new file mode 100644 index 00000000000..88b3c6e97ca --- /dev/null +++ b/src/target/stm32_flash.h @@ -0,0 +1,270 @@ +/* + * 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 TARGET_STM32_FLASH_H +#define TARGET_STM32_FLASH_H + +#include "general.h" +#include "target.h" +#include "target_internal.h" + +/* + * Flash program and erase controller (FPEC) + * + * This accounts for STM32F1x, STM32F3x and STM32F4x series + * + * This is based on the PM0068 and PM0075 ST programing manuals + * https://www.st.com/resource/en/programming_manual/pm0068-stm32f10xxx-xldensity-flash-programming-stmicroelectronics.pdf + * https://www.st.com/resource/en/programming_manual/pm0075-stm32f10xxx-flash-memory-microcontrollers-stmicroelectronics.pdf + */ +#define STM32F10X_FPEC_BASE 0x40022000U +#define STM32F4X_FPEC_BASE 0x40023c00U +#define STM32_FLASH_BANK_OFFSET 0x40U + +/* Flash access control register (FLASH_ACR) */ +#define STM32_FLASH_ACR_OFFSET 0x00U +#define STM32_FLASH_ACR(fpec_base) ((fpec_base) + STM32_FLASH_ACR_OFFSET) + +/* STM32F4x Only */ +#define STM32F4X_FLASH_ACR_DCRST (1U << 12U) /* Data cache reset (1: reset, 0: not reset) */ +#define STM32F4X_FLASH_ACR_ICRST (1U << 11U) /* Instruction cache reset (1: reset, 0: not reset) */ +#define STM32F4X_FLASH_ACR_DCEN (1U << 10U) /* Data cache enable (1: enabled, 0: disabled) */ +#define STM32F4X_FLASH_ACR_ICEN (1U << 9U) /* Instruction cache enable (1: enabled, 0: disabled) */ +#define STM32F4X_FLASH_ACR_PRFTEN (1U << 8U) /* Prefetch enable (1: enabled, 0: disabled) */ +/* STM32F1x/F3x Only */ +#define STM32F10X_FLASH_ACR_PRFTBS (1U << 5U) /* Prefetch buffer status (1: enabled, 0: disabled) */ +#define STM32F10X_FLASH_ACR_PRFTBE (1U << 4U) /* Prefetch buffer enable (1: enable, 0: disable) */ +#define STM32F10X_FLASH_ACR_HLFCYA (1U << 3U) /* Flash half cycle access enable (1: enabled, 0: disabled) */ + +#define STM32_FLASH_ACR_LATENCY_MASK 0x7U /* Latency */ +#define STM32_FLASH_ACR_LATENCY_0WS 0U /* 0b000 - Zero wait state (STM32F10X: 0 < SYSCLK≤ 24 MHz) */ +#define STM32_FLASH_ACR_LATENCY_1WS 1U /* 0b001 - One wait state (STM32F10X: 24 MHz < SYSCLK ≤ 48 MHz) */ +#define STM32_FLASH_ACR_LATENCY_2WS 2U /* 0b010 - Two wait states (STM32F10X: 48 MHz < SYSCLK ≤ 72 MHz) */ +#define STM32_FLASH_ACR_LATENCY_3WS 3U /* 0b011 - Three wait states (STM32F4x Only) */ +#define STM32_FLASH_ACR_LATENCY_4WS 4U /* 0b100 - Four wait states (STM32F4x Only) */ +#define STM32_FLASH_ACR_LATENCY_5WS 5U /* 0b101 - Five wait states (STM32F4x Only) */ +#define STM32_FLASH_ACR_LATENCY_6WS 6U /* 0b110 - Six wait states (STM32F4x Only) */ +#define STM32_FLASH_ACR_LATENCY_7WS 7U /* 0b111 - Seven wait states (STM32F4x Only) */ + +/* FPEC key register (FLASH_KEYR) */ +#define STM32_FLASH_KEYR_OFFSET 0x04U +#define STM32_FLASH_KEYR(fpec_base) ((fpec_base) + STM32_FLASH_KEYR_OFFSET) +#define STM32_FLASH_KEYR_BANK(fpec_base, bank) (STM32_FLASH_KEYR(fpec_base) + (bank)*STM32_FLASH_BANK_OFFSET) + +#define STM32_FLASH_KEY1 0x45670123U +#define STM32_FLASH_KEY2 0xcdef89abU + +/* Flash OPTKEY register (FLASH_OPTKEYR) */ +#define STM32_FLASH_OPTKEYR_OFFSET 0x08U +#define STM32_FLASH_OPTKEYR(fpec_base) ((fpec_base) + STM32_FLASH_OPTKEYR_OFFSET) + +#define STM32F4X_FLASH_OPTKEY1 0x08192a3bU +#define STM32F4X_FLASH_OPTKEY2 0x4c5d6e7fU + +/* Flash status register (FLASH_SR) */ +#define STM32_FLASH_SR_OFFSET 0x0cU +#define STM32_FLASH_SR(fpec_base) ((fpec_base) + STM32_FLASH_SR_OFFSET) +#define STM32_FLASH_SR_BANK(fpec_base, bank) (STM32_FLASH_SR(fpec_base) + (bank)*STM32_FLASH_BANK_OFFSET) + +#define STM32_FLASH_SR_WRPRTERR (1U << 4U) /* Write protection error */ +// /* STM32F4x Only */ +// #define STM32F4X_FLASH_SR_BSY (1U << 16U) /* Busy */ +// #define STM32F4X_FLASH_SR_RDERR (1U << 8U) /* Proprietary readout protection error (STM32F42/3x Only) */ +// #define STM32F4X_FLASH_SR_PGSERR (1U << 7U) /* Programming sequence error */ +// #define STM32F4X_FLASH_SR_PGPERR (1U << 6U) /* Programming parallelism error */ +// #define STM32F4X_FLASH_SR_PGAERR (1U << 5U) /* Programming alignment error */ +// #define STM32F4X_FLASH_SR_OPERR (1U << 1U) /* Operation error */ +// #define STM32F4X_FLASH_SR_EOP (1U << 0U) /* End of operation */ +/* STM32F1x/F3x Only */ +// #define STM32F10X_FLASH_SR_EOP (1U << 5U) /* End of operation */ +// #define STM32F10X_FLASH_SR_PGERR (1U << 2U) /* Programming error */ +// #define STM32F10X_FLASH_SR_BSY (1U << 0U) /* Busy operation */ +#define STM32_FLASH_SR_EOP (1U << 5U) /* End of operation */ +#define STM32_FLASH_SR_PGERR (1U << 2U) /* Programming error */ +#define STM32_FLASH_SR_BSY (1U << 0U) /* Busy operation */ + +/* Flash control register (FLASH_CR) */ +#define STM32_FLASH_CR_OFFSET 0x10U +#define STM32_FLASH_CR(fpec_base) ((fpec_base) + STM32_FLASH_CR_OFFSET) +#define STM32_FLASH_CR_BANK(fpec_base, bank) (STM32_FLASH_CR(fpec_base) + (bank)*STM32_FLASH_BANK_OFFSET) + +#define STM32_FLASH_CR_MER (1U << 2U) /* Mass erase bank */ +#define STM32_FLASH_CR_PER_SER (1U << 1U) /* Page / Sector erase */ +#define STM32_FLASH_CR_PG (1U << 0U) /* Programming */ +// /* STM32F4x Only */ +// #define STM32F4X_FLASH_CR_LOCK (1U << 31) /* Lock */ +// #define STM32F4X_FLASH_CR_ERRIE (1U << 25U) /* Error interrupt enable */ +// #define STM32F4X_FLASH_CR_EOPIE (1U << 24U) /* End of operation interrupt enable */ +// #define STM32F4X_FLASH_CR_STRT (1U << 16U) /* Start */ +// #define STM32F4X_FLASH_CR_MER2 (1U << 15U) /* Mass Erase of bank 2 (STM32F42/3x Only) */ +// #define STM32F4X_FLASH_CR_PSIZE_OFFSET 8U /* Program parallelism */ +// #define STM32F4X_FLASH_CR_PSIZE_MASK (0x3U << STM32F4X_FLASH_CR_PSIZE_OFFSET) /* Program parallelism */ +// #define STM32F4X_FLASH_CR_PSIZE_8BIT (0x0U << STM32F4X_FLASH_CR_PSIZE_OFFSET) /* 0b00 program x8 */ +// #define STM32F4X_FLASH_CR_PSIZE_16BIT (0x1U << STM32F4X_FLASH_CR_PSIZE_OFFSET) /* 0b01 program x16 */ +// #define STM32F4X_FLASH_CR_PSIZE_32BIT (0x2U << STM32F4X_FLASH_CR_PSIZE_OFFSET) /* 0b10 program x32 */ +// #define STM32F4X_FLASH_CR_PSIZE_64BIT (0x3U << STM32F4X_FLASH_CR_PSIZE_OFFSET) /* 0b11 program x64 */ +// #define STM32F4X_FLASH_CR_SNB_OFFSET 3U /* Sector number */ +// #define STM32F4X_FLASH_CR_SNB_MASK (0x1fU << STM32F4X_FLASH_CR_SNB_OFFSET) /* Sector number */ +// /* STM32F1x/F3x Only */ +// #define STM32F10X_FLASH_CR_EOPIE (1U << 12U) /* End of operation interrupt enable */ +// #define STM32F10X_FLASH_CR_ERRIE (1U << 10U) /* Error interrupt enable */ +// #define STM32F10X_FLASH_CR_OPTWRE (1U << 9U) /* Option bytes write enable */ +// #define STM32F10X_FLASH_CR_LOCK (1U << 7U) /* Lock */ +// #define STM32F10X_FLASH_CR_STRT (1U << 6U) /* Start */ +// #define STM32F10X_FLASH_CR_OPTER (1U << 5U) /* Option byte erase */ +// #define STM32F10X_FLASH_CR_OPTPG (1U << 4U) /* Option byte programming */ +#define STM32_FLASH_CR_EOPIE (1U << 12U) /* End of operation interrupt enable */ +#define STM32_FLASH_CR_ERRIE (1U << 10U) /* Error interrupt enable */ +#define STM32_FLASH_CR_OPTWRE (1U << 9U) /* Option bytes write enable */ +#define STM32_FLASH_CR_LOCK (1U << 7U) /* Lock */ +#define STM32_FLASH_CR_STRT (1U << 6U) /* Start */ +#define STM32_FLASH_CR_OPTER (1U << 5U) /* Option byte erase */ +#define STM32_FLASH_CR_OPTPG (1U << 4U) /* Option byte programming */ + +/* OBL_LAUNCH is not available on all families */ +#define STM32_FLASH_CR_OBL_LAUNCH (1U << 13U) /* Force the option byte loading (generates system reset) */ + +/* Flash address register (FLASH_AR) (STM32F1x/F3x Only) */ +// #define STM32F10X_FLASH_AR_OFFSET 0x14U +// #define STM32F10X_FLASH_AR(fpec_base) ((fpec_base) + STM32_FLASH_AR_OFFSET) +// #define STM32F10X_FLASH_AR_BANK(fpec_base, bank) (STM32_FLASH_AR(fpec_base) + (bank) * STM32_FLASH_BANK_OFFSET) +#define STM32_FLASH_AR_OFFSET 0x14U +#define STM32_FLASH_AR(fpec_base) ((fpec_base) + STM32_FLASH_AR_OFFSET) +#define STM32_FLASH_AR_BANK(fpec_base, bank) (STM32_FLASH_AR(fpec_base) + (bank)*STM32_FLASH_BANK_OFFSET) + +/* Flash option control register (FLASH_OPTCR) (STM32F4x Only) */ +#define STM32F4X_FLASH_OPTCR_OFFSET 0x14U +#define STM32F4X_FLASH_OPTCR(fpec_base) ((fpec_base) + STM32F4X_FLASH_OPTCR_OFFSET) + +/* TODO */ + +/* Flash option control register (FLASH_OPTCR1) (STM32F4x Only) */ +#define STM32F4X_FLASH_OPTCR1_OFFSET 0x18U +#define STM32F4X_FLASH_OPTCR1(fpec_base) ((fpec_base) + STM32F4X_FLASH_OPTCR1_OFFSET) + +/* TODO */ + +/* Option byte register (FLASH_OBR) (STM32F1x/F3x Only) */ +#define STM32_FLASH_OBR_OFFSET 0x1cU +#define STM32_FLASH_OBR(fpec_base) ((fpec_base) + STM32_FLASH_OBR_OFFSET) + +#define STM32_FLASH_OBR_OPTERR (1U << 0U) /* OPTERR Option byte error */ + +/* Write protection register (FLASH_WRPR) (STM32F1x/F3x Only) */ +#define STM32_FLASH_WRPR_OFFSET 0x20U +#define STM32_FLASH_WRPR(fpec_base) ((fpec_base) + STM32_FLASH_WRPR_OFFSET) + +/* + * §2.5 - Option byte description + * + * ┌───────────────────────────────────────────────────────────────────────────────────────┐ + * │ Table 3. Option byte format │ + * ├─────────────────────────────┬───────────────┬─────────────────────────┬───────────────┤ + * │ 31:24 │ 23:16 │ 15:8 │ 7:0 │ + * ├─────────────────────────────┼───────────────┼─────────────────────────┼───────────────┤ + * │ Complement option byte1 │ Option byte 1 │ Complement option byte0 │ Option byte 0 │ + * └─────────────────────────────┴───────────────┴─────────────────────────┴───────────────┘ + * + * Table 4. Option byte organization + * ┌────────────┬────────┬───────┬────────┬───────┐ + * │ Address │ 31:24 │ 23:16 │ 15:8 │ 7:0 │ + * ├────────────┼────────┼───────┼────────┼───────┤ + * │ 0x1ffff800 │ nUSER │ USER │ nRDP │ RDP │ + * │ 0x1ffff804 │ nData1 │ Data1 │ nData0 │ Data0 │ + * │ 0x1ffff808 │ nWRP1 │ WRP1 │ nWRP0 │ WRP0 │ + * │ 0x1ffff80c │ nWRP3 │ WRP3 │ nWRP2 │ WRP2 │ + * └────────────┴────────┴───────┴────────┴───────┘ + */ + +#define STM32_FLASH_OPT_ADDR 0x1ffff800U + +/* Option byte register STM32F10x */ +#define STM32F10X_FLASH_OBR_DATA1_OFFSET 18U /* 8 bits */ +#define STM32F10X_FLASH_OBR_DATA0_OFFSET 10U /* 8 bits */ +#define STM32F10X_FLASH_OBR_USER_9 (1U << 9U) /* Not used */ +#define STM32F10X_FLASH_OBR_USER_8 (1U << 8U) /* Not used */ +#define STM32F10X_FLASH_OBR_USER_7 (1U << 7U) /* Not used */ +#define STM32F10X_FLASH_OBR_USER_6 (1U << 6U) /* Not used */ +#define STM32F10X_FLASH_OBR_USER_BFB2 (1U << 5U) +#define STM32F10X_FLASH_OBR_USER_NRST_STDBY (1U << 4U) +#define STM32F10X_FLASH_OBR_USER_NRST_STOP (1U << 3U) +#define STM32F10X_FLASH_OBR_USER_WDG_SW (1U << 2U) +#define STM32F10X_FLASH_OBR_RDPRT (1U << 1U) /* Read protection */ + +#define STM32F10X_FLASH_RDPRT 0xa5U + +/* Option byte register STM32F3xx */ +//#define STM32F3XX_FLASH_RDPRT 0x55U +// Bits 31:24 Data1 +// Bits 23:16 Data0 +// Bits 15:8 OBR: User Option Byte +// Bit 15: Reserved, must be kept at reset value. +// Bit 14: Reserved, must be kept at reset value. +// Bit 13: VDDA_MONITOR +// Bit 12: nBOOT1 +// Bit 11: Reserved, must be kept at reset value. +// Bit 10: nRST_STDBY +// Bit 9: nRST_STOP +// Bit 8: WDG_SW +// Bits 7:3 Reserved, must be kept at reset value. +// Bit 2:1 RDPRT[1:0]: Read protection Level status +// 00: Read protection Level 0 is enabled (ST production set up) +// 01: Read protection Level 1 is enabled +// 10: Reserved +// 11: Read protection Level 2 is enabled +// Note: These bits are read-only. + +#define STM32F3X_FLASH_RDPRT 0xaaU + +/* Insternal API */ +typedef struct stm32_flash { + target_flash_s flash; + uint32_t fpec_base; + uint8_t bank; +} stm32_flash_s; + +bool stm32_flash_locked(target_s *target, uint32_t fpec_base, uint8_t bank); +bool stm32_flash_unlock(target_s *target, uint32_t fpec_base, uint8_t bank); +void stm32_flash_lock(target_s *target, uint32_t fpec_base, uint8_t bank); +void stm32_flash_clear_status(target_s *target, uint32_t fpec_base, uint8_t bank); +bool stm32_flash_busy_wait(target_s *target, uint32_t fpec_base, uint8_t bank, platform_timeout_s *print_progess); +bool stm32_flash_mass_erase(target_flash_s *flash, platform_timeout_s *print_progess); + +/* Generic flash routines */ +void stm32_add_flash(target_s *target, target_addr_t addr, size_t length, uint32_t fpec_base, size_t block_size); +void stm32_add_banked_flash(target_s *target, target_addr_t addr, size_t length, target_addr_t bank_split_addr, + uint32_t fpec_base, size_t block_size); + +/* Option bytes command */ +bool stm32_option_bytes_cmd(target_s *target, int argc, const char **argv); + +#endif /* TARGET_STM32_FLASH_H */ diff --git a/src/target/stm32f1.c b/src/target/stm32f1.c index 049cd9c3f77..79e8ad9e2d0 100644 --- a/src/target/stm32f1.c +++ b/src/target/stm32f1.c @@ -41,111 +41,72 @@ #include "target.h" #include "target_internal.h" #include "cortexm.h" - -static bool stm32f1_cmd_option(target_s *target, int argc, const char **argv); - -const command_s stm32f1_cmd_list[] = { - {"option", stm32f1_cmd_option, "Manipulate option bytes"}, +#include "jep106.h" +#include "stm32_flash.h" + +/* IDCODE register */ +#define STM32_IDCODE_REVISION_ID_OFFSET 16U +#define STM32_IDCODE_REVISION_ID_MASK (0xffffU << STM32_IDCODE_REVISION_ID_OFFSET) +#define STM32_IDCODE_DEVICE_ID_MASK 0xfffU + +/* Noted as reserved by ST, but contains useful information for AT32F43x */ +#define STM32_IDCODE_RESERVED_OFFSET 12U +#define STM32_IDCODE_RESERVED_MASK (0xfU << STM32_IDCODE_RESERVED_OFFSET) + +#define STM32F10X_IDCODE 0xe0042000U +#define STM32F0X_IDCODE 0x40015800U +#define GD32E5X_IDCODE 0xe0044000U +#define MM32L0X_IDCODE 0x40013400U +#define MM32FX_IDCODE 0x40007080U + +#define AT32F40X_REVISION_ID 0x7005U +#define AT32F41X_REVISION_ID 0x7003U +#define AT32F43X_REVISION_ID 0x7008U + +#define AT32F43X_RESERVED_B_2K 3U +#define AT32F43X_RESERVED_B_4K 4U + +/* Electronic Signature (ESIG) registers */ +#define GD32_ESIG_MEM_DENSITY(esig_base) ((esig_base) + 0x00U) /* Memory density register */ +#define GD32_ESIG_MEM_DENSITY_RAM_OFFSET 16U +#define GD32_ESIG_MEM_DENSITY_RAM_MASK (0xffffU << GD32_ESIG_MEM_DENSITY_RAM_OFFSET) /* KiB units */ +#define GD32_ESIG_MEM_DENSITY_FLASH_MASK 0xffffU /* KiB units */ +#define GD32_ESIG_UID1(esig_base) ((esig_base) + 0x08U) /* Unique ID register, bits 0:31 */ +#define GD32_ESIG_UID2(esig_base) ((esig_base) + 0x0cU) /* Unique ID register, bits 32:63 */ +#define GD32_ESIG_UID3(esig_base) ((esig_base) + 0x10U) /* Unique ID register, bits 64:95 */ + +#define GD32FX_ESIG_BASE 0x1ffff7e0U /* GD32Fx Electronic signature base address */ +#define GD32F0X_ESIG_BASE 0x1ffff7ccU /* GD32F0x Electronic signature base address */ + +/* STM32F10X memory mapping */ +#define STM32F10X_FLASH_MEMORY_ADDR 0x08000000U +#define STM32F10X_SRAM_ADDR 0x20000000U +#define STM32F10X_FLASH_BANK_SIZE 512U /* KiB */ +#define STM32F10X_FLASH_BANK_SPLIT_ADDR (STM32F10X_FLASH_MEMORY_ADDR + (STM32F10X_FLASH_BANK_SIZE << 10U)) + +const command_s stm32_cmd_list[] = { + {"option", stm32_option_bytes_cmd, "Manipulate option bytes"}, {NULL, NULL, NULL}, }; -static bool stm32f1_flash_erase(target_flash_s *flash, target_addr_t addr, size_t len); -static bool stm32f1_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len); -static bool stm32f1_mass_erase(target_s *target); - -/* Flash Program ad Erase Controller Register Map */ -#define FPEC_BASE 0x40022000U -#define FLASH_ACR (FPEC_BASE + 0x00U) -#define FLASH_KEYR (FPEC_BASE + 0x04U) -#define FLASH_OPTKEYR (FPEC_BASE + 0x08U) -#define FLASH_SR (FPEC_BASE + 0x0cU) -#define FLASH_CR (FPEC_BASE + 0x10U) -#define FLASH_AR (FPEC_BASE + 0x14U) -#define FLASH_OBR (FPEC_BASE + 0x1cU) -#define FLASH_WRPR (FPEC_BASE + 0x20U) - -#define FLASH_BANK1_OFFSET 0x00U -#define FLASH_BANK2_OFFSET 0x40U -#define FLASH_BANK_SPLIT 0x08080000U - -#define FLASH_CR_OBL_LAUNCH (1U << 13U) -#define FLASH_CR_OPTWRE (1U << 9U) -#define FLASH_CR_LOCK (1U << 7U) -#define FLASH_CR_STRT (1U << 6U) -#define FLASH_CR_OPTER (1U << 5U) -#define FLASH_CR_OPTPG (1U << 4U) -#define FLASH_CR_MER (1U << 2U) -#define FLASH_CR_PER (1U << 1U) -#define FLASH_CR_PG (1U << 0U) - -#define FLASH_OBR_RDPRT (1U << 1U) - -#define FLASH_SR_BSY (1U << 0U) - -#define FLASH_OBP_RDP 0x1ffff800U -#define FLASH_OBP_RDP_KEY 0x5aa5U -#define FLASH_OBP_RDP_KEY_F3 0x55aaU - -#define KEY1 0x45670123U -#define KEY2 0xcdef89abU - -#define SR_ERROR_MASK 0x14U -#define SR_PROG_ERROR 0x04U -#define SR_EOP 0x20U - -#define DBGMCU_IDCODE 0xe0042000U -#define DBGMCU_IDCODE_F0 0x40015800U -#define DBGMCU_IDCODE_GD32E5 0xe0044000U - -#define GD32Fx_FLASHSIZE 0x1ffff7e0U -#define GD32F0_FLASHSIZE 0x1ffff7ccU - -#define AT32F4x_IDCODE_SERIES_MASK 0xfffff000U -#define AT32F4x_IDCODE_PART_MASK 0x00000fffU -#define AT32F41_SERIES 0x70030000U -#define AT32F40_SERIES 0x70050000U -#define AT32F43_SERIES_4K 0x70084000U -#define AT32F43_SERIES_2K 0x70083000U - -#define DBGMCU_IDCODE_MM32L0 0x40013400U -#define DBGMCU_IDCODE_MM32F3 0x40007080U - -static void stm32f1_add_flash(target_s *target, uint32_t addr, size_t length, size_t erasesize) -{ - target_flash_s *flash = calloc(1, sizeof(*flash)); - if (!flash) { /* calloc failed: heap exhaustion */ - DEBUG_ERROR("calloc: failed in %s\n", __func__); - return; - } - - flash->start = addr; - flash->length = length; - flash->blocksize = erasesize; - flash->writesize = 1024U; - flash->erase = stm32f1_flash_erase; - flash->write = stm32f1_flash_write; - flash->erased = 0xff; - target_add_flash(target, flash); -} - -static uint16_t stm32f1_read_idcode(target_s *const target) +static uint16_t stm32_read_idcode(target_s *const target) { if ((target->cpuid & CORTEX_CPUID_PARTNO_MASK) == CORTEX_M0 || (target->cpuid & CORTEX_CPUID_PARTNO_MASK) == CORTEX_M23) - return target_mem_read32(target, DBGMCU_IDCODE_F0) & 0xfffU; + return target_mem_read32(target, STM32F0X_IDCODE) & 0xfffU; /* Is this a Cortex-M33 core with STM32F1-style peripherals? (GD32E50x) */ if ((target->cpuid & CORTEX_CPUID_PARTNO_MASK) == CORTEX_M33) - return target_mem_read32(target, DBGMCU_IDCODE_GD32E5) & 0xfffU; + return target_mem_read32(target, GD32E5X_IDCODE) & 0xfffU; - return target_mem_read32(target, DBGMCU_IDCODE) & 0xfffU; + return target_mem_read32(target, STM32F0X_IDCODE) & 0xfffU; } /* Identify GD32F1, GD32F2 and GD32F3 chips */ -bool gd32f1_probe(target_s *target) +bool gd32f1_probe(target_s *const target) { - const uint16_t device_id = stm32f1_read_idcode(target); - size_t block_size = 0x400; + const uint16_t device_id = stm32_read_idcode(target); + uint16_t block_size = 1U; /* KiB */ switch (device_id) { case 0x414U: /* Gigadevice gd32f303 */ case 0x430U: @@ -164,110 +125,153 @@ bool gd32f1_probe(target_s *target) break; case 0x444U: /* Gigadevice gd32e50x */ target->driver = "GD32E5"; - block_size = 0x2000; + block_size = 8U; break; default: return false; } - const uint32_t signature = target_mem_read32(target, GD32Fx_FLASHSIZE); - const uint16_t flash_size = signature & 0xffffU; - const uint16_t ram_size = signature >> 16U; - target->part_id = device_id; - target->mass_erase = stm32f1_mass_erase; - target_add_ram(target, 0x20000000, ram_size * 1024U); - stm32f1_add_flash(target, 0x8000000, (size_t)flash_size * 1024U, block_size); - target_add_commands(target, stm32f1_cmd_list, target->driver); + + /* Get flash capacity from ESIG register */ + const uint32_t memory_density = target_mem_read32(target, GD32FX_ESIG_BASE); + const uint16_t flash_size = memory_density & GD32_ESIG_MEM_DENSITY_FLASH_MASK; + const uint16_t ram_size = (memory_density & GD32_ESIG_MEM_DENSITY_RAM_MASK) >> GD32_ESIG_MEM_DENSITY_RAM_OFFSET; + + target_add_ram(target, 0x20000000, ram_size << 10U); + stm32_add_flash(target, 0x8000000, flash_size << 10U, STM32F10X_FPEC_BASE, block_size << 10U); + + target_add_commands(target, stm32_cmd_list, target->driver); return true; } -static bool at32f40_detect(target_s *target, const uint16_t part_id) +/* Identify RISC-V GD32VF1 chips */ +bool gd32vf1_probe(target_s *const target) { - // Current driver supports only *default* memory layout (256 KB Flash / 96 KB SRAM) - // XXX: Support for external Flash for 512KB and 1024KB parts requires specific flash code (not implemented) - switch (part_id) { - case 0x0240U: // AT32F403AVCT7 256KB / LQFP100 - case 0x0241U: // AT32F403ARCT7 256KB / LQFP64 - case 0x0242U: // AT32F403ACCT7 256KB / LQFP48 - case 0x0243U: // AT32F403ACCU7 256KB / QFN48 - case 0x0249U: // AT32F407VCT7 256KB / LQFP100 - case 0x024aU: // AT32F407RCT7 256KB / LQFP64 - case 0x0254U: // AT32F407AVCT7 256KB / LQFP100 - case 0x02cdU: // AT32F403AVET7 512KB / LQFP100 (*) - case 0x02ceU: // AT32F403ARET7 512KB / LQFP64 (*) - case 0x02cfU: // AT32F403ACET7 512KB / LQFP48 (*) - case 0x02d0U: // AT32F403ACEU7 512KB / QFN48 (*) - case 0x02d1U: // AT32F407VET7 512KB / LQFP100 (*) - case 0x02d2U: // AT32F407RET7 512KB / LQFP64 (*) - case 0x0344U: // AT32F403AVGT7 1024KB / LQFP100 (*) - case 0x0345U: // AT32F403ARGT7 1024KB / LQFP64 (*) - case 0x0346U: // AT32F403ACGT7 1024KB / LQFP48 (*) - case 0x0347U: // AT32F403ACGU7 1024KB / QFN48 (found on BlackPill+ WeAct Studio) (*) - case 0x034bU: // AT32F407VGT7 1024KB / LQFP100 (*) - case 0x034cU: // AT32F407VGT7 1024KB / LQFP64 (*) - case 0x0353U: // AT32F407AVGT7 1024KB / LQFP100 (*) - // Flash: 256 KB / 2KB per block - stm32f1_add_flash(target, 0x08000000, 256U * 1024U, 2U * 1024U); + /* Make sure the architecture ID matches */ + if (target->cpuid != 0x80000022U) + return false; + + /* Then read out the device IDCODE */ + const uint16_t device_id = target_mem_read32(target, STM32F10X_IDCODE) & STM32_IDCODE_DEVICE_ID_MASK; + switch (device_id) { + case 0x410U: /* GD32VF103 */ + target->driver = "GD32VF1"; break; - // Unknown/undocumented default: return false; } - // All parts have 96KB SRAM - target_add_ram(target, 0x20000000, 96U * 1024U); - target->driver = "AT32F403A/407"; - target->mass_erase = stm32f1_mass_erase; + + /* Get flash capacity from ESIG register */ + const uint32_t memory_density = target_mem_read32(target, GD32FX_ESIG_BASE); + const uint16_t flash_size = memory_density & GD32_ESIG_MEM_DENSITY_FLASH_MASK; + const uint16_t ram_size = (memory_density & GD32_ESIG_MEM_DENSITY_RAM_MASK) >> GD32_ESIG_MEM_DENSITY_RAM_OFFSET; + + target->part_id = device_id; + + target_add_ram(target, 0x20000000, ram_size << 10U); /* KiB to bytes */ + stm32_add_flash(target, 0x8000000, flash_size << 10U, STM32F10X_FPEC_BASE, 1U << 10U); /* KiB to bytes */ + + target_add_commands(target, stm32_cmd_list, target->driver); + + return true; +} + +static bool at32f40x_probe(target_s *const target, const uint16_t device_id) +{ + /* + * Current driver supports only *default* memory layout (256 KB Flash / 96 KB SRAM) + * XXX: Support for external Flash for 512KB and 1024KB parts requires specific flash code (not implemented) + */ + switch (device_id) { + case 0x0240U: /* AT32F403AVCT7 256KB / LQFP100 */ + case 0x0241U: /* AT32F403ARCT7 256KB / LQFP64 */ + case 0x0242U: /* AT32F403ACCT7 256KB / LQFP48 */ + case 0x0243U: /* AT32F403ACCU7 256KB / QFN48 */ + target->driver = "AT32F403x"; + break; + case 0x0249U: /* AT32F407VCT7 256KB / LQFP100 */ + case 0x024aU: /* AT32F407RCT7 256KB / LQFP64 */ + case 0x0254U: /* AT32F407AVCT7 256KB / LQFP100 */ + case 0x02cdU: /* AT32F403AVET7 512KB / LQFP100 (*) */ + case 0x02ceU: /* AT32F403ARET7 512KB / LQFP64 (*) */ + case 0x02cfU: /* AT32F403ACET7 512KB / LQFP48 (*) */ + case 0x02d0U: /* AT32F403ACEU7 512KB / QFN48 (*) */ + case 0x02d1U: /* AT32F407VET7 512KB / LQFP100 (*) */ + case 0x02d2U: /* AT32F407RET7 512KB / LQFP64 (*) */ + case 0x0344U: /* AT32F403AVGT7 1024KB / LQFP100 (*) */ + case 0x0345U: /* AT32F403ARGT7 1024KB / LQFP64 (*) */ + case 0x0346U: /* AT32F403ACGT7 1024KB / LQFP48 (*) */ + case 0x0347U: /* AT32F403ACGU7 1024KB / QFN48 (found on BlackPill+ WeAct Studio) */ + case 0x034bU: /* AT32F407VGT7 1024KB / LQFP100 (*) */ + case 0x034cU: /* AT32F407VGT7 1024KB / LQFP64 (*) */ + case 0x0353U: /* AT32F407AVGT7 1024KB / LQFP100 (*) */ + target->driver = "AT32F407x"; + break; + default: + return false; /* Unknown/undocumented */ + } + + /* 256 KiB Flash with 2 KiB blocks */ + stm32_add_flash(target, 0x08000000, 256U << 10U, STM32F10X_FPEC_BASE, 2U << 10U); /* KiB to bytes */ + + /* All parts have 96KB SRAM */ + target_add_ram(target, 0x20000000, 96U << 10U); /* KiB to bytes */ + return true; } -static bool at32f41_detect(target_s *target, const uint16_t part_id) +static bool at32f41x_probe(target_s *const target, const uint16_t device_id) { - switch (part_id) { - case 0x0240U: // LQFP64_10x10 - case 0x0241U: // LQFP48_7x7 - case 0x0242U: // QFN32_4x4 - case 0x0243U: // LQFP64_7x7 - case 0x024cU: // QFN48_6x6 - // Flash: 256 KB / 2KB per block - stm32f1_add_flash(target, 0x08000000, 256U * 1024U, 2U * 1024U); + size_t flash_size = 0U; /* KiB */ + switch (device_id) { + case 0x0240U: /* LQFP64_10x10 */ + case 0x0241U: /* LQFP48_7x7 */ + case 0x0242U: /* QFN32_4x4 */ + case 0x0243U: /* LQFP64_7x7 */ + case 0x024cU: /* QFN48_6x6 */ + flash_size = 256U; /* KiB */ break; - case 0x01c4U: // LQFP64_10x10 - case 0x01c5U: // LQFP48_7x7 - case 0x01c6U: // QFN32_4x4 - case 0x01c7U: // LQFP64_7x7 - case 0x01cdU: // QFN48_6x6 - // Flash: 128 KB / 2KB per block - stm32f1_add_flash(target, 0x08000000, 128U * 1024U, 2U * 1024U); + case 0x01c4U: /* LQFP64_10x10 */ + case 0x01c5U: /* LQFP48_7x7 */ + case 0x01c6U: /* QFN32_4x4 */ + case 0x01c7U: /* LQFP64_7x7 */ + case 0x01cdU: /* QFN48_6x6 */ + flash_size = 128U; /* KiB */ break; - case 0x0108U: // LQFP64_10x10 - case 0x0109U: // LQFP48_7x7 - case 0x010aU: // QFN32_4x4 - // Flash: 64 KB / 2KB per block - stm32f1_add_flash(target, 0x08000000, 64U * 1024U, 2U * 1024U); + case 0x0108U: /* LQFP64_10x10 */ + case 0x0109U: /* LQFP48_7x7 */ + case 0x010aU: /* QFN32_4x4 */ + flash_size = 64U; /* KiB */ break; - // Unknown/undocumented default: - return false; + return false; /* Unknown/undocumented */ } - // All parts have 32KB SRAM - target_add_ram(target, 0x20000000, 32U * 1024U); + target->driver = "AT32F415"; - target->mass_erase = stm32f1_mass_erase; + + /* Flash with 2 KiB blocks */ + stm32_add_flash(target, 0x08000000, flash_size << 10U, STM32F10X_FPEC_BASE, 2U << 10U); /* KiB to bytes */ + + /* All parts have 32KB SRAM */ + target_add_ram(target, 0x20000000, 32U << 10U); + return true; } -static bool at32f43_detect(target_s *target, const uint16_t part_id) +static bool at32f43x_probe(target_s *const target, const uint16_t device_id) { - /* AT32F435 EOPB0 ZW/NZW split reconfiguration unsupported, + /* + * AT32F435 EOPB0 ZW/NZW split reconfiguration unsupported, * assuming default split ZW=256 SRAM=384. * AT32F437 also have a working "EMAC" (Ethernet MAC) */ - uint32_t flash_size_kb = 0; - uint32_t sector_size = 0; - switch (part_id) { - // 0x70084000U parts with 4KB sectors: + size_t flash_size = 0; /* KiB */ + target_addr_t bank_split = 0; + size_t block_size = 0; /* KiB */ + switch (device_id) { + /* Parts with 4 KiB sectors */ case 0x0540U: // LQFP144 case 0x0543U: // LQFP100 case 0x0546U: // LQFP64 @@ -276,9 +280,10 @@ static bool at32f43_detect(target_s *target, const uint16_t part_id) case 0x054fU: // LQFP144 w/Eth case 0x0552U: // LQFP100 w/Eth case 0x0555U: // LQFP64 w/Eth - // Flash (G): 4032 KB in 2 banks (2048+1984), 4KB per sector. - flash_size_kb = 4032; - sector_size = 4096; + /* Flash (G): 4032 KB in 2 banks (2048 + 1984) */ + flash_size = 4032U; /* KiB */ + bank_split = 0x08000000 + (2048U << 10U); + block_size = 4U; /* KiB */ break; case 0x0598U: // LQFP144 case 0x0599U: // LQFP100 @@ -288,11 +293,11 @@ static bool at32f43_detect(target_s *target, const uint16_t part_id) case 0x059dU: // LQFP144 w/Eth case 0x059eU: // LQFP100 w/Eth case 0x059fU: // LQFP64 w/Eth - // Flash (D): 448 KB, only bank 1, 4KB per sector. - flash_size_kb = 448; - sector_size = 4096; + /* Flash (D): 448 KiB, only 1 bank */ + flash_size = 448U; /* KiB */ + block_size = 4U; /* KiB */ break; - // 0x70083000U parts with 2KB sectors: + /* Parts with 2 KiB sectors */ case 0x0341U: // LQFP144 case 0x0344U: // LQFP100 case 0x0347U: // LQFP64 @@ -301,9 +306,10 @@ static bool at32f43_detect(target_s *target, const uint16_t part_id) case 0x0350U: // LQFP144 w/Eth case 0x0353U: // LQFP100 w/Eth case 0x0356U: // LQFP64 w/Eth - // Flash (M): 1024 KB in 2 banks (equal), 2KB per sector. - flash_size_kb = 1024; - sector_size = 2048; + /* Flash (M): 1024 KB in 2 banks (equal sized) */ + flash_size = 1024U; /* KiB */ + bank_split = 0x08000000 + (512U << 10U); + block_size = 2U; /* KiB */ break; case 0x0242U: // LQFP144 case 0x0245U: // LQFP100 @@ -313,63 +319,74 @@ static bool at32f43_detect(target_s *target, const uint16_t part_id) case 0x0251U: // LQFP144 w/Eth case 0x0254U: // LQFP100 w/Eth case 0x0257U: // LQFP64 w/Eth - // Flash (C): 256 KB, only bank 1, 2KB per sector. - flash_size_kb = 256; - sector_size = 2048; + /* Flash (C): 256 KB, only 1 bank */ + flash_size = 256U; /* KiB */ + block_size = 2U; /* KiB */ break; default: return false; } + + target->driver = "AT32F435"; + /* - * Arterytek F43x Flash controller has BLKERS (1<<3U). + * FIXME: This sounds like it can be handled + * Arterytek F43x Flash controller has BLKERS (1U << 3U). * Block erase operates on 64 KB at once for all parts. * Using here only sector erase (page erase) for compatibility. */ - stm32f1_add_flash(target, 0x08000000, flash_size_kb * 1024U, sector_size); - // SRAM1 (64KB) can be remapped to 0x10000000. - target_add_ram(target, 0x20000000, 64U * 1024U); - // SRAM2 (384-64=320 KB default). - target_add_ram(target, 0x20010000, 320U * 1024U); + if (bank_split) + stm32_add_banked_flash(target, 0x08000000, flash_size << 10U, bank_split, STM32F10X_FPEC_BASE, + block_size << 10U); /* KiB to bytes */ + else + stm32_add_flash(target, 0x08000000, flash_size, STM32F10X_FPEC_BASE, block_size << 10U); /* KiB to bytes */ + /* + * FIXME: handle dynamic SRAM mapping (see SAMx7x) * SRAM total is adjustable between 128 KB and 512 KB (max). * Out of 640 KB SRAM present on silicon, at least 128 KB are always * dedicated to "zero-wait-state Flash". ZW region is limited by - * specific part flash capacity (for 256, 448 KB) or at 512 KB. + * specific part flash capacity (for 256, 448 KiB) or at 512 KiB. * AT32F435ZMT default EOPB0=0xffff05fa, - * EOPB[0:2]=0b010 for 384 KB SRAM + 256 KB zero-wait-state flash. + * EOPB[0:2]=0b010 for 384 KiB SRAM + 256 KiB zero-wait-state flash. */ - target->driver = "AT32F435"; - target->mass_erase = stm32f1_mass_erase; + + /* SRAM1 (64 KiB) can be remapped to 0x10000000 */ + target_add_ram(target, 0x20000000 + 7U, 64U << 10U); + /* SRAM2 (384 - 64 = 320 KiB default) */ + target_add_ram(target, 0x20010000, 320U << 10U); + return true; } /* Identify AT32F4x devices (Cortex-M4) */ -bool at32fxx_probe(target_s *target) +bool at32fxx_probe(target_s *const target) { // Artery clones use Cortex M4 cores if ((target->cpuid & CORTEX_CPUID_PARTNO_MASK) != CORTEX_M4) return false; // Artery chips use the complete idcode word for identification - const uint32_t idcode = target_mem_read32(target, DBGMCU_IDCODE); - const uint32_t series = idcode & AT32F4x_IDCODE_SERIES_MASK; - const uint16_t part_id = idcode & AT32F4x_IDCODE_PART_MASK; - - if (series == AT32F40_SERIES) - return at32f40_detect(target, part_id); - if (series == AT32F41_SERIES) - return at32f41_detect(target, part_id); - if (series == AT32F43_SERIES_4K || series == AT32F43_SERIES_2K) - return at32f43_detect(target, part_id); + const uint32_t idcode = target_mem_read32(target, STM32F10X_IDCODE); + const uint16_t revision_id = (idcode & STM32_IDCODE_REVISION_ID_MASK) >> STM32_IDCODE_REVISION_ID_OFFSET; + const uint16_t device_id = idcode & STM32_IDCODE_DEVICE_ID_MASK; + + if (revision_id == AT32F40X_REVISION_ID) + return at32f40x_probe(target, device_id); + if (revision_id == AT32F41X_REVISION_ID) + return at32f41x_probe(target, device_id); + if (revision_id == AT32F43X_REVISION_ID) + return at32f43x_probe(target, device_id); return false; } /* * On STM32, 16-bit writes use bits 0:15 for even halfwords; bits 16:31 for odd halfwords. * On MM32 cortex-m0, 16-bit writes always use bits 0:15. - * Set both halfwords to the same value, works on both STM32 and MM32. + * Set both halfwords to the same value, works on both STM32 and NN32. */ -void mm32l0_mem_write_sized(adiv5_access_port_s *ap, uint32_t dest, const void *src, size_t len, align_e align) +void mm32l0_mem_write_sized( + adiv5_access_port_s *const ap, uint32_t dest, const void *src, size_t len, const align_e align) { uint32_t odest = dest; @@ -416,487 +433,174 @@ void mm32l0_mem_write_sized(adiv5_access_port_s *ap, uint32_t dest, const void * } /* Identify MM32 devices (Cortex-M0) */ - -bool mm32l0xx_probe(target_s *target) +bool mm32l0xx_probe(target_s *const target) { - const char *name; - size_t flash_kbyte = 0; - size_t ram_kbyte = 0; - size_t block_size = 0x400U; - - const uint32_t mm32_id = target_mem_read32(target, DBGMCU_IDCODE_MM32L0); - if (target_check_error(target)) { - DEBUG_ERROR("%s: read error at 0x%" PRIx32 "\n", __func__, (uint32_t)DBGMCU_IDCODE_MM32L0); - return false; - } - switch (mm32_id) { + const uint32_t idcode = target_mem_read32(target, MM32L0X_IDCODE); + + size_t flash_size = 0; + size_t ram_size = 0; + switch (idcode) { case 0xcc568091U: - name = "MM32L07x"; - flash_kbyte = 128; - ram_kbyte = 8; - break; - case 0xcc4460b1: - name = "MM32SPIN05"; - flash_kbyte = 32; - ram_kbyte = 4; + target->driver = "MM32L07x"; + flash_size = 128U; /* KiB */ + ram_size = 8U; /* KiB */ break; case 0xcc56a097U: - name = "MM32SPIN27"; - flash_kbyte = 128; - ram_kbyte = 12; + target->driver = "MM32SPIN27"; + flash_size = 128U; /* KiB */ + ram_size = 12U; /* KiB */ break; - case 0x00000000U: - case 0xffffffffU: - return false; default: - DEBUG_WARN("%s: unknown mm32 dev_id 0x%" PRIx32 "\n", __func__, mm32_id); + DEBUG_WARN("%s: unknown MM32 IDCODE 0x%" PRIx32 "\n", __func__, idcode); return false; } - target->part_id = mm32_id & 0xfffU; - target->driver = name; - target->mass_erase = stm32f1_mass_erase; - target_add_ram(target, 0x20000000U, ram_kbyte * 1024U); - stm32f1_add_flash(target, 0x08000000U, flash_kbyte * 1024U, block_size); - target_add_commands(target, stm32f1_cmd_list, name); + + target->part_id = idcode & STM32_IDCODE_DEVICE_ID_MASK; + cortex_ap(target)->dp->mem_write = mm32l0_mem_write_sized; + + target_add_ram(target, 0x20000000U, ram_size << 10U); /* KiB to bytes */ + stm32_add_flash(target, 0x08000000U, flash_size << 10U, STM32F10X_FPEC_BASE, 1U << 10U); /* KiB to bytes */ + + target_add_commands(target, stm32_cmd_list, target->driver); + return true; } /* Identify MM32 devices (Cortex-M3, Star-MC1) */ bool mm32f3xx_probe(target_s *target) { - const char *name; - size_t flash_kbyte = 0; - size_t ram1_kbyte = 0; /* ram at 0x20000000 */ - size_t ram2_kbyte = 0; /* ram at 0x30000000 */ - size_t block_size = 0x400U; - - const uint32_t mm32_id = target_mem_read32(target, DBGMCU_IDCODE_MM32F3); - if (target_check_error(target)) { - DEBUG_ERROR("%s: read error at 0x%" PRIx32 "\n", __func__, (uint32_t)DBGMCU_IDCODE_MM32F3); - return false; - } - switch (mm32_id) { + const uint32_t idcode = target_mem_read32(target, MM32FX_IDCODE); + + size_t flash_size = 0; /* KiB */ + size_t ram1_size = 0; /* RAM at 0x20000000, KiB */ + size_t ram2_size = 0; /* RAM at 0x30000000, KiB */ + switch (idcode) { case 0xcc9aa0e7U: - name = "MM32F3273"; - flash_kbyte = 512; - ram1_kbyte = 128; + target->driver = "MM32F3273"; + flash_size = 512U; /* KiB */ + ram1_size = 128U; /* KiB */ break; case 0x4d4d0800U: - name = "MM32F5277"; - flash_kbyte = 256; - ram1_kbyte = 32; - ram2_kbyte = 128; + target->driver = "MM32F5277"; + flash_size = 256U; /* KiB */ + ram1_size = 32U; /* KiB */ + ram2_size = 128U; /* KiB */ break; - case 0x00000000U: - case 0xffffffffU: - return false; default: - DEBUG_WARN("%s: unknown mm32 ID code 0x%" PRIx32 "\n", __func__, mm32_id); + DEBUG_WARN("%s: unknown MM32 IDCODE 0x%" PRIx32 "\n", __func__, idcode); return false; } - target->part_id = mm32_id & 0xfffU; - target->driver = name; - target->mass_erase = stm32f1_mass_erase; - if (ram1_kbyte != 0) - target_add_ram(target, 0x20000000U, ram1_kbyte * 1024U); - if (ram2_kbyte != 0) - target_add_ram(target, 0x30000000U, ram2_kbyte * 1024U); - stm32f1_add_flash(target, 0x08000000U, flash_kbyte * 1024U, block_size); - target_add_commands(target, stm32f1_cmd_list, name); + + target->part_id = idcode & STM32_IDCODE_DEVICE_ID_MASK; + + stm32_add_flash(target, 0x08000000U, flash_size << 10U, STM32F10X_FPEC_BASE, 1U << 10U); /* KiB to bytes */ + if (ram1_size) + target_add_ram(target, 0x20000000U, ram1_size << 10U); /* KiB to bytes */ + if (ram2_size) + target_add_ram(target, 0x30000000U, ram2_size << 10U); /* KiB to bytes */ + + target_add_commands(target, stm32_cmd_list, target->driver); + return true; } /* Identify real STM32F0/F1/F3 devices */ bool stm32f1_probe(target_s *target) { - const uint16_t device_id = stm32f1_read_idcode(target); - - target->mass_erase = stm32f1_mass_erase; - size_t flash_size = 0; - size_t block_size = 0x400; + const uint16_t device_id = stm32_read_idcode(target); + size_t ram_size = 20U; /* KiB */ + size_t flash_size = 0; /* KiB */ + size_t block_size = 1U; /* KiB */ switch (device_id) { case 0x29bU: /* CS clone */ case 0x410U: /* Medium density */ case 0x412U: /* Low density */ case 0x420U: /* Value Line, Low-/Medium density */ - target_add_ram(target, 0x20000000, 0x5000); - stm32f1_add_flash(target, 0x8000000, 0x20000, 0x400); - target_add_commands(target, stm32f1_cmd_list, "STM32 LD/MD/VL-LD/VL-MD"); /* Test for clone parts with Core rev 2*/ - adiv5_access_port_s *ap = cortex_ap(target); - if ((ap->idr >> 28U) > 1U) { + if ((cortex_ap(target)->idr >> 28U) > 1U) { target->driver = "STM32F1 (clone) medium density"; DEBUG_WARN("Detected clone STM32F1\n"); } else target->driver = "STM32F1 medium density"; - target->part_id = device_id; - return true; + flash_size = 128U; /* KiB */ + break; case 0x414U: /* High density */ case 0x418U: /* Connectivity Line */ case 0x428U: /* Value Line, High Density */ - target->driver = "STM32F1 VL density"; - target->part_id = device_id; - target_add_ram(target, 0x20000000, 0x10000); - stm32f1_add_flash(target, 0x8000000, 0x80000, 0x800); - target_add_commands(target, stm32f1_cmd_list, "STM32 HF/CL/VL-HD"); - return true; + target->driver = "STM32F1 VL density"; + ram_size = 64U; /* KiB */ + flash_size = 512U; /* KiB */ + block_size = 2U; /* KiB */ + break; case 0x430U: /* XL-density */ - target->driver = "STM32F1 XL density"; - target->part_id = device_id; - target_add_ram(target, 0x20000000, 0x18000); - stm32f1_add_flash(target, 0x8000000, 0x80000, 0x800); - stm32f1_add_flash(target, 0x8080000, 0x80000, 0x800); - target_add_commands(target, stm32f1_cmd_list, "STM32 XL/VL-XL"); - return true; + target->driver = "STM32F1 XL density"; + ram_size = 96U; /* KiB */ + flash_size = 1024U; /* KiB */ + block_size = 2U; /* KiB */ + break; case 0x438U: /* STM32F303x6/8 and STM32F328 */ case 0x422U: /* STM32F30x */ case 0x446U: /* STM32F303xD/E and STM32F398xE */ - target_add_ram(target, 0x10000000, 0x4000); - /* fall through */ + /* Additional RAM */ + target_add_ram(target, 0x10000000, 16U << 10U); /* KiB to bytes */ + /* fall through */ case 0x432U: /* STM32F37x */ case 0x439U: /* STM32F302C8 */ target->driver = "STM32F3"; - target->part_id = device_id; - target_add_ram(target, 0x20000000, 0x10000); - stm32f1_add_flash(target, 0x8000000, 0x80000, 0x800); - target_add_commands(target, stm32f1_cmd_list, "STM32F3"); - return true; + ram_size = 64U; /* KiB */ + flash_size = 512U; /* KiB */ + block_size = 2U; /* KiB */ + break; case 0x444U: /* STM32F03 RM0091 Rev. 7, STM32F030x[4|6] RM0360 Rev. 4 */ target->driver = "STM32F03"; - flash_size = 0x8000; + flash_size = 32U; /* KiB */ break; case 0x445U: /* STM32F04 RM0091 Rev. 7, STM32F070x6 RM0360 Rev. 4 */ target->driver = "STM32F04/F070x6"; - flash_size = 0x8000; + flash_size = 32U; /* KiB */ break; case 0x440U: /* STM32F05 RM0091 Rev. 7, STM32F030x8 RM0360 Rev. 4 */ target->driver = "STM32F05/F030x8"; - flash_size = 0x10000; + flash_size = 64U; /* KiB */ break; case 0x448U: /* STM32F07 RM0091 Rev. 7, STM32F070xb RM0360 Rev. 4 */ target->driver = "STM32F07"; - flash_size = 0x20000; - block_size = 0x800; + flash_size = 128U; /* KiB */ + block_size = 2U; /* KiB */ break; case 0x442U: /* STM32F09 RM0091 Rev. 7, STM32F030xc RM0360 Rev. 4 */ target->driver = "STM32F09/F030xc"; - flash_size = 0x40000; - block_size = 0x800; + flash_size = 256U; /* KiB */ + block_size = 2U; /* KiB */ break; - default: /* NONE */ - return false; + default: + return false; /* Unknown/undocumented */ } target->part_id = device_id; - target_add_ram(target, 0x20000000, 0x5000); - stm32f1_add_flash(target, 0x8000000, flash_size, block_size); - target_add_commands(target, stm32f1_cmd_list, "STM32F0"); - return true; -} -static bool stm32f1_flash_unlock(target_s *target, uint32_t bank_offset) -{ - target_mem_write32(target, FLASH_KEYR + bank_offset, KEY1); - target_mem_write32(target, FLASH_KEYR + bank_offset, KEY2); - uint32_t ctrl = target_mem_read32(target, FLASH_CR); - if (ctrl & FLASH_CR_LOCK) - DEBUG_ERROR("unlock failed, cr: 0x%08" PRIx32 "\n", ctrl); - return !(ctrl & FLASH_CR_LOCK); -} + target_add_ram(target, STM32F10X_SRAM_ADDR, ram_size << 10U); /* KiB to bytes */ -static inline void stm32f1_flash_clear_eop(target_s *const target, const uint32_t bank_offset) -{ - const uint32_t status = target_mem_read32(target, FLASH_SR + bank_offset); - target_mem_write32(target, FLASH_SR + bank_offset, status | SR_EOP); /* EOP is W1C */ -} - -static bool stm32f1_flash_busy_wait( - target_s *const target, const uint32_t bank_offset, platform_timeout_s *const timeout) -{ - /* Read FLASH_SR to poll for BSY bit */ - uint32_t status = FLASH_SR_BSY; - /* - * Please note that checking EOP here is only legal because every operation is preceded by - * a call to stm32f1_flash_clear_eop. Without this the flag could be stale from a previous - * operation and is always set at the end of every program/erase operation. - * For more information, see FLASH_SR register description §3.4 pg 25. - * https://www.st.com/resource/en/programming_manual/pm0075-stm32f10xxx-flash-memory-microcontrollers-stmicroelectronics.pdf - */ - while (!(status & SR_EOP) && (status & FLASH_SR_BSY)) { - status = target_mem_read32(target, FLASH_SR + bank_offset); - if (target_check_error(target)) { - DEBUG_ERROR("Lost communications with target"); - return false; - } - if (timeout) - target_print_progress(timeout); - }; - if (status & SR_ERROR_MASK) - DEBUG_ERROR("stm32f1 flash error 0x%" PRIx32 "\n", status); - return !(status & SR_ERROR_MASK); -} - -static uint32_t stm32f1_bank_offset_for(target_addr_t addr) -{ - if (addr >= FLASH_BANK_SPLIT) - return FLASH_BANK2_OFFSET; - return FLASH_BANK1_OFFSET; -} - -static bool stm32f1_flash_erase(target_flash_s *flash, target_addr_t addr, size_t length) -{ - target_s *target = flash->t; - target_addr_t end = addr + length - 1U; - DEBUG_TARGET("%s: at %08" PRIx32 "\n", __func__, addr); - - /* Unlocked an appropriate flash bank */ - if ((target->part_id == 0x430U && end >= FLASH_BANK_SPLIT && !stm32f1_flash_unlock(target, FLASH_BANK2_OFFSET)) || - (addr < FLASH_BANK_SPLIT && !stm32f1_flash_unlock(target, 0))) - return false; - - const uint32_t bank_offset = stm32f1_bank_offset_for(addr); - stm32f1_flash_clear_eop(target, bank_offset); - - /* Flash page erase instruction */ - target_mem_write32(target, FLASH_CR + bank_offset, FLASH_CR_PER); - /* write address to FMA */ - target_mem_write32(target, FLASH_AR + bank_offset, addr); - /* Flash page erase start instruction */ - target_mem_write32(target, FLASH_CR + bank_offset, FLASH_CR_STRT | FLASH_CR_PER); - - /* Wait for completion or an error */ - return stm32f1_flash_busy_wait(target, bank_offset, NULL); -} - -static size_t stm32f1_bank1_length(target_addr_t addr, size_t len) -{ - if (addr >= FLASH_BANK_SPLIT) - return 0; - if (addr + len > FLASH_BANK_SPLIT) - return FLASH_BANK_SPLIT - addr; - return len; -} - -static bool stm32f1_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len) -{ - target_s *target = flash->t; - const size_t offset = stm32f1_bank1_length(dest, len); - DEBUG_TARGET("%s: at %08" PRIx32 " for %zu bytes\n", __func__, dest, len); - - /* Start by writing any bank 1 data */ - if (offset) { - stm32f1_flash_clear_eop(target, FLASH_BANK1_OFFSET); - - target_mem_write32(target, FLASH_CR, FLASH_CR_PG); - cortexm_mem_write_sized(target, dest, src, offset, ALIGN_16BIT); - - /* Wait for completion or an error */ - if (!stm32f1_flash_busy_wait(target, FLASH_BANK1_OFFSET, NULL)) - return false; - } - - /* If there's anything to write left over and we're on a part with a second bank, write to bank 2 */ - const size_t remainder = len - offset; - if (target->part_id == 0x430U && remainder) { - const uint8_t *data = src; - stm32f1_flash_clear_eop(target, FLASH_BANK2_OFFSET); - - target_mem_write32(target, FLASH_CR + FLASH_BANK2_OFFSET, FLASH_CR_PG); - cortexm_mem_write_sized(target, dest + offset, data + offset, remainder, ALIGN_16BIT); - - /* Wait for completion or an error */ - if (!stm32f1_flash_busy_wait(target, FLASH_BANK2_OFFSET, NULL)) - return false; - } - - return true; -} - -static bool stm32f1_mass_erase_bank( - target_s *const target, const uint32_t bank_offset, platform_timeout_s *const timeout) -{ - /* Unlock the bank */ - if (!stm32f1_flash_unlock(target, bank_offset)) - return false; - stm32f1_flash_clear_eop(target, bank_offset); - - /* Flash mass erase start instruction */ - target_mem_write32(target, FLASH_CR + bank_offset, FLASH_CR_MER); - target_mem_write32(target, FLASH_CR + bank_offset, FLASH_CR_STRT | FLASH_CR_MER); - - /* Wait for completion or an error */ - return stm32f1_flash_busy_wait(target, bank_offset, timeout); -} - -static bool stm32f1_mass_erase(target_s *target) -{ - if (!stm32f1_flash_unlock(target, 0)) - return false; - - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); - if (!stm32f1_mass_erase_bank(target, FLASH_BANK1_OFFSET, &timeout)) - return false; - - /* If we're on a part that has a second bank, mass erase that bank too */ - if (target->part_id == 0x430U) - return stm32f1_mass_erase_bank(target, FLASH_BANK2_OFFSET, &timeout); - return true; -} - -static uint16_t stm32f1_flash_readable_key(const target_s *const target) -{ - switch (target->part_id) { - case 0x422U: /* STM32F30x */ - case 0x432U: /* STM32F37x */ - case 0x438U: /* STM32F303x6/8 and STM32F328 */ - case 0x440U: /* STM32F0 */ - case 0x446U: /* STM32F303xD/E and STM32F398xE */ - case 0x445U: /* STM32F04 RM0091 Rev.7, STM32F070x6 RM0360 Rev. 4*/ - case 0x448U: /* STM32F07 RM0091 Rev.7, STM32F070xb RM0360 Rev. 4*/ - case 0x442U: /* STM32F09 RM0091 Rev.7, STM32F030xc RM0360 Rev. 4*/ - return FLASH_OBP_RDP_KEY_F3; - } - return FLASH_OBP_RDP_KEY; -} - -static bool stm32f1_option_erase(target_s *target) -{ - stm32f1_flash_clear_eop(target, FLASH_BANK1_OFFSET); - - /* Erase option bytes instruction */ - target_mem_write32(target, FLASH_CR, FLASH_CR_OPTER | FLASH_CR_OPTWRE); - target_mem_write32(target, FLASH_CR, FLASH_CR_STRT | FLASH_CR_OPTER | FLASH_CR_OPTWRE); - - /* Wait for completion or an error */ - return stm32f1_flash_busy_wait(target, FLASH_BANK1_OFFSET, NULL); -} - -static bool stm32f1_option_write_erased( - target_s *const target, const size_t offset, const uint16_t value, const bool write16_broken) -{ - if (value == 0xffffU) - return true; - - stm32f1_flash_clear_eop(target, FLASH_BANK1_OFFSET); - - /* Erase option bytes instruction */ - target_mem_write32(target, FLASH_CR, FLASH_CR_OPTPG | FLASH_CR_OPTWRE); - - const uint32_t addr = FLASH_OBP_RDP + (offset * 2U); - if (write16_broken) - target_mem_write32(target, addr, 0xffff0000U | value); + if (flash_size > STM32F10X_FLASH_BANK_SIZE) + stm32_add_banked_flash(target, STM32F10X_FLASH_MEMORY_ADDR, flash_size << 10U, STM32F10X_FLASH_BANK_SPLIT_ADDR, + STM32F10X_FPEC_BASE, block_size << 10U); else - target_mem_write16(target, addr, value); + stm32_add_flash(target, STM32F10X_FLASH_MEMORY_ADDR, flash_size << 10U, STM32F10X_FPEC_BASE, + block_size << 10U); /* KiB to bytes */ - /* Wait for completion or an error */ - const bool result = stm32f1_flash_busy_wait(target, FLASH_BANK1_OFFSET, NULL); - if (result || offset != 0U) - return result; - /* - * In the case that the write failed and we're handling option byte 0 (RDP), - * check if we got a status of "Program Error" in FLASH_SR, indicating the target - * refused to erase the read protection option bytes (and turn it into a truthy return). - */ - const uint8_t status = target_mem_read32(target, FLASH_SR) & SR_ERROR_MASK; - return status == SR_PROG_ERROR; -} - -static bool stm32f1_option_write(target_s *const target, const uint32_t addr, const uint16_t value) -{ - const uint32_t index = (addr - FLASH_OBP_RDP) >> 1U; - /* If index would be negative, the high most bit is set, so we get a giant positive number. */ - if (index > 7U) - return false; - - uint16_t opt_val[8]; - /* Retrieve old values */ - for (size_t i = 0U; i < 16U; i += 4U) { - const size_t offset = i >> 1U; - uint32_t val = target_mem_read32(target, FLASH_OBP_RDP + i); - opt_val[offset] = val & 0xffffU; - opt_val[offset + 1U] = val >> 16U; - } - - if (opt_val[index] == value) - return true; - - /* Check for erased value */ - if (opt_val[index] != 0xffffU && !stm32f1_option_erase(target)) - return false; - opt_val[index] = value; - - /* - * Write changed values, taking into account if we can use 32- or have to use 16-bit writes. - * GD32E230 is a special case as target_mem_write16 does not work - */ - const bool write16_broken = target->part_id == 0x410U && (target->cpuid & CORTEX_CPUID_PARTNO_MASK) == CORTEX_M23; - for (size_t i = 0U; i < 8U; ++i) { - if (!stm32f1_option_write_erased(target, i, opt_val[i], write16_broken)) - return false; - } - - return true; -} - -static bool stm32f1_cmd_option(target_s *target, int argc, const char **argv) -{ - const uint32_t read_protected = target_mem_read32(target, FLASH_OBR) & FLASH_OBR_RDPRT; - const bool erase_requested = argc == 2 && strcmp(argv[1], "erase") == 0; - /* Fast-exit if the Flash is not readable and the user didn't ask us to erase the option bytes */ - if (read_protected && !erase_requested) { - tc_printf(target, "Device is Read Protected\nUse `monitor option erase` to unprotect and erase device\n"); - return true; - } - - /* Unprotect the option bytes so we can modify them */ - if (!stm32f1_flash_unlock(target, FLASH_BANK1_OFFSET)) - return false; - target_mem_write32(target, FLASH_OPTKEYR, KEY1); - target_mem_write32(target, FLASH_OPTKEYR, KEY2); - - if (erase_requested) { - /* When the user asks us to erase the option bytes, kick of an erase */ - if (!stm32f1_option_erase(target)) - return false; - /* - * Write the option bytes Flash readable key, taking into account if we can - * use 32- or have to use 16-bit writes. - * GD32E230 is a special case as target_mem_write16 does not work - */ - const bool write16_broken = - target->part_id == 0x410U && (target->cpuid & CORTEX_CPUID_PARTNO_MASK) == CORTEX_M23; - if (!stm32f1_option_write_erased(target, 0U, stm32f1_flash_readable_key(target), write16_broken)) - return false; - } else if (argc == 3) { - /* If 3 arguments are given, assume the second is an address, and the third a value */ - const uint32_t addr = strtoul(argv[1], NULL, 0); - const uint32_t val = strtoul(argv[2], NULL, 0); - /* Try and program the new option value to the requested option byte */ - if (!stm32f1_option_write(target, addr, val)) - return false; - } else - tc_printf(target, "usage: monitor option erase\nusage: monitor option \n"); - - /* When all gets said and done, display the current option bytes values */ - for (size_t i = 0U; i < 16U; i += 4U) { - const uint32_t addr = FLASH_OBP_RDP + i; - const uint32_t val = target_mem_read32(target, addr); - tc_printf(target, "0x%08X: 0x%04X\n", addr, val & 0xffffU); - tc_printf(target, "0x%08X: 0x%04X\n", addr + 2U, val >> 16U); - } + target_add_commands(target, stm32_cmd_list, target->driver); return true; } diff --git a/src/target/stm32f4.c b/src/target/stm32f4.c index 54d3c2f4633..f40b4b6afbb 100644 --- a/src/target/stm32f4.c +++ b/src/target/stm32f4.c @@ -52,7 +52,7 @@ static bool stm32f4_attach(target_s *t); static void stm32f4_detach(target_s *t); static bool stm32f4_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); static bool stm32f4_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len); -static bool stm32f4_mass_erase(target_s *t); +static bool stm32f4_mass_erase(target_s *target, platform_timeout_s *print_progess); /* Flash Program and Erase Controller Register Map */ #define FPEC_BASE 0x40023c00U @@ -532,21 +532,19 @@ static bool stm32f4_flash_write(target_flash_s *f, target_addr_t dest, const voi return stm32f4_flash_busy_wait(t, NULL); } -static bool stm32f4_mass_erase(target_s *t) +static bool stm32f4_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { /* XXX: Is it correct to grab the most recently added Flash region here? What is this really trying to do? */ - stm32f4_flash_s *sf = (stm32f4_flash_s *)t->flash; - stm32f4_flash_unlock(t); + stm32f4_flash_s *stm32f4_flash = (stm32f4_flash_s *)target->flash; + stm32f4_flash_unlock(target); /* Flash mass erase start instruction */ - const uint32_t ctrl = FLASH_CR_MER | (sf->bank_split ? FLASH_CR_MER1 : 0); - target_mem_write32(t, FLASH_CR, ctrl); - target_mem_write32(t, FLASH_CR, ctrl | FLASH_CR_STRT); + const uint32_t ctrl = FLASH_CR_MER | (stm32f4_flash->bank_split ? FLASH_CR_MER1 : 0); + target_mem_write32(target, FLASH_CR, ctrl); + target_mem_write32(target, FLASH_CR, ctrl | FLASH_CR_STRT); - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); /* Wait for completion or an error */ - return stm32f4_flash_busy_wait(t, &timeout); + return stm32f4_flash_busy_wait(target, print_progess); } /* diff --git a/src/target/stm32g0.c b/src/target/stm32g0.c index 83451529adb..5ff4093e6d1 100644 --- a/src/target/stm32g0.c +++ b/src/target/stm32g0.c @@ -177,7 +177,7 @@ static bool stm32g0_attach(target_s *t); static void stm32g0_detach(target_s *t); static bool stm32g0_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); static bool stm32g0_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len); -static bool stm32g0_mass_erase(target_s *t); +static bool stm32g0_mass_erase(target_s *target, platform_timeout_s *print_progess); /* Custom commands */ static bool stm32g0_cmd_erase_bank(target_s *t, int argc, const char **argv); @@ -478,24 +478,22 @@ static bool stm32g0_flash_write(target_flash_s *f, target_addr_t dest, const voi return true; } -static bool stm32g0_mass_erase(target_s *t) +static bool stm32g0_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { const uint32_t ctrl = FLASH_CR_MER1 | FLASH_CR_MER2 | FLASH_CR_START; - stm32g0_flash_unlock(t); - target_mem_write32(t, FLASH_CR, ctrl); + stm32g0_flash_unlock(target); + target_mem_write32(target, FLASH_CR, ctrl); - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); /* Wait for completion or an error */ - if (!stm32g0_wait_busy(t, &timeout)) { - stm32g0_flash_op_finish(t); + if (!stm32g0_wait_busy(target, print_progess)) { + stm32g0_flash_op_finish(target); return false; } /* Check for error */ - const uint16_t status = target_mem_read32(t, FLASH_SR); - stm32g0_flash_op_finish(t); + const uint16_t status = target_mem_read32(target, FLASH_SR); + stm32g0_flash_op_finish(target); return !(status & FLASH_SR_ERROR_MASK); } diff --git a/src/target/stm32h5.c b/src/target/stm32h5.c index a31bb08a628..31326d814cf 100644 --- a/src/target/stm32h5.c +++ b/src/target/stm32h5.c @@ -128,7 +128,7 @@ static bool stm32h5_enter_flash_mode(target_s *target); static bool stm32h5_exit_flash_mode(target_s *target); static bool stm32h5_flash_erase(target_flash_s *flash, target_addr_t addr, size_t len); static bool stm32h5_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len); -static bool stm32h5_mass_erase(target_s *target); +static bool stm32h5_mass_erase(target_s *target, platform_timeout_s *print_progess); static void stm32h5_add_flash( target_s *const target, const uint32_t base_addr, const size_t length, const uint32_t bank_and_sector_count) @@ -282,22 +282,13 @@ static bool stm32h5_flash_write( return true; } -static bool stm32h5_mass_erase(target_s *const target) +static bool stm32h5_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { - /* To start mass erase, enter into Flash mode */ - if (!stm32h5_enter_flash_mode(target)) - return false; - - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); /* Trigger the mass erase */ target_mem_write32(target, STM32H5_FLASH_CTRL, STM32H5_FLASH_CTRL_MASS_ERASE); target_mem_write32(target, STM32H5_FLASH_CTRL, STM32H5_FLASH_CTRL_MASS_ERASE | STM32H5_FLASH_CTRL_START); /* And wait for it to complete, reporting errors along the way */ - const bool result = stm32h5_flash_wait_complete(target, &timeout); - - /* When done, leave Flash mode */ - return stm32h5_exit_flash_mode(target) && result; + return stm32h5_flash_wait_complete(target, print_progess); } static bool stm32h5_cmd_uid(target_s *target, int argc, const char **argv) diff --git a/src/target/stm32h7.c b/src/target/stm32h7.c index cacbb5eee67..53b8371b361 100644 --- a/src/target/stm32h7.c +++ b/src/target/stm32h7.c @@ -160,7 +160,7 @@ static bool stm32h7_attach(target_s *target); static void stm32h7_detach(target_s *target); static bool stm32h7_flash_erase(target_flash_s *target_flash, target_addr_t addr, size_t len); static bool stm32h7_flash_write(target_flash_s *target_flash, target_addr_t dest, const void *src, size_t len); -static bool stm32h7_mass_erase(target_s *target); +static bool stm32h7_mass_erase(target_s *target, platform_timeout_s *print_progess); static void stm32h7_add_flash(target_s *target, uint32_t addr, size_t length, size_t blocksize) { @@ -398,7 +398,7 @@ static bool stm32h7_check_bank(target_s *const target, const uint32_t reg_base) } /* Both banks are erased in parallel.*/ -static bool stm32h7_mass_erase(target_s *target) +static bool stm32h7_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { align_e psize = ALIGN_64BIT; /* @@ -415,11 +415,9 @@ static bool stm32h7_mass_erase(target_s *target) !stm32h7_erase_bank(target, psize, STM32H7_FLASH_BANK2_BASE, FPEC2_BASE)) return false; - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); /* Wait for the banks to finish erasing */ - if (!stm32h7_wait_erase_bank(target, &timeout, FPEC1_BASE) || - !stm32h7_wait_erase_bank(target, &timeout, FPEC2_BASE)) + if (!stm32h7_wait_erase_bank(target, print_progess, FPEC1_BASE) || + !stm32h7_wait_erase_bank(target, print_progess, FPEC2_BASE)) return false; /* Check the banks for final errors */ diff --git a/src/target/stm32l0.c b/src/target/stm32l0.c index 9bf392c71d7..a8904d4fed6 100644 --- a/src/target/stm32l0.c +++ b/src/target/stm32l0.c @@ -131,8 +131,7 @@ static bool stm32lx_nvm_data_erase(target_flash_s *flash, target_addr_t addr, si static bool stm32lx_nvm_data_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t length); static bool stm32lx_protected_attach(target_s *target); -static bool stm32lx_protected_mass_erase(target_s *target); -static bool stm32lx_mass_erase(target_s *target); +static bool stm32lx_protected_mass_erase(target_s *target, platform_timeout_s *print_progess); static bool stm32lx_cmd_option(target_s *target, int argc, const char **argv); static bool stm32lx_cmd_eeprom(target_s *target, int argc, const char **argv); @@ -263,8 +262,7 @@ bool stm32l0_probe(target_s *const target) if (protected) { target->attach = stm32lx_protected_attach; target->mass_erase = stm32lx_protected_mass_erase; - } else - target->mass_erase = stm32lx_mass_erase; + } return true; } @@ -457,7 +455,7 @@ static bool stm32lx_protected_attach(target_s *const target) return true; } -static bool stm32lx_protected_mass_erase(target_s *const target) +static bool stm32lx_protected_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { const uint32_t nvm = stm32lx_nvm_phys(target); if (!stm32lx_nvm_opt_unlock(target, nvm)) @@ -468,27 +466,14 @@ static bool stm32lx_protected_mass_erase(target_s *const target) target_mem_write32(target, STM32Lx_NVM_OPT_PHYS, 0xff5500aaU); target_mem_write32(target, STM32Lx_NVM_PECR(nvm), STM32Lx_NVM_PECR_OBL_LAUNCH); - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); - while (target_mem_read32(target, STM32Lx_NVM_SR(nvm)) & STM32Lx_NVM_SR_BSY) - target_print_progress(&timeout); + target_print_progress(print_progess); /* Disable further programming by locking PECR */ stm32lx_nvm_lock(target, nvm); return true; } -static bool stm32lx_mass_erase(target_s *const target) -{ - for (target_flash_s *flash = target->flash; flash; flash = flash->next) { - const int result = stm32lx_nvm_prog_erase(flash, flash->start, flash->length); - if (result != 0) - return false; - } - return true; -} - /* * Write one option word. * The address is the physical address of the word and the value is a complete word value. diff --git a/src/target/stm32l4.c b/src/target/stm32l4.c index 70eae2123d6..a39dbec1028 100644 --- a/src/target/stm32l4.c +++ b/src/target/stm32l4.c @@ -62,7 +62,7 @@ static bool stm32l4_attach(target_s *t); static void stm32l4_detach(target_s *t); static bool stm32l4_flash_erase(target_flash_s *f, target_addr_t addr, size_t len); static bool stm32l4_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len); -static bool stm32l4_mass_erase(target_s *t); +static bool stm32l4_mass_erase(target_s *target, platform_timeout_s *print_progess); const command_s stm32l4_cmd_list[] = { {"erase_bank1", stm32l4_cmd_erase_bank1, "Erase entire bank1 flash memory"}, @@ -768,7 +768,7 @@ static bool stm32l4_flash_write(target_flash_s *f, target_addr_t dest, const voi return stm32l4_flash_busy_wait(t, NULL); } -static bool stm32l4_cmd_erase(target_s *const t, const uint32_t action) +static bool stm32l4_cmd_erase(target_s *const t, const uint32_t action, platform_timeout_s *const print_progess) { stm32l4_flash_unlock(t); /* Erase time is 25 ms. Timeout logic shouldn't get fired.*/ @@ -776,15 +776,13 @@ static bool stm32l4_cmd_erase(target_s *const t, const uint32_t action) stm32l4_flash_write32(t, FLASH_CR, action); stm32l4_flash_write32(t, FLASH_CR, action | FLASH_CR_STRT); - platform_timeout_s timeout; - platform_timeout_set(&timeout, 500); /* Wait for completion or an error */ - return stm32l4_flash_busy_wait(t, &timeout); + return stm32l4_flash_busy_wait(t, print_progess); } -static bool stm32l4_mass_erase(target_s *const t) +static bool stm32l4_mass_erase(target_s *const target, platform_timeout_s *const print_progess) { - return stm32l4_cmd_erase(t, FLASH_CR_MER1 | FLASH_CR_MER2); + return stm32l4_cmd_erase(target, FLASH_CR_MER1 | FLASH_CR_MER2, print_progess); } static bool stm32l4_cmd_erase_bank1(target_s *const t, const int argc, const char **const argv) @@ -792,7 +790,7 @@ static bool stm32l4_cmd_erase_bank1(target_s *const t, const int argc, const cha (void)argc; (void)argv; gdb_outf("Erasing bank %u: ", 1); - const bool result = stm32l4_cmd_erase(t, FLASH_CR_MER1); + const bool result = stm32l4_cmd_erase(t, FLASH_CR_MER1, NULL); gdb_out("done\n"); return result; } @@ -802,7 +800,7 @@ static bool stm32l4_cmd_erase_bank2(target_s *const t, const int argc, const cha (void)argc; (void)argv; gdb_outf("Erasing bank %u: ", 2); - const bool result = stm32l4_cmd_erase(t, FLASH_CR_MER2); + const bool result = stm32l4_cmd_erase(t, FLASH_CR_MER2, NULL); gdb_out("done\n"); return result; } diff --git a/src/target/target.c b/src/target/target.c index 3396d99c469..b2d490732d8 100644 --- a/src/target/target.c +++ b/src/target/target.c @@ -436,13 +436,11 @@ static bool target_cmd_mass_erase(target_s *const t, const int argc, const char { (void)argc; (void)argv; - if (!t || !t->mass_erase) { - gdb_out("Mass erase not implemented for target_s"); - return true; - } + gdb_out("Erasing device Flash: "); - const bool result = t->mass_erase(t); + const bool result = target_flash_mass_erase(t); gdb_out("done\n"); + return result; } diff --git a/src/target/target_flash.c b/src/target/target_flash.c index bacd19a1c51..ef33b47df23 100644 --- a/src/target/target_flash.c +++ b/src/target/target_flash.c @@ -154,18 +154,29 @@ bool target_flash_erase(target_s *target, target_addr_t addr, size_t len) active_flash = flash; } + /* Align the start address to the erase block size */ const target_addr_t local_start_addr = addr & ~(flash->blocksize - 1U); - const target_addr_t local_end_addr = local_start_addr + flash->blocksize; - if (!flash_prepare(flash, FLASH_OPERATION_ERASE)) + /* Check if we can use mass erase */ + const bool can_use_mass_erase = flash->mass_erase != NULL && local_start_addr == flash->start && + (addr + len) >= (flash->start + flash->length); + + /* Calculate the address at the end of the erase block */ + const target_addr_t local_end_addr = + can_use_mass_erase ? flash->start + flash->length : local_start_addr + flash->blocksize; + + if (!flash_prepare(flash, can_use_mass_erase ? FLASH_OPERATION_MASS_ERASE : FLASH_OPERATION_ERASE)) return false; - result &= flash->erase(flash, local_start_addr, flash->blocksize); + /* Erase flash, either a single aligned block size or a full mass erase */ + result &= can_use_mass_erase ? flash->mass_erase(flash, NULL) : + flash->erase(flash, local_start_addr, flash->blocksize); if (!result) { DEBUG_ERROR("Erase failed at %" PRIx32 "\n", local_start_addr); break; } + /* Update the remaining length and address, taking into account the alignment */ len -= MIN(local_end_addr - addr, len); addr = local_end_addr; } @@ -174,6 +185,65 @@ bool target_flash_erase(target_s *target, target_addr_t addr, size_t len) return result; } +static inline bool flash_manual_mass_erase(target_flash_s *const flash, platform_timeout_s *const print_progess) +{ + for (target_addr_t addr = flash->start; addr < (flash->start + flash->length); addr += flash->blocksize) { + if (!flash->erase(flash, addr, flash->blocksize)) + return false; + target_print_progress(print_progess); + } + return true; +} + +/* Run specialized target mass erase if available, otherwise erase all flash' */ +bool target_flash_mass_erase(target_s *const target) +{ + if (!target_enter_flash_mode(target)) + return false; + + /* Setup progress printout */ + platform_timeout_s print_progess; + platform_timeout_set(&print_progess, 500U); + + bool result = false; + if (target->mass_erase) { + DEBUG_INFO("Running specialized target mass erase\n"); + + /* Run specialized target mass erase */ + result = target->mass_erase(target, &print_progess); + } else { + DEBUG_WARN("No specialized target mass erase available, erasing all flash\n"); + + /* Erase all target flash */ + for (target_flash_s *flash = target->flash; flash; flash = flash->next) { + /* If the flash has a mass erase function, use it */ + const bool can_use_mass_erase = flash->mass_erase != NULL; + + if (can_use_mass_erase) + DEBUG_INFO("Running specialized flash mass erase for flash 0x%08" PRIx32 "\n", flash->start); + else + DEBUG_WARN("No specialized flash mass erase available for 0x%08" PRIx32 "\n", flash->start); + + result = flash_prepare(flash, can_use_mass_erase ? FLASH_OPERATION_MASS_ERASE : FLASH_OPERATION_ERASE); + if (!result) { + DEBUG_ERROR("Failed to prepare flash 0x%08" PRIx32 " for mass erase\n", flash->start); + break; + } + + result = can_use_mass_erase ? flash->mass_erase(flash, &print_progess) : + flash_manual_mass_erase(flash, &print_progess); + result &= flash_done(flash); /* Don't overwrite previous result, AND with it instead */ + if (!result) { + DEBUG_ERROR("Failed to mass erase flash 0x%08" PRIx32 "\n", flash->start); + break; + } + } + } + + target_exit_flash_mode(target); + return result; +} + bool flash_buffer_alloc(target_flash_s *flash) { /* Allocate buffer */ diff --git a/src/target/target_internal.h b/src/target/target_internal.h index f78cf00d300..4e7e7d85a13 100644 --- a/src/target/target_internal.h +++ b/src/target/target_internal.h @@ -33,6 +33,7 @@ target_s *target_new(void); typedef enum flash_operation { FLASH_OPERATION_NONE, FLASH_OPERATION_ERASE, + FLASH_OPERATION_MASS_ERASE, FLASH_OPERATION_WRITE, } flash_operation_e; @@ -48,27 +49,29 @@ typedef struct target_flash target_flash_s; typedef bool (*flash_prepare_func)(target_flash_s *flash); typedef bool (*flash_erase_func)(target_flash_s *flash, target_addr_t addr, size_t len); +typedef bool (*flash_mass_erase_func)(target_flash_s *flash, platform_timeout_s *print_progess); typedef bool (*flash_write_func)(target_flash_s *flash, target_addr_t dest, const void *src, size_t len); typedef bool (*flash_done_func)(target_flash_s *flash); struct target_flash { - target_s *t; /* Target this flash is attached to */ - target_addr_t start; /* Start address of flash */ - size_t length; /* Flash length */ - size_t blocksize; /* Erase block size */ - size_t writesize; /* Write operation size, must be <= blocksize/writebufsize */ - size_t writebufsize; /* Size of write buffer, this is calculated and not set in target code */ - uint8_t erased; /* Byte erased state */ - uint8_t operation; /* Current Flash operation (none means it's idle/unprepared) */ - flash_prepare_func prepare; /* Prepare for flash operations */ - flash_erase_func erase; /* Erase a range of flash */ - flash_write_func write; /* Write to flash */ - flash_done_func done; /* Finish flash operations */ - void *buf; /* Buffer for flash operations */ - target_addr_t buf_addr_base; /* Address of block this buffer is for */ - target_addr_t buf_addr_low; /* Address of lowest byte written */ - target_addr_t buf_addr_high; /* Address of highest byte written */ - target_flash_s *next; /* Next flash in list */ + target_s *t; /* Target this flash is attached to */ + target_addr_t start; /* Start address of flash */ + size_t length; /* Flash length */ + size_t blocksize; /* Erase block size */ + size_t writesize; /* Write operation size, must be <= blocksize/writebufsize */ + size_t writebufsize; /* Size of write buffer, this is calculated and not set in target code */ + uint8_t erased; /* Byte erased state */ + uint8_t operation; /* Current Flash operation (none means it's idle/unprepared) */ + flash_prepare_func prepare; /* Prepare for flash operations */ + flash_erase_func erase; /* Erase a range of flash */ + flash_mass_erase_func mass_erase; /* Mass erase flash (this flash only) */ + flash_write_func write; /* Write to flash */ + flash_done_func done; /* Finish flash operations */ + void *buf; /* Buffer for flash operations */ + target_addr_t buf_addr_base; /* Address of block this buffer is for */ + target_addr_t buf_addr_low; /* Address of lowest byte written */ + target_addr_t buf_addr_high; /* Address of highest byte written */ + target_flash_s *next; /* Next flash in list */ }; typedef bool (*cmd_handler_fn)(target_s *target, int argc, const char **argv); @@ -132,7 +135,7 @@ struct target { breakwatch_s *bw_list; /* Recovery functions */ - bool (*mass_erase)(target_s *target); + bool (*mass_erase)(target_s *target, platform_timeout_s *print_progess); /* Mass erase all target flash */ /* Flash functions */ bool (*enter_flash_mode)(target_s *target); diff --git a/src/target/target_probe.c b/src/target/target_probe.c index 5edecacd92b..591865c34b9 100644 --- a/src/target/target_probe.c +++ b/src/target/target_probe.c @@ -75,14 +75,19 @@ CORTEXA_PROBE_WEAK_NOP(cortexa_probe) CORTEXM_PROBE_WEAK_NOP(cortexm_probe) +TARGET_PROBE_WEAK_NOP(riscv32_probe) +TARGET_PROBE_WEAK_NOP(riscv64_probe) + CORTEXM_PROBE_WEAK_NOP(kinetis_mdm_probe) CORTEXM_PROBE_WEAK_NOP(nrf51_mdm_probe) CORTEXM_PROBE_WEAK_NOP(efm32_aap_probe) CORTEXM_PROBE_WEAK_NOP(rp_rescue_probe) CORTEXM_PROBE_WEAK_NOP(lpc55_dmap_probe) +TARGET_PROBE_WEAK_NOP(ch32vx_probe) TARGET_PROBE_WEAK_NOP(ch32f1_probe) TARGET_PROBE_WEAK_NOP(gd32f1_probe) +TARGET_PROBE_WEAK_NOP(gd32vf1_probe) TARGET_PROBE_WEAK_NOP(gd32f4_probe) TARGET_PROBE_WEAK_NOP(stm32f1_probe) TARGET_PROBE_WEAK_NOP(at32fxx_probe) @@ -120,5 +125,6 @@ TARGET_PROBE_WEAK_NOP(renesas_probe) TARGET_PROBE_WEAK_NOP(mm32l0xx_probe) TARGET_PROBE_WEAK_NOP(mm32f3xx_probe) TARGET_PROBE_WEAK_NOP(imxrt_probe) +TARGET_PROBE_WEAK_NOP(esp32c3_probe) LPC55_DP_PREPARE_WEAK_NOP(lpc55_dp_prepare) diff --git a/src/target/target_probe.h b/src/target/target_probe.h index 8829f81edf0..25620d570c1 100644 --- a/src/target/target_probe.h +++ b/src/target/target_probe.h @@ -28,9 +28,9 @@ #define PROBE(x) \ do { \ DEBUG_TARGET("Calling " STRINGIFY(x) "\n"); \ - if ((x)(t)) \ + if ((x)(target)) \ return true; \ - target_check_error(t); \ + target_check_error(target); \ } while (0) /* @@ -42,17 +42,22 @@ bool cortexa_probe(adiv5_access_port_s *apb, uint32_t debug_base); bool cortexm_probe(adiv5_access_port_s *ap); +bool riscv32_probe(target_s *target); +bool riscv64_probe(target_s *target); + bool kinetis_mdm_probe(adiv5_access_port_s *ap); bool nrf51_mdm_probe(adiv5_access_port_s *ap); bool efm32_aap_probe(adiv5_access_port_s *ap); bool rp_rescue_probe(adiv5_access_port_s *ap); bool lpc55_dmap_probe(adiv5_access_port_s *ap); +bool ch32vx_probe(target_s *target); bool ch32f1_probe(target_s *target); // will catch all the clones bool at32fxx_probe(target_s *target); // STM32 clones from Artery bool mm32l0xx_probe(target_s *target); bool mm32f3xx_probe(target_s *target); bool gd32f1_probe(target_s *target); +bool gd32vf1_probe(target_s *target); bool gd32f4_probe(target_s *target); bool stm32f1_probe(target_s *target); bool stm32f4_probe(target_s *target); @@ -87,6 +92,7 @@ bool ke04_probe(target_s *target); bool rp_probe(target_s *target); bool renesas_probe(target_s *target); bool imxrt_probe(target_s *target); +bool esp32c3_probe(target_s *target); void lpc55_dp_prepare(adiv5_debug_port_s *dp);