From 355c8d23e494182114f124929264447b63011e32 Mon Sep 17 00:00:00 2001 From: Renze Nicolai Date: Fri, 27 Dec 2024 23:25:58 +0100 Subject: [PATCH] Initial commit --- .clang-format | 35 ++ .github/workflows/upload_component.yml | 21 ++ CMakeLists.txt | 9 + LICENSE | 11 + Makefile | 10 + README.md | 10 + idf_component.yml | 7 + include/rvswd.h | 29 ++ include/rvswd_ch32v203.h | 16 + src/rvswd.c | 190 ++++++++++ src/rvswd_ch32v203.c | 500 +++++++++++++++++++++++++ 11 files changed, 838 insertions(+) create mode 100644 .clang-format create mode 100644 .github/workflows/upload_component.yml create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 idf_component.yml create mode 100644 include/rvswd.h create mode 100644 include/rvswd_ch32v203.h create mode 100644 src/rvswd.c create mode 100644 src/rvswd_ch32v203.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..eb1870e --- /dev/null +++ b/.clang-format @@ -0,0 +1,35 @@ +BasedOnStyle: Google +CommentPragmas: '(@copydoc)' + +# Pointers +DerivePointerAlignment: false +PointerAlignment: Left + +# Indents +IndentWidth: 4 +TabWidth: 4 +UseTab: "Never" +NamespaceIndentation: All +IndentGotoLabels: true +AlignConsecutiveMacros: true + +# Line breaks +AlwaysBreakTemplateDeclarations: No +AllowShortFunctionsOnASingleLine: None +AllowShortEnumsOnASingleLine: false +KeepEmptyLinesAtTheStartOfBlocks: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false +ColumnLimit: 120 + +# Includes +IncludeBlocks: Merge diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml new file mode 100644 index 0000000..997487a --- /dev/null +++ b/.github/workflows/upload_component.yml @@ -0,0 +1,21 @@ +name: Push components to Espressif Component Service + +on: + push: + tags: + - v* + +jobs: + upload_components: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Upload components to component service + uses: espressif/upload-components-ci-action@v1 + with: + name: "rvswd" + version: ${{ github.ref_name }} + namespace: "nicolaielectronics" + api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..10dc03f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register( + SRCS + "src/rvswd.c" + "src/rvswd_ch32v203.c" + INCLUDE_DIRS + "include" + REQUIRES + "driver" +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a8e6f4c --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +MIT License (Extended) + +Copyright (c) 2025 Nicolai Electronics + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is provided to do so, subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +2. Notwithstanding any other provision of this License, the Software shall not be used, copied, modified, distributed, or otherwise interacted with by the organization known as Stichting International Festivals for Creative Application of Technology Foundation (IFCAT), nor by any individuals or entities associated with Stichting International Festivals for Creative Application of Technology Foundation (IFCAT), whether directly or indirectly. This prohibition includes but is not limited to employees, affiliates, contractors, and any entities that are controlled or influenced by Stichting International Festivals for Creative Application of Technology Foundation (IFCAT). + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4d105a1 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +# General targets + +.PHONY: all +all: format + +# Formatting + +.PHONY: format +format: + find . -iname '*.h' -o -iname '*.c' -o -iname '*.cpp' | xargs clang-format -i diff --git a/README.md b/README.md new file mode 100644 index 0000000..6fabc93 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# RVSWD programmer component + +This component enables an ESP32 host to program an RVSWD compatible target. RVSWD is a custom two wire programming protocol used by WCH in their CH32 line of microcontrollers. + +Currently the component only supports and has only been tested on the **CH32V203** microcontroller. + +## License + +This project will be made available under unmodified MIT license starting September of 2025. Until then the component is made available under a [modified, extended variant of the MIT license](LICENSE) which explicitly excludes a specific organization from using this component. + diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 0000000..5e4262b --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,7 @@ +dependencies: + cmake_utilities: 0.* + idf: '>=5.3' +description: RVSWD programmer +repository: git://github.com/Nicolai-Electronics/esp32-component-rvswd.git +issues: https://github.com/Nicolai-Electronics/esp32-component-rvswd/issues +url: https://github.com/Nicolai-Electronics/esp32-component-rvswd diff --git a/include/rvswd.h b/include/rvswd.h new file mode 100644 index 0000000..4619ef3 --- /dev/null +++ b/include/rvswd.h @@ -0,0 +1,29 @@ + +/** + * Copyright (c) 2025 Nicolai Electronics + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" + +typedef struct rvswd_handle { + gpio_num_t swdio; + gpio_num_t swclk; +} rvswd_handle_t; + +typedef enum rvswd_result { + RVSWD_OK = 0, + RVSWD_FAIL = 1, + RVSWD_INVALID_ARGS = 2, + RVSWD_PARITY_ERROR = 3, +} rvswd_result_t; + +rvswd_result_t rvswd_init(rvswd_handle_t* handle); +rvswd_result_t rvswd_reset(rvswd_handle_t* handle); +rvswd_result_t rvswd_write(rvswd_handle_t* handle, uint8_t reg, uint32_t value); +rvswd_result_t rvswd_read(rvswd_handle_t* handle, uint8_t reg, uint32_t* value); diff --git a/include/rvswd_ch32v203.h b/include/rvswd_ch32v203.h new file mode 100644 index 0000000..574411c --- /dev/null +++ b/include/rvswd_ch32v203.h @@ -0,0 +1,16 @@ + +/** + * Copyright (c) 2025 Nicolai Electronics + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "rvswd.h" + +// Optional user-defined status update callback. +void ch32_status_callback(char const* msg, int progress, int total); + +// Program and restart the CH32V203. +bool ch32_program(rvswd_handle_t* handle, void const* firmware, size_t firmware_len); diff --git a/src/rvswd.c b/src/rvswd.c new file mode 100644 index 0000000..43649ee --- /dev/null +++ b/src/rvswd.c @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2025 Nicolai Electronics + * + * SPDX-License-Identifier: MIT + */ + +#include "rvswd.h" +#include +#include +#include "rom/ets_sys.h" + +rvswd_result_t rvswd_init(rvswd_handle_t* handle) { + gpio_config_t swio_cfg = { + .pin_bit_mask = BIT64(handle->swdio), + .mode = GPIO_MODE_INPUT_OUTPUT_OD, + .pull_up_en = true, + .pull_down_en = false, + .intr_type = GPIO_INTR_DISABLE, + }; + esp_err_t res = gpio_config(&swio_cfg); + if (res != ESP_OK) { + return RVSWD_FAIL; + } + + gpio_config_t swck_cfg = { + .pin_bit_mask = BIT64(handle->swclk), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = false, + .pull_down_en = false, + .intr_type = GPIO_INTR_DISABLE, + }; + res = gpio_config(&swck_cfg); + if (res != ESP_OK) { + return RVSWD_FAIL; + } + + return RVSWD_OK; +} + +rvswd_result_t rvswd_start(rvswd_handle_t* handle) { + // Start with both lines high + gpio_set_level(handle->swdio, true); + gpio_set_level(handle->swclk, true); + ets_delay_us(2); + + // Pull data low + gpio_set_level(handle->swdio, false); + gpio_set_level(handle->swclk, true); + ets_delay_us(1); + + // Pull clock low + gpio_set_level(handle->swdio, false); + gpio_set_level(handle->swclk, false); + ets_delay_us(1); + return RVSWD_OK; +} + +rvswd_result_t rvswd_stop(rvswd_handle_t* handle) { + // Pull data low + gpio_set_level(handle->swdio, false); + ets_delay_us(1); + gpio_set_level(handle->swclk, true); + ets_delay_us(2); + // Let data float high + gpio_set_level(handle->swdio, true); + ets_delay_us(1); + return RVSWD_OK; +} + +rvswd_result_t rvswd_reset(rvswd_handle_t* handle) { + gpio_set_level(handle->swdio, true); + ets_delay_us(1); + for (uint8_t i = 0; i < 100; i++) { + gpio_set_level(handle->swclk, false); + ets_delay_us(1); + gpio_set_level(handle->swclk, true); + ets_delay_us(1); + } + return rvswd_stop(handle); +} + +void rvswd_write_bit(rvswd_handle_t* handle, bool value) { + gpio_set_level(handle->swdio, value); + gpio_set_level(handle->swclk, false); + gpio_set_level(handle->swclk, true); // Data is sampled on rising edge of clock +} + +bool rvswd_read_bit(rvswd_handle_t* handle) { + gpio_set_level(handle->swdio, true); + gpio_set_level(handle->swclk, false); + gpio_set_level(handle->swclk, true); // Data is output on rising edge of clock + return gpio_get_level(handle->swdio); +} + +rvswd_result_t rvswd_write(rvswd_handle_t* handle, uint8_t reg, uint32_t value) { + rvswd_start(handle); + + // ADDR HOST + bool parity = false; // This time it's odd parity? + for (uint8_t position = 0; position < 7; position++) { + bool bit = (reg >> (6 - position)) & 1; + rvswd_write_bit(handle, bit); + if (bit) parity = !parity; + } + + // Operation: write + rvswd_write_bit(handle, true); + parity = !parity; + + // Parity bit (even) + rvswd_write_bit(handle, parity); + + rvswd_write_bit(handle, 1); + rvswd_write_bit(handle, 0); + rvswd_write_bit(handle, 1); + rvswd_write_bit(handle, 0); + rvswd_write_bit(handle, 1); + + // Data + parity = false; // This time it's even parity? + for (uint8_t position = 0; position < 32; position++) { + bool bit = (value >> (31 - position)) & 1; + rvswd_write_bit(handle, bit); + if (bit) parity = !parity; + } + + // Parity bit + rvswd_write_bit(handle, parity); + + rvswd_write_bit(handle, 1); + rvswd_write_bit(handle, 0); + rvswd_write_bit(handle, 1); + rvswd_write_bit(handle, 1); + rvswd_write_bit(handle, 1); + + rvswd_stop(handle); + + return RVSWD_OK; +} + +rvswd_result_t rvswd_read(rvswd_handle_t* handle, uint8_t reg, uint32_t* value) { + bool parity; + + rvswd_start(handle); + + // ADDR HOST + parity = false; + for (uint8_t position = 0; position < 7; position++) { + bool bit = (reg >> (6 - position)) & 1; + rvswd_write_bit(handle, bit); + if (bit) parity = !parity; + } + + // Operation: read + rvswd_write_bit(handle, false); + + // Parity bit (even) + rvswd_write_bit(handle, parity); + + rvswd_write_bit(handle, 1); + rvswd_write_bit(handle, 0); + rvswd_write_bit(handle, 1); + rvswd_write_bit(handle, 0); + rvswd_write_bit(handle, 1); + + *value = 0; + + // Data + parity = false; + for (uint8_t position = 0; position < 32; position++) { + bool bit = rvswd_read_bit(handle); + if (bit) { + *value |= 1 << (31 - position); + } + if (bit) parity = !parity; + } + + // Parity bit + bool parity_read = rvswd_read_bit(handle); + + rvswd_write_bit(handle, 1); + rvswd_write_bit(handle, 0); + rvswd_write_bit(handle, 1); + rvswd_write_bit(handle, 1); + rvswd_write_bit(handle, 1); + + rvswd_stop(handle); + + return (parity == parity_read) ? RVSWD_OK : RVSWD_FAIL; +} diff --git a/src/rvswd_ch32v203.c b/src/rvswd_ch32v203.c new file mode 100644 index 0000000..48bc48a --- /dev/null +++ b/src/rvswd_ch32v203.c @@ -0,0 +1,500 @@ + +/** + * Copyright (c) 2025 Nicolai Electronics + * + * SPDX-License-Identifier: MIT + */ + +#include "rvswd_ch32v203.h" +#include "esp_log.h" +#include "freertos/projdefs.h" +#include "string.h" + +static char const TAG[] = "RVSWD-CH32V203"; + +#define CH32_REG_DEBUG_DATA0 0x04 // Data register 0, can be used for temporary storage of data +#define CH32_REG_DEBUG_DATA1 0x05 // Data register 1, can be used for temporary storage of data +#define CH32_REG_DEBUG_DMCONTROL 0x10 // Debug module control register +#define CH32_REG_DEBUG_DMSTATUS 0x11 // Debug module status register +#define CH32_REG_DEBUG_HARTINFO 0x12 // Microprocessor status register +#define CH32_REG_DEBUG_ABSTRACTCS 0x16 // Abstract command status register +#define CH32_REG_DEBUG_COMMAND 0x17 // Astract command register +#define CH32_REG_DEBUG_ABSTRACTAUTO 0x18 // Abstract command auto-executtion +#define CH32_REG_DEBUG_PROGBUF0 0x20 // Instruction cache register 0 +#define CH32_REG_DEBUG_PROGBUF1 0x21 // Instruction cache register 1 +#define CH32_REG_DEBUG_PROGBUF2 0x22 // Instruction cache register 2 +#define CH32_REG_DEBUG_PROGBUF3 0x23 // Instruction cache register 3 +#define CH32_REG_DEBUG_PROGBUF4 0x24 // Instruction cache register 4 +#define CH32_REG_DEBUG_PROGBUF5 0x25 // Instruction cache register 5 +#define CH32_REG_DEBUG_PROGBUF6 0x26 // Instruction cache register 6 +#define CH32_REG_DEBUG_PROGBUF7 0x27 // Instruction cache register 7 +#define CH32_REG_DEBUG_HALTSUM0 0x40 // Halt status register +#define CH32_REG_DEBUG_CPBR 0x7C // Capability register +#define CH32_REG_DEBUG_CFGR 0x7D // Configuration register +#define CH32_REG_DEBUG_SHDWCFGR 0x7E // Shadow configuration register + +#define CH32_REGS_CSR 0x0000 // Offsets for accessing CSRs. +#define CH32_REGS_GPR 0x1000 // Offsets for accessing general-purpose (x)registers. + +#define CH32_CFGR_KEY 0x5aa50000 +#define CH32_CFGR_OUTEN (1 << 10) + +// The start of CH32 CODE FLASH region. +#define CH32_CODE_BEGIN 0x08000000 +// the end of the CH32 CODE FLASH region. +#define CH32_CODE_END 0x08004000 + +// FLASH status register. +#define CH32_FLASH_STATR 0x4002200C +// FLASH configuration register. +#define CH32_FLASH_CTLR 0x40022010 +// FLASH address register. +#define CH32_FLASH_ADDR 0x40022014 + +// FLASH is busy writing or erasing. +#define CH32_FLASH_STATR_BUSY (1 << 0) +// FLASH is busy writing +#define CH32_FLASH_STATR_WRBUSY (1 << 1) +// FLASH write protection error +#define CH32_FLASH_STATR_WRPRTERR (1 << 4) +// FLASH is finished with the operation. +#define CH32_FLASH_STATR_EOP (1 << 5) +// FLASH enhanced read mode start +#define CH32_FLASH_STATR_EHMODS (1 << 7) + +// Perform standard programming operation. +#define CH32_FLASH_CTLR_PG (1 << 0) +// Perform 1K sector erase. +#define CH32_FLASH_CTLR_PER (1 << 1) +// Perform full FLASH erase. +#define CH32_FLASH_CTLR_MER (1 << 2) +// Perform user-selected word program. +#define CH32_FLASH_CTLR_OBG (1 << 4) +// Perform user-selected word erasure. +#define CH32_FLASH_CTLR_OBER (1 << 5) +// Start an erase operation. +#define CH32_FLASH_CTLR_STRT (1 << 6) +// Lock the FLASH. +#define CH32_FLASH_CTLR_LOCK (1 << 7) +// Start a fast page programming operation (256 bytes). +#define CH32_FLASH_CTLR_FTPG (1 << 16) +// Start a fast page erase operation (256 bytes). +#define CH32_FLASH_CTLR_FTER (1 << 17) +// Start a page programming operation (256 bytes). +#define CH32_FLASH_CTLR_PGSTRT (1 << 21) + +uint8_t const ch32_readmem[] = {0x88, 0x41, 0x02, 0x90}; + +uint8_t const ch32_writemem[] = {0x88, 0xc1, 0x02, 0x90}; + +rvswd_result_t ch32_halt_microprocessor(rvswd_handle_t* handle) { + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x80000001); // Make the debug module work properly + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x80000001); // Initiate a halt request + + // Get the debug module status information, check rdata[9:8], if the value is 0b11, + // it means the processor enters the halt state normally. Otherwise try again. + uint8_t timeout = 5; + while (1) { + uint32_t value; + rvswd_read(handle, CH32_REG_DEBUG_DMSTATUS, &value); + if (((value >> 8) & 0b11) == 0b11) { // Check that processor has entered halted state + break; + } + if (timeout == 0) { + ESP_LOGE(TAG, "Failed to halt microprocessor, DMSTATUS=%" PRIx32, value); + return false; + } + timeout--; + vTaskDelay(pdMS_TO_TICKS(10)); + } + + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x00000001); // Clear the halt request + ESP_LOGI(TAG, "Microprocessor halted"); + return RVSWD_OK; +} + +rvswd_result_t ch32_resume_microprocessor(rvswd_handle_t* handle) { + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x80000001); // Make the debug module work properly + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x80000001); // Initiate a halt request + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x00000001); // Clear the halt request + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x40000001); // Initiate a resume request + + // Get the debug module status information, check rdata[17:16], + // if the value is 0b11, it means the processor has recovered. + uint8_t timeout = 5; + while (1) { + uint32_t value; + rvswd_read(handle, CH32_REG_DEBUG_DMSTATUS, &value); + if ((((value >> 10) & 0b11) == 0b11)) { + break; + } + if (timeout == 0) { + ESP_LOGE(TAG, "Failed to resume microprocessor, DMSTATUS=%" PRIx32, value); + return RVSWD_FAIL; + } + timeout--; + vTaskDelay(pdMS_TO_TICKS(10)); + } + return RVSWD_OK; +} + +rvswd_result_t ch32_reset_microprocessor_and_run(rvswd_handle_t* handle) { + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x80000001); // Make the debug module work properly + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x80000001); // Initiate a halt request + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x00000001); // Clear the halt request + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x00000003); // Initiate a core reset request + + uint8_t timeout = 5; + while (1) { + uint32_t value; + rvswd_read(handle, CH32_REG_DEBUG_DMSTATUS, &value); + if (((value >> 18) & 0b11) == 0b11) { // Check that processor has been reset + break; + } + if (timeout == 0) { + ESP_LOGE(TAG, "Failed to reset microprocessor"); + return RVSWD_FAIL; + } + timeout--; + vTaskDelay(pdMS_TO_TICKS(10)); + } + + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x00000001); // Clear the core reset request + vTaskDelay(pdMS_TO_TICKS(10)); + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x10000001); // Clear the core reset status signal + vTaskDelay(pdMS_TO_TICKS(10)); + rvswd_write(handle, CH32_REG_DEBUG_DMCONTROL, 0x00000001); // Clear the core reset status signal clear request + vTaskDelay(pdMS_TO_TICKS(10)); + + return RVSWD_OK; +} + +bool ch32_write_cpu_reg(rvswd_handle_t* handle, uint16_t regno, uint32_t value) { + uint32_t command = regno // Register to access. + | (1 << 16) // Write access. + | (1 << 17) // Perform transfer. + | (2 << 20) // 32-bit register access. + | (0 << 24); // Access register command. + + rvswd_write(handle, CH32_REG_DEBUG_DATA0, value); + rvswd_write(handle, CH32_REG_DEBUG_COMMAND, command); + return true; +} + +bool ch32_read_cpu_reg(rvswd_handle_t* handle, uint16_t regno, uint32_t* value_out) { + uint32_t command = regno // Register to access. + | (0 << 16) // Read access. + | (1 << 17) // Perform transfer. + | (2 << 20) // 32-bit register access. + | (0 << 24); // Access register command. + + rvswd_write(handle, CH32_REG_DEBUG_COMMAND, command); + rvswd_read(handle, CH32_REG_DEBUG_DATA0, value_out); + return true; +} + +bool ch32_run_debug_code(rvswd_handle_t* handle, void const* code, size_t code_size) { + if (code_size > 8 * 4) { + ESP_LOGE(TAG, "Debug program is too long (%zd/%zd)", code_size, (size_t)8 * 4); + return false; + } else if (code_size & 1) { + ESP_LOGE(TAG, "Debug program size must be a multiple of 2 (%zd)", code_size); + return false; + } + + // Copy into program buffer. + uint32_t tmp[8] = {0}; + memcpy(tmp, code, code_size); + for (size_t i = 0; i < 8; i++) { + rvswd_write(handle, CH32_REG_DEBUG_PROGBUF0 + i, tmp[i]); + } + + // Run program buffer. + uint32_t command = (0 << 17) // Do not perform transfer. + | (1 << 18) // Run program buffer afterwards. + | (2 << 20) // 32-bit register access. + | (0 << 24); // Access register command. + rvswd_write(handle, CH32_REG_DEBUG_COMMAND, command); + + return true; +} + +bool ch32_read_memory_word(rvswd_handle_t* handle, uint32_t address, uint32_t* value_out) { + ch32_write_cpu_reg(handle, CH32_REGS_GPR + 11, address); + ch32_run_debug_code(handle, ch32_readmem, sizeof(ch32_readmem)); + ch32_read_cpu_reg(handle, CH32_REGS_GPR + 10, value_out); + return true; +} + +bool ch32_write_memory_word(rvswd_handle_t* handle, uint32_t address, uint32_t value) { + ch32_write_cpu_reg(handle, CH32_REGS_GPR + 10, value); + ch32_write_cpu_reg(handle, CH32_REGS_GPR + 11, address); + ch32_run_debug_code(handle, ch32_writemem, sizeof(ch32_writemem)); + return true; +} + +// Wait for the FLASH chip to finish its current operation. +static bool ch32_wait_flash(rvswd_handle_t* handle) { + uint32_t value = 0; + ch32_read_memory_word(handle, CH32_FLASH_STATR, &value); + + uint32_t timeout = 1000; + + while (value & CH32_FLASH_STATR_BUSY) { + ESP_LOGD(TAG, "Flash busy: FLASH_STATR = 0x%08" PRIx32 "\r\n", value); + vTaskDelay(1); + ch32_read_memory_word(handle, CH32_FLASH_STATR, &value); + timeout--; + if (timeout == 0) { + return false; + } + } + return true; +} + +static void ch32_wait_flash_write(rvswd_handle_t* handle) { + uint32_t value = 0; + ch32_read_memory_word(handle, CH32_FLASH_STATR, &value); + while (value & CH32_FLASH_STATR_WRBUSY) { + ch32_read_memory_word(handle, CH32_FLASH_STATR, &value); + } +} + +// Unlock the FLASH if not already unlocked. +bool ch32_unlock_flash(rvswd_handle_t* handle) { + uint32_t ctlr; + ch32_read_memory_word(handle, CH32_FLASH_CTLR, &ctlr); + + // Enter the unlock keys. + ch32_write_memory_word(handle, 0x40022004, 0x45670123); + ch32_write_memory_word(handle, 0x40022004, 0xCDEF89AB); + ch32_write_memory_word(handle, 0x40022008, 0x45670123); + ch32_write_memory_word(handle, 0x40022008, 0xCDEF89AB); + ch32_write_memory_word(handle, 0x40022024, 0x45670123); + ch32_write_memory_word(handle, 0x40022024, 0xCDEF89AB); + + // Check again if FLASH is unlocked. + ch32_read_memory_word(handle, CH32_FLASH_CTLR, &ctlr); + + return ((ctlr & CH32_FLASH_CTLR_LOCK) != CH32_FLASH_CTLR_LOCK); +} + +// Lock the FLASH +bool ch32_lock_flash(rvswd_handle_t* handle) { + uint32_t ctlr; + + // Check if FLASH is locked + ch32_read_memory_word(handle, CH32_FLASH_CTLR, &ctlr); + + if ((ctlr & CH32_FLASH_CTLR_LOCK) == CH32_FLASH_CTLR_LOCK) { + ESP_LOGW(TAG, "Target flash already locked"); + return true; + } + + // Lock FLASH + ch32_write_memory_word(handle, CH32_FLASH_CTLR, ctlr | CH32_FLASH_CTLR_LOCK); + + // Check again if FLASH is locked + ch32_read_memory_word(handle, CH32_FLASH_CTLR, &ctlr); + + return ((ctlr & CH32_FLASH_CTLR_LOCK) == CH32_FLASH_CTLR_LOCK); +} + +// If unlocked: Erase a 256-byte block of FLASH. +bool ch32_erase_flash_block(rvswd_handle_t* handle, uint32_t addr) { + if (addr % 256) return false; + bool wait_res = ch32_wait_flash(handle); + if (!wait_res) { + return false; + } + ch32_write_memory_word(handle, CH32_FLASH_CTLR, CH32_FLASH_CTLR_FTER); + ch32_write_memory_word(handle, CH32_FLASH_ADDR, addr); + ch32_write_memory_word(handle, CH32_FLASH_CTLR, CH32_FLASH_CTLR_FTER | CH32_FLASH_CTLR_STRT); + wait_res = ch32_wait_flash(handle); + if (!wait_res) { + return false; + } + ch32_write_memory_word(handle, CH32_FLASH_CTLR, 0); + return true; +} + +// If unlocked: Write a 256-byte block of FLASH. +bool ch32_write_flash_block(rvswd_handle_t* handle, uint32_t addr, void const* data) { + if (addr % 256) return false; + + bool wait_res = ch32_wait_flash(handle); + if (!wait_res) { + return false; + } + ch32_write_memory_word(handle, CH32_FLASH_CTLR, CH32_FLASH_CTLR_FTPG); + + ch32_write_memory_word(handle, CH32_FLASH_ADDR, addr); + + uint32_t wdata[64]; + memcpy(wdata, data, sizeof(wdata)); + for (size_t i = 0; i < 64; i++) { + ch32_write_memory_word(handle, addr + i * 4, wdata[i]); + ch32_wait_flash_write(handle); + } + + ch32_write_memory_word(handle, CH32_FLASH_CTLR, CH32_FLASH_CTLR_FTPG | CH32_FLASH_CTLR_PGSTRT); + wait_res = ch32_wait_flash(handle); + if (!wait_res) { + return false; + } + ch32_write_memory_word(handle, CH32_FLASH_CTLR, 0); + vTaskDelay(1); + + uint32_t rdata[64]; + for (size_t i = 0; i < 64; i++) { + vTaskDelay(0); + ch32_read_memory_word(handle, addr + i * 4, &rdata[i]); + } + if (memcmp(wdata, rdata, sizeof(wdata))) { + ESP_LOGE(TAG, "Write block mismatch at %08" PRIx32, addr); + ESP_LOGE(TAG, "Write:"); + for (size_t i = 0; i < 64; i++) { + ESP_LOGE(TAG, "%zx: %08" PRIx32, i, wdata[i]); + } + ESP_LOGE(TAG, "Read:"); + for (size_t i = 0; i < 64; i++) { + ESP_LOGE(TAG, "%zx: %08" PRIx32, i, rdata[i]); + } + return false; + } + + return true; +} + +// If unlocked: Erase and write a range of FLASH memory. +bool ch32_write_flash(rvswd_handle_t* handle, uint32_t addr, void const* _data, size_t data_len) { + if (addr % 64) { + return false; + } + + uint8_t const* data = _data; + + char buffer[32]; + + for (size_t i = 0; i < data_len; i += 256) { + vTaskDelay(0); + snprintf(buffer, sizeof(buffer) - 1, "Writing at 0x%08" PRIx32, addr + i); + ch32_status_callback(buffer, i, data_len); + + if (!ch32_erase_flash_block(handle, addr + i)) { + ESP_LOGE(TAG, "Error: Failed to erase FLASH at %08" PRIx32, addr + i); + return false; + } + + if (!ch32_write_flash_block(handle, addr + i, data + i)) { + ESP_LOGE(TAG, "Error: Failed to write FLASH at %08" PRIx32, addr + i); + return false; + } + } + + return true; +} + +bool ch32_clear_running_operations(rvswd_handle_t* handle) { + uint32_t timeout = 100; + while (1) { + uint32_t value = 0; + ch32_read_memory_word(handle, CH32_FLASH_STATR, &value); + if (value & CH32_FLASH_STATR_BUSY) { + if (value & CH32_FLASH_STATR_EOP) { + ESP_LOGD(TAG, "Clearing EOP flag...\r\n"); + ch32_write_memory_word(handle, CH32_FLASH_STATR, value | CH32_FLASH_STATR_EOP); + } else if (value & CH32_FLASH_STATR_WRPRTERR) { + ESP_LOGD(TAG, "Clearing WRPRTERR flag...\r\n"); + ch32_write_memory_word(handle, CH32_FLASH_STATR, value | CH32_FLASH_STATR_WRPRTERR); + } else if (value & CH32_FLASH_STATR_WRBUSY) { + ESP_LOGD(TAG, "Waiting for busy flag to clear...\r\n"); + timeout--; + if (timeout == 0) { + ESP_LOGE(TAG, "Timeout while waiting for target to clear busy flag!\r\n"); + return false; + } + } else { + uint32_t ctlr_value = 0; + ch32_read_memory_word(handle, CH32_FLASH_CTLR, &ctlr_value); + ESP_LOGE(TAG, + "Target busy for unknown reason (FLASH_STATR: 0x%08" PRIx32 ", FLASH_CTLR: 0x%08" PRIx32 + ")!\r\n", + value, ctlr_value); + return false; + } + vTaskDelay(pdMS_TO_TICKS(10)); + } else { + return true; + } + } +} + +// Program and restart the CH32V203. +bool ch32_program(rvswd_handle_t* handle, void const* firmware, size_t firmware_len) { + rvswd_result_t res; + + res = rvswd_init(handle); + + if (res != RVSWD_OK) { + ESP_LOGE(TAG, "RVSWD initialization error %u!", res); + return false; + } + + res = rvswd_reset(handle); + + if (res != RVSWD_OK) { + ESP_LOGE(TAG, "RVSWD reset error %u!", res); + return false; + } + + res = ch32_reset_microprocessor_and_run(handle); + if (res != RVSWD_OK) { + ESP_LOGE(TAG, "Failed to reset target"); + return false; + } + + res = ch32_halt_microprocessor(handle); + if (res != RVSWD_OK) { + ESP_LOGE(TAG, "Failed to halt target"); + return false; + } + + bool bool_res = ch32_unlock_flash(handle); + + if (!bool_res) { + ESP_LOGE(TAG, "Failed to unlock flash"); + return false; + } + + bool_res = ch32_clear_running_operations(handle); + if (!bool_res) { + ESP_LOGE(TAG, "Failed to clear target running operations"); + return false; + }; + + bool_res = ch32_write_flash(handle, 0x08000000, firmware, firmware_len); + if (!bool_res) { + ESP_LOGE(TAG, "Failed to write target flash"); + return false; + }; + + bool_res = ch32_lock_flash(handle); + if (!bool_res) { + ESP_LOGE(TAG, "Failed to lock target flash"); + return false; + }; + + res = ch32_reset_microprocessor_and_run(handle); + if (res != RVSWD_OK) { + ESP_LOGE(TAG, "Failed to reset target to run firmware"); + return false; + } + + return true; +} + +// Default status callback implementation. +void __attribute__((weak)) ch32_status_callback(char const* msg, int progress, int total) { + ESP_LOGI(TAG, "%s: %d%% (%d/%d)", msg, progress * 100 / total, progress, total); +}