diff --git a/meson_options.txt b/meson_options.txt index 612c2f53ec9..f94d06db885 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -30,7 +30,9 @@ option( 'cortexm', 'riscv32', 'riscv64', + 'avr', 'apollo3', + 'atxmega', 'at32f4', 'ch32', 'ch579', diff --git a/src/target/atxmega.c b/src/target/atxmega.c new file mode 100644 index 00000000000..9b2bc109311 --- /dev/null +++ b/src/target/atxmega.c @@ -0,0 +1,528 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2022 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 "gdb_reg.h" +#include "exception.h" +#include "avr_pdi.h" + +#include + +#define IDCODE_XMEGA64A3U 0x9642U +#define IDCODE_XMEGA128A3U 0x9742U +#define IDCODE_XMEGA192A3U 0x9744U +#define IDCODE_XMEGA256A3U 0x9842U + +#define ATXMEGA_DBG_BASE 0x00000000U +#define ATXMEGA_DBG_CTR (ATXMEGA_DBG_BASE + 0x0U) +#define ATXMEGA_DBG_PC (ATXMEGA_DBG_BASE + 0x4U) +#define ATXMEGA_DBG_CTRL (ATXMEGA_DBG_BASE + 0xaU) +#define ATXMEGA_DBG_SPECIAL (ATXMEGA_DBG_BASE + 0xcU) + +#define AVR_DBG_READ_REGS 0x11U +#define AVR_NUM_REGS 32 + +#define ATXMEGA_BRK_BASE 0x00000020U +#define ATXMEGA_BRK_COUNTER 0x00000028U +#define ATXMEGA_BRK_UNKNOWN1 0x00000040U +#define ATXMEGA_BRK_UNKNOWN2 0x00000046U +#define ATXMEGA_BRK_UNKNOWN3 0x00000048U + +#define ATXMEGA_CPU_BASE 0x01000030U +/* Address of the low byte of the stack pointer */ +#define ATXMEGA_CPU_SPL (ATXMEGA_CPU_BASE + 0xdU) +/* This is followed by the high byte and SREG */ + +#define ATXMEGA_NVM_BASE 0x010001c0U +#define ATXMEGA_NVM_DATA (ATXMEGA_NVM_BASE + 0x4U) +#define ATXMEGA_NVM_CMD (ATXMEGA_NVM_BASE + 0xaU) +#define ATXMEGA_NVM_STATUS (ATXMEGA_NVM_BASE + 0xfU) + +#define ATXMEGA_NVM_CMD_NOP 0x00U +#define ATXMEGA_NVM_CMD_ERASE_FLASH_BUFFER 0x26U +#define ATXMEGA_NVM_CMD_WRITE_FLASH_BUFFER 0x23U +#define ATXMEGA_NVM_CMD_ERASE_FLASH_PAGE 0x2bU +#define ATXMEGA_NVM_CMD_WRITE_FLASH_PAGE 0x2eU +#define ATXMEGA_NVM_CMD_READ_NVM 0x43U + +#define ATXMEGA_NVM_STATUS_BUSY 0x80U +#define ATXMEGA_NVM_STATUS_FBUSY 0x40U + +/* Special-purpose register name strings */ +static const char *const avr_spr_names[] = { + "sreg", + "sp", + "pc", +}; + +/* Special-purpose register types */ +static const gdb_reg_type_e avr_spr_types[] = { + GDB_TYPE_UNSPECIFIED, /* sreg */ + GDB_TYPE_DATA_PTR, /* sp */ + GDB_TYPE_CODE_PTR, /* pc */ +}; + +/* Special-purpose register bitsizes */ +static const uint8_t avr_spr_bitsizes[] = { + 8, /* sreg */ + 16, /* sp */ + 32, /* pc */ +}; + +// clang-format off +static_assert(ARRAY_LENGTH(avr_spr_types) == ARRAY_LENGTH(avr_spr_names), + "SPR array length mismatch! SPR type array should have the same length as SPR name array." +); + +static_assert(ARRAY_LENGTH(avr_spr_bitsizes) == ARRAY_LENGTH(avr_spr_names), + "SPR array length mismatch! SPR bitsize array should have the same length as SPR name array." +); +// clang-format on + +static bool atxmega_flash_erase(target_flash_s *flash, target_addr_t addr, size_t len); +static bool atxmega_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len); +static bool atxmega_flash_done(target_flash_s *flash); + +static const char *atxmega_target_description(target_s *target); + +static bool atxmega_check_error(target_s *target); +static void atxmega_halt_resume(target_s *target, bool step); + +static void atxmega_regs_read(target_s *target, void *data); +static void atxmega_mem_read(target_s *target, void *dest, target_addr64_t src, size_t len); + +static bool atxmega_ensure_nvm_idle(const avr_pdi_s *pdi); +static bool atxmega_config_breakpoints(const avr_pdi_s *pdi, bool step); + +void avr_add_flash(target_s *const target, const uint32_t start, const size_t length, const uint16_t block_size) +{ + target_flash_s *flash = calloc(1, sizeof(*flash)); + if (!flash) { /* calloc failed: heap exhaustion */ + DEBUG_WARN("calloc: failed in %s\n", __func__); + return; + } + + flash->start = start; + flash->length = length; + flash->blocksize = block_size; + flash->erase = atxmega_flash_erase; + flash->write = atxmega_flash_write; + flash->done = atxmega_flash_done; + flash->erased = 0xffU; + target_add_flash(target, flash); +} + +bool atxmega_probe(target_s *const target) +{ + uint32_t application_flash = 0; + uint32_t application_table_flash = 0; + uint32_t bootloader_flash = 0; + uint16_t flash_block_size = 0; + uint32_t sram = 0; + + switch (target->part_id) { + case IDCODE_XMEGA64A3U: + /* + * The 64A3U has: + * 60kiB of normal Flash + * 4kiB of application table Flash + * 4kiB of bootloader Flash + * 16kiB of internal SRAM + */ + application_flash = 0xf000U; + application_table_flash = 0x1000U; + bootloader_flash = 0x1000U; + flash_block_size = 128; + target->core = "ATXMega64A3U"; + break; + case IDCODE_XMEGA128A3U: + /* + * The 128A3U has: + * 120kiB of normal Flash + * 8kiB of application table Flash + * 8kiB of bootloader Flash + * 16kiB of internal SRAM + */ + application_flash = 0x1e000U; + application_table_flash = 0x2000U; + bootloader_flash = 0x2000U; + flash_block_size = 256; + target->core = "ATXMega128A3U"; + break; + case IDCODE_XMEGA192A3U: + /* + * The 192A3U has: + * 184kiB of normal Flash + * 8kiB of application table Flash + * 8kiB of bootloader Flash + * 16kiB of internal SRAM + */ + application_flash = 0x2e000U; + application_table_flash = 0x2000U; + bootloader_flash = 0x2000U; + flash_block_size = 256; + target->core = "ATXMega192A3U"; + break; + case IDCODE_XMEGA256A3U: + /* + * The 256A3U has: + * 248kiB of normal Flash + * 8kiB of application table Flash + * 8kiB of bootloader Flash + * 16kiB of internal SRAM + */ + application_flash = 0x3e000U; + application_table_flash = 0x2000U; + bootloader_flash = 0x2000U; + flash_block_size = 256; + sram = 0x800U; + target->core = "ATXMega256A3U"; + break; + default: + return false; + } + + target->regs_description = atxmega_target_description; + target->check_error = atxmega_check_error; + target->halt_resume = atxmega_halt_resume; + + target->regs_read = atxmega_regs_read; + target->mem_read = atxmega_mem_read; + + /* + * RAM is actually at 0x01002000 in the 24-bit linearised PDI address space however, because GDB/GCC, + * internally we have to map at 0x00800000 to get a suitable mapping for the host + */ + target_add_ram32(target, 0x00802000U, sram); + uint32_t flash_base_address = 0x00000000; + avr_add_flash(target, flash_base_address, application_flash, flash_block_size); + flash_base_address += application_flash; + avr_add_flash(target, flash_base_address, application_table_flash, flash_block_size); + flash_base_address += application_table_flash; + avr_add_flash(target, flash_base_address, bootloader_flash, flash_block_size); + + avr_pdi_s *const pdi = avr_pdi_struct(target); + pdi->ensure_nvm_idle = atxmega_ensure_nvm_idle; + + /* This is unfortunately hard-coded as we don't currently have a way to "learn" this from the target. */ + pdi->breakpoints_available = 2; + return true; +} + +static bool atxmega_ensure_nvm_idle(const avr_pdi_s *const pdi) +{ + return avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_NVM_CMD, ATXMEGA_NVM_CMD_NOP) && + avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_NVM_DATA, 0xffU); +} + +static bool atxmega_flash_erase(target_flash_s *const flash, const target_addr_t addr, const size_t len) +{ + const avr_pdi_s *const pdi = avr_pdi_struct(flash->t); + for (size_t i = 0; i < len; i += flash->blocksize) { + if (!avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_NVM_CMD, ATXMEGA_NVM_CMD_ERASE_FLASH_PAGE) || + !avr_pdi_write(pdi, PDI_DATA_8, (addr + i) | PDI_FLASH_OFFSET, 0x55U)) + return false; + + uint8_t status = 0; + while (avr_pdi_read8(pdi, ATXMEGA_NVM_STATUS, &status) && + (status & (ATXMEGA_NVM_STATUS_BUSY | ATXMEGA_NVM_STATUS_FBUSY)) == + (ATXMEGA_NVM_STATUS_BUSY | ATXMEGA_NVM_STATUS_FBUSY)) + continue; + + /* Check if the read status failed */ + if (status & (ATXMEGA_NVM_STATUS_BUSY | ATXMEGA_NVM_STATUS_FBUSY)) + return false; + } + return true; +} + +static bool atxmega_flash_write( + target_flash_s *const flash, target_addr_t dest, const void *const src, const size_t len) +{ + const avr_pdi_s *const pdi = avr_pdi_struct(flash->t); + const uint8_t *const buffer = (const uint8_t *)src; + for (size_t i = 0; i < len; i += flash->blocksize) { + const size_t amount = MIN(flash->blocksize, len - i); + const uint32_t addr = (dest + i) | PDI_FLASH_OFFSET; + if (!avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_NVM_CMD, ATXMEGA_NVM_CMD_WRITE_FLASH_BUFFER) || + !avr_pdi_write_ind(pdi, addr, PDI_MODE_IND_INCPTR, buffer + i, amount) || + !avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_NVM_CMD, ATXMEGA_NVM_CMD_WRITE_FLASH_PAGE) || + !avr_pdi_write(pdi, PDI_DATA_8, addr, 0xffU)) + return false; + + uint8_t status = 0; + while (avr_pdi_read8(pdi, ATXMEGA_NVM_STATUS, &status) && + (status & (ATXMEGA_NVM_STATUS_BUSY | ATXMEGA_NVM_STATUS_FBUSY)) == + (ATXMEGA_NVM_STATUS_BUSY | ATXMEGA_NVM_STATUS_FBUSY)) + continue; + + /* Check if the read status failed */ + if (status & (ATXMEGA_NVM_STATUS_BUSY | ATXMEGA_NVM_STATUS_FBUSY)) + return false; + } + return true; +} + +static bool atxmega_flash_done(target_flash_s *const flash) +{ + const avr_pdi_s *const pdi = avr_pdi_struct(flash->t); + return atxmega_ensure_nvm_idle(pdi); +} + +/* + * This function creates the target description XML string for an ATXMega6 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 equivilent to. + * + * This string it creates is the XML-equivalent to the following: + * "" + * "" + * "" + * " avr:106" + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * " " + * "" + */ +static size_t atxmega_build_target_description(char *buffer, size_t max_len) +{ + size_t print_size = max_len; + /* Start with the "preamble" chunks, which are mostly common across targets save for 2 words. */ + int offset = snprintf(buffer, print_size, "%s target %savr:106%s ", + gdb_xml_preamble_first, gdb_xml_preamble_second, gdb_xml_preamble_third); + + /* Then build the general purpose register descriptions which have names r0 through r31 and the same bitsize */ + for (uint8_t i = 0; i < 32; ++i) { + if (max_len != 0) + print_size = max_len - (size_t)offset; + offset += snprintf( + buffer + offset, print_size, "", i, i == 0 ? " regnum=\"0\"" : ""); + } + + /* Then finally build the special-purpose register descriptions using the arrays at top of file. */ + for (size_t i = 0; i < ARRAY_LENGTH(avr_spr_names); ++i) { + if (max_len != 0) + print_size = max_len - (size_t)offset; + + const char *const name = avr_spr_names[i]; + const uint8_t bitsize = avr_spr_bitsizes[i]; + const gdb_reg_type_e type = avr_spr_types[i]; + + offset += snprintf(buffer + offset, print_size, "", name, bitsize, + gdb_reg_type_strings[type]); + } + + /* Add the closing tags required */ + if (max_len != 0) + print_size = max_len - (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 *atxmega_target_description(target_s *const target) +{ + (void)target; + const size_t description_length = atxmega_build_target_description(NULL, 0) + 1U; + char *const description = malloc(description_length); + if (description) + atxmega_build_target_description(description, description_length); + return description; +} + +static bool atxmega_check_error(target_s *const target) +{ + const avr_pdi_s *const pdi = avr_pdi_struct(target); + return pdi->error_state != pdi_ok; +} + +static void atxmega_mem_read(target_s *const target, void *const dest, const target_addr64_t src, const size_t len) +{ + avr_pdi_s *const pdi = avr_pdi_struct(target); + const target_addr32_t translated_src = (target_addr32_t)src + PDI_FLASH_OFFSET; + if (target_flash_for_addr(target, src)) { + if (!avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_NVM_CMD, ATXMEGA_NVM_CMD_READ_NVM) || + !avr_pdi_read_ind(pdi, translated_src, PDI_MODE_IND_INCPTR, dest, len) || !atxmega_ensure_nvm_idle(pdi)) + pdi->error_state = pdi_failure; + } else if (translated_src < PDI_FLASH_OFFSET) { + if (!avr_pdi_read_ind(pdi, translated_src, PDI_MODE_IND_INCPTR, dest, len)) + pdi->error_state = pdi_failure; + } +} + +static void atxmega_regs_read(target_s *const target, void *const data) +{ + avr_pdi_s *const pdi = avr_pdi_struct(target); + avr_regs_s *regs = (avr_regs_s *)data; + uint8_t status[3]; + uint32_t program_counter = 0; + if (!avr_pdi_read32(pdi, ATXMEGA_DBG_PC, &program_counter) || + !avr_pdi_read_ind(pdi, ATXMEGA_CPU_SPL, PDI_MODE_IND_INCPTR, status, 3) || + !avr_pdi_write(pdi, PDI_DATA_32, ATXMEGA_DBG_PC, 0) || + !avr_pdi_write(pdi, PDI_DATA_32, ATXMEGA_DBG_CTR, AVR_NUM_REGS) || + !avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_DBG_CTRL, AVR_DBG_READ_REGS) || + !avr_pdi_reg_write(pdi, PDI_REG_R4, 1) || + !avr_pdi_read_ind(pdi, ATXMEGA_DBG_SPECIAL, PDI_MODE_IND_PTR, regs->general, 32) || + avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x04U) + raise_exception(EXCEPTION_ERROR, "Error reading registers"); + /* Store the newly read program counter */ + pdi->program_counter = program_counter - 1U; + /* + * These aren't in the reads above because regs is a packed struct, which results in compiler errors. + * Additionally, the program counter is stored in words and points to the next instruction to be executed + * so we have to adjust it by 1 and make it bytes. + */ + regs->pc = pdi->program_counter << 1U; + regs->sp = status[0] | ((uint16_t)status[1] << 8); + regs->sreg = status[2]; +} + +static bool atxmega_config_breakpoints(const avr_pdi_s *const pdi, const bool step) +{ + uint8_t breakpoint_count = 0U; + if (step) { + /* If we are single stepping, clear all enabled breakpoints */ + for (uint8_t idx = 0; idx < pdi->breakpoints_available; ++idx) { + if (!avr_pdi_write(pdi, PDI_DATA_32, ATXMEGA_BRK_BASE + (idx * 4U), 0U)) + return false; + } + } else { + /* We are not single stepping, so configure the breakpoints as defined in the PDI structure */ + for (uint8_t idx = 0; idx < pdi->breakpoints_available; ++idx) { + const uint32_t breakpoint = pdi->breakpoints[idx]; + /* If the breakpoint is enabled, increment breakpoint_count */ + if (breakpoint & AVR_BREAKPOINT_ENABLED) + ++breakpoint_count; + /* Try to write the address of the breakpoint */ + /* XXX: Need to first collect all the breakpoints on the stack, then write all of them used first */ + if (!avr_pdi_write(pdi, PDI_DATA_32, ATXMEGA_BRK_BASE + (idx * 4U), breakpoint & AVR_BREAKPOINT_MASK)) + return false; + } + } + /* Tell the breakpoint unit how many breakpoints are enabled */ + return avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_BRK_UNKNOWN1, 0) && + avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_BRK_UNKNOWN2, 0) && + avr_pdi_write(pdi, PDI_DATA_16, ATXMEGA_BRK_COUNTER, (uint16_t)breakpoint_count << 8U) && + avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_BRK_UNKNOWN3, 0); +} + +static void atxmega_halt_resume(target_s *const target, const bool step) +{ + avr_pdi_s *const pdi = avr_pdi_struct(target); + if (step) { + const uint32_t current_pc = pdi->program_counter; + const uint32_t next_pc = current_pc + 1U; + /* + * To do a single step, we run the following steps: + * Write the debug control register to 4, which puts the processor in a temporary breakpoint mode + * Write the debug counter register with the address to stop execution on + * Write the program counter with the address to resume execution on + */ + /* Check that we are in administrative halt */ + if (avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x04U || + /* Try to configure (clear) the breakpoints */ + !atxmega_config_breakpoints(pdi, step) || avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x04U || + /* Configure the debug controller */ + !avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_DBG_CTRL, 4U) || + !avr_pdi_write(pdi, PDI_DATA_32, ATXMEGA_DBG_CTR, next_pc) || + !avr_pdi_write(pdi, PDI_DATA_32, ATXMEGA_DBG_PC, current_pc) || + avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x04U || + /* And try to execute the request*/ + !avr_pdi_reg_write(pdi, PDI_REG_R4, 1U)) + raise_exception(EXCEPTION_ERROR, "Error stepping device, device in incorrect state"); + /* Then spin waiting to see the processor stop back in administrative halt */ + while (avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x04U) + continue; + pdi->halt_reason = TARGET_HALT_STEPPING; + } else { + /* + * To resume the processor we go through the following specific steps: + * Write the program counter to ensure we start where we expect + * Then we release the externally (PDI) applied reset + * We then poke the debug control register to indicate debug-supervised run + * Ensure that PDI is still in debug mode (r4 = 1) + * Read r3 to see that the processor is resuming + */ + /* Check that we are in administrative halt */ + if (avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x04U || + /* Try to configure the breakpoints */ + !atxmega_config_breakpoints(pdi, step) || avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x04U || + /* Set the program counter and release reset */ + !avr_pdi_write(pdi, PDI_DATA_32, ATXMEGA_DBG_PC, pdi->program_counter) || + !avr_pdi_reg_write(pdi, PDI_REG_RESET, 0U) || + /* Configure the debug controller */ + !avr_pdi_write(pdi, PDI_DATA_8, ATXMEGA_DBG_CTRL, 0U) || avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x04U || + /* And try to execute the request */ + !avr_pdi_reg_write(pdi, PDI_REG_R4, 1U)) + raise_exception(EXCEPTION_ERROR, "Error resuming device, device in incorrect state"); + pdi->halt_reason = TARGET_HALT_RUNNING; + } +} diff --git a/src/target/avr_pdi.c b/src/target/avr_pdi.c new file mode 100644 index 00000000000..3c60275f472 --- /dev/null +++ b/src/target/avr_pdi.c @@ -0,0 +1,522 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2022 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 "jtag_scan.h" +#include "jtagtap.h" +#include "avr_pdi.h" +#include "gdb_packet.h" +#include "exception.h" + +#define IR_PDI 0x7U +#define IR_BYPASS 0xfU + +#define PDI_BREAK 0xbbU +#define PDI_DELAY 0xdbU +#define PDI_EMPTY 0xebU + +#define PDI_LDS 0x00U +#define PDI_LD 0x20U +#define PDI_STS 0x40U +#define PDI_ST 0x60U +#define PDI_LDCS 0x80U +#define PDI_REPEAT 0xa0U +#define PDI_STCS 0xc0U +#define PDI_KEY 0xe0U + +#define PDI_ADDR_8 0x00U +#define PDI_ADDR_16 0x04U +#define PDI_ADDR_24 0x08U +#define PDI_ADDR_32 0x0cU + +#define PDI_MODE_MASK 0xf3U + +#define PDI_RESET 0x59U + +typedef enum pdi_key { + PDI_NVM = 0x02U, + PDI_DEBUG = 0x04U, +} pdi_key_e; + +static const uint8_t pdi_key_nvm[] = {0xff, 0x88, 0xd8, 0xcd, 0x45, 0xab, 0x89, 0x12}; +static const uint8_t pdi_key_debug[] = {0x21, 0x81, 0x7c, 0x9f, 0xd4, 0x2d, 0x21, 0x3a}; + +static bool avr_pdi_init(avr_pdi_s *pdi); +static bool avr_attach(target_s *target); +static void avr_detach(target_s *target); + +static void avr_reset(target_s *target); +static void avr_halt_request(target_s *target); +static target_halt_reason_e avr_halt_poll(target_s *target, target_addr_t *watch); + +static int avr_breakwatch_set(target_s *target, breakwatch_s *breakwatch); +static int avr_breakwatch_clear(target_s *target, breakwatch_s *breakwatch); + +void avr_jtag_pdi_handler(const uint8_t dev_index) +{ + avr_pdi_s *pdi = calloc(1, sizeof(*pdi)); + if (!pdi) { /* calloc failed: heap exhaustion */ + DEBUG_WARN("calloc: failed in %s\n", __func__); + return; + } + + /* Setup and try to initialise the PDI controller */ + pdi->dev_index = dev_index; + pdi->idcode = jtag_devs[dev_index].jd_idcode; + /* If we failed, free the structure */ + if (!avr_pdi_init(pdi)) + free(pdi); + /* Reset the JTAG machinary back to bypass to scan the next device in the chain */ + jtag_dev_write_ir(dev_index, IR_BYPASS); +} + +static bool avr_pdi_init(avr_pdi_s *const pdi) +{ + /* Check for a valid part number in the JTAG ID code */ + if ((pdi->idcode & 0x0ffff000U) == 0) { + DEBUG_WARN("Invalid PDI idcode %08" PRIx32 "\n", pdi->idcode); + return false; + } + DEBUG_INFO("AVR ID 0x%08" PRIx32 " (v%u)\n", pdi->idcode, (uint8_t)((pdi->idcode >> 28U) & 0xfU)); + /* Transition the part into PDI mode */ + jtag_dev_write_ir(pdi->dev_index, IR_PDI); + + /* Allocate a new target */ + target_s *target = target_new(); + if (!target) + return false; + + /* Do preliminary setup of the target structure for an AVR target */ + target->cpuid = pdi->idcode; + target->part_id = (pdi->idcode >> 12U) & 0xffffU; + target->driver = "Atmel AVR"; + target->core = "AVR"; + target->priv = pdi; + target->priv_free = free; + + target->attach = avr_attach; + target->detach = avr_detach; + + target->halt_request = avr_halt_request; + target->halt_poll = avr_halt_poll; + target->reset = avr_reset; + /* + * Unlike on an ARM processor, where this is the length of a table, here + * we return the size of a sutable registers structure. + */ + target->regs_size = sizeof(avr_regs_s); + + target->breakwatch_set = avr_breakwatch_set; + target->breakwatch_clear = avr_breakwatch_clear; + + /* Try probing for various known AVR parts */ + PROBE(atxmega_probe); + +#if PC_HOSTED == 0 + gdb_outf("Please report unknown AVR device with Part ID 0x%x\n", target->part_id); +#else + DEBUG_WARN("Please report unknown AVR device with Part ID 0x%x\n", target->part_id); +#endif + return true; +} + +avr_pdi_s *avr_pdi_struct(target_s *const target) +{ + return (avr_pdi_s *)target->priv; +} + +/* + * This is a PDI-specific DR manipulation function that handles PDI_DELAY responses + * transparently to the caller. It also does parity validation, returning true for + * valid parity. + */ +static bool avr_jtag_shift_dr(uint8_t dev_index, uint8_t *data_out, const uint8_t data_in) +{ + jtag_dev_s *dev = &jtag_devs[dev_index]; + uint8_t result = 0; + uint16_t request = 0; + uint16_t response = 0; + uint8_t *data; + if (!data_out) + return false; + /* Try to perform a PDI access, with retry when the reply is a delay packet */ + do { + data = (uint8_t *)&request; + /* Begin the data register transaction, handling any devices in bypass before us in the chain */ + jtagtap_shift_dr(); + jtag_proc.jtagtap_tdi_seq(false, ones, dev->dr_prescan); + /* Build the PDI packet to send */ + data[0] = data_in; + /* Calculate the parity bit of the request */ + for (uint8_t i = 0; i < 8; ++i) + data[1] ^= (data_in >> i) & 1U; + /* Then send it and handle any devices in bypass after us in the chain */ + jtag_proc.jtagtap_tdi_tdo_seq((uint8_t *)&response, !dev->dr_postscan, (uint8_t *)&request, 9); + jtag_proc.jtagtap_tdi_seq(true, ones, dev->dr_postscan); + /* Then return the JTAG bus to idle */ + jtagtap_return_idle(0); + data = (uint8_t *)&response; + } while (data[0] == PDI_DELAY && data[1] == 1); + /* Calculate the parity bit from the reply */ + for (uint8_t i = 0; i < 8; ++i) + result ^= (data[0] >> i) & 1U; + *data_out = data[0]; + DEBUG_TARGET("Sent 0x%02x to target, response was 0x%02x (0x%x)\n", data_in, data[0], data[1]); + /* Return if the calculated parity matches the received parity */ + return result == data[1]; +} + +bool avr_pdi_reg_write(const avr_pdi_s *const pdi, const uint8_t reg, const uint8_t value) +{ + uint8_t result = 0; + const uint8_t command = PDI_STCS | reg; + if (reg >= 16 || avr_jtag_shift_dr(pdi->dev_index, &result, command) || result != PDI_EMPTY || + avr_jtag_shift_dr(pdi->dev_index, &result, value)) + return false; + return result == PDI_EMPTY; +} + +uint8_t avr_pdi_reg_read(const avr_pdi_s *const pdi, const uint8_t reg) +{ + uint8_t result = 0; + const uint8_t command = PDI_LDCS | reg; + if (reg >= 16 || avr_jtag_shift_dr(pdi->dev_index, &result, command) || result != PDI_EMPTY || + !avr_jtag_shift_dr(pdi->dev_index, &result, 0)) + return 0xffU; // TODO - figure out a better way to indicate failure. + return result; +} + +bool avr_pdi_write(const avr_pdi_s *const pdi, const uint8_t bytes, const uint32_t reg, const uint32_t value) +{ + uint8_t result = 0; + uint8_t command = PDI_STS | PDI_ADDR_32 | bytes; + uint8_t data_bytes[4] = { + value & 0xffU, + (value >> 8U) & 0xffU, + (value >> 16U) & 0xffU, + (value >> 24U) & 0xffU, + }; + + if (avr_jtag_shift_dr(pdi->dev_index, &result, command) || result != PDI_EMPTY || + avr_jtag_shift_dr(pdi->dev_index, &result, reg & 0xffU) || result != PDI_EMPTY || + avr_jtag_shift_dr(pdi->dev_index, &result, (reg >> 8U) & 0xffU) || result != PDI_EMPTY || + avr_jtag_shift_dr(pdi->dev_index, &result, (reg >> 16U) & 0xffU) || result != PDI_EMPTY || + avr_jtag_shift_dr(pdi->dev_index, &result, (reg >> 24U) & 0xffU) || result != PDI_EMPTY) + return false; + // This is intentionally <= to avoid `bytes + 1` silliness + for (uint8_t i = 0; i <= bytes; ++i) { + if (avr_jtag_shift_dr(pdi->dev_index, &result, data_bytes[i]) || result != PDI_EMPTY) + return false; + } + return true; +} + +static bool avr_pdi_read(const avr_pdi_s *const pdi, const uint8_t bytes, const uint32_t reg, uint32_t *const value) +{ + uint8_t result = 0; + uint8_t command = PDI_LDS | PDI_ADDR_32 | bytes; + uint8_t data_bytes[4]; + uint32_t data = 0xffffffffU; + if (avr_jtag_shift_dr(pdi->dev_index, &result, command) || result != PDI_EMPTY || + avr_jtag_shift_dr(pdi->dev_index, &result, reg & 0xffU) || result != PDI_EMPTY || + avr_jtag_shift_dr(pdi->dev_index, &result, (reg >> 8U) & 0xffU) || result != PDI_EMPTY || + avr_jtag_shift_dr(pdi->dev_index, &result, (reg >> 16U) & 0xffU) || result != PDI_EMPTY || + avr_jtag_shift_dr(pdi->dev_index, &result, (reg >> 24U) & 0xffU) || result != PDI_EMPTY) + return false; + for (uint8_t i = 0; i <= bytes; ++i) { + if (!avr_jtag_shift_dr(pdi->dev_index, &data_bytes[i], 0)) + return false; + } + data = data_bytes[0]; + if (bytes > PDI_DATA_8) + data |= (uint32_t)data_bytes[1] << 8U; + if (bytes > PDI_DATA_16) + data |= (uint32_t)data_bytes[2] << 16U; + if (bytes > PDI_DATA_24) + data |= (uint32_t)data_bytes[3] << 24U; + *value = data; + return true; +} + +bool avr_pdi_read8(const avr_pdi_s *const pdi, const uint32_t reg, uint8_t *const value) +{ + uint32_t data; + const bool result = avr_pdi_read(pdi, PDI_DATA_8, reg, &data); + if (result) + *value = data; + return result; +} + +bool avr_pdi_read16(const avr_pdi_s *const pdi, const uint32_t reg, uint16_t *const value) +{ + uint32_t data; + const bool result = avr_pdi_read(pdi, PDI_DATA_16, reg, &data); + if (result) + *value = data; + return result; +} + +bool avr_pdi_read24(const avr_pdi_s *const pdi, const uint32_t reg, uint32_t *const value) +{ + return avr_pdi_read(pdi, PDI_DATA_24, reg, value); +} + +bool avr_pdi_read32(const avr_pdi_s *const pdi, const uint32_t reg, uint32_t *const value) +{ + return avr_pdi_read(pdi, PDI_DATA_32, reg, value); +} + +// Runs `st ptr ` +static bool avr_pdi_write_ptr(const avr_pdi_s *const pdi, const uint32_t addr) +{ + const uint8_t command = PDI_ST | PDI_MODE_DIR_PTR | PDI_DATA_32; + uint8_t result = 0; + return !avr_jtag_shift_dr(pdi->dev_index, &result, command) && result == PDI_EMPTY && + !avr_jtag_shift_dr(pdi->dev_index, &result, addr & 0xffU) && result == PDI_EMPTY && + !avr_jtag_shift_dr(pdi->dev_index, &result, (addr >> 8U) & 0xffU) && result == PDI_EMPTY && + !avr_jtag_shift_dr(pdi->dev_index, &result, (addr >> 16U) & 0xffU) && result == PDI_EMPTY && + !avr_jtag_shift_dr(pdi->dev_index, &result, (addr >> 24U) & 0xffU) && result == PDI_EMPTY; +} + +// Runs `repeat ` +static bool avr_pdi_repeat(const avr_pdi_s *const pdi, const uint32_t count) +{ + const uint32_t repeat = count - 1U; + const uint8_t command = PDI_REPEAT | PDI_DATA_32; + uint8_t result = 0; + return !avr_jtag_shift_dr(pdi->dev_index, &result, command) && result == PDI_EMPTY && + !avr_jtag_shift_dr(pdi->dev_index, &result, repeat & 0xffU) && result == PDI_EMPTY && + !avr_jtag_shift_dr(pdi->dev_index, &result, (repeat >> 8U) & 0xffU) && result == PDI_EMPTY && + !avr_jtag_shift_dr(pdi->dev_index, &result, (repeat >> 16U) & 0xffU) && result == PDI_EMPTY && + !avr_jtag_shift_dr(pdi->dev_index, &result, (repeat >> 24U) & 0xffU) && result == PDI_EMPTY; +} + +bool avr_pdi_write_ind(const avr_pdi_s *const pdi, const uint32_t addr, const uint8_t ptr_mode, const void *const src, + const uint32_t count) +{ + const uint8_t command = PDI_ST | ptr_mode; + uint8_t result = 0; + const uint8_t *const data = (const uint8_t *)src; + if ((ptr_mode & PDI_MODE_MASK) || !count || !avr_pdi_write_ptr(pdi, addr) || !avr_pdi_repeat(pdi, count)) + return false; + // Run `st ` + if (avr_jtag_shift_dr(pdi->dev_index, &result, command) || result != PDI_EMPTY) + return false; + for (uint32_t i = 0; i < count; ++i) { + if (avr_jtag_shift_dr(pdi->dev_index, &result, data[i]) || result != PDI_EMPTY) + return false; + } + return true; +} + +bool avr_pdi_read_ind( + const avr_pdi_s *const pdi, const uint32_t addr, const uint8_t ptr_mode, void *const dst, const uint32_t count) +{ + const uint8_t command = PDI_LD | ptr_mode; + uint8_t result = 0; + uint8_t *const data = (uint8_t *)dst; + if ((ptr_mode & PDI_MODE_MASK) || !count || !avr_pdi_write_ptr(pdi, addr) || !avr_pdi_repeat(pdi, count)) + return false; + // Run `ld ` + if (avr_jtag_shift_dr(pdi->dev_index, &result, command) || result != PDI_EMPTY) + return false; + for (uint32_t i = 0; i < count; ++i) { + if (!avr_jtag_shift_dr(pdi->dev_index, data + i, 0)) + return false; + } + return true; +} + +/* + * Enable a PDI feature based on the `what` argument. + * This sends a PDI `KEY` instruction with the appropriate enablement key and + * returns if the PDI controller did actually enable the requested feature. + */ +static bool avr_enable(const avr_pdi_s *const pdi, const pdi_key_e what) +{ + const uint8_t *const key = what == PDI_DEBUG ? pdi_key_debug : pdi_key_nvm; + uint8_t result = 0; + if (avr_jtag_shift_dr(pdi->dev_index, &result, PDI_KEY) || result != PDI_EMPTY) + return false; + for (uint8_t i = 0; i < 8; ++i) { + if (avr_jtag_shift_dr(pdi->dev_index, &result, key[i]) || result != PDI_EMPTY) + return false; + } + return (avr_pdi_reg_read(pdi, PDI_REG_STATUS) & what) == what; +} + +/* Disables the requested PDI feature based on the `what` argument */ +static bool avr_disable(const avr_pdi_s *const pdi, const pdi_key_e what) +{ + return avr_pdi_reg_write(pdi, PDI_REG_STATUS, ~what); +} + +static bool avr_ensure_nvm_idle(const avr_pdi_s *const pdi) +{ + if (pdi->ensure_nvm_idle) + return pdi->ensure_nvm_idle(pdi); + return true; +} + +static bool avr_attach(target_s *const target) +{ + const avr_pdi_s *const pdi = avr_pdi_struct(target); + /* Put the target back in PDI comms mode */ + jtag_dev_write_ir(pdi->dev_index, IR_PDI); + + TRY (EXCEPTION_ALL) { + /* Attempt to reset the target */ + target_reset(target); + /* Then enable the debug unit in the PDI controller */ + if (!avr_enable(pdi, PDI_DEBUG)) + return false; + /* Ask the PDI controller to halt the processor */ + target_halt_request(target); + /* + * And now finish by enabling the NVM unit in the PDI controller and + * check that the processor is in administrative halt + */ + if (!avr_enable(pdi, PDI_NVM) || !avr_ensure_nvm_idle(pdi) || avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x04U) + return false; + } + CATCH () { + default: + return !exception_frame.type; + } + return true; +} + +static void avr_detach(target_s *const target) +{ + const avr_pdi_s *const pdi = avr_pdi_struct(target); + + /* Disable all enabled PDI controller units to detach from the target */ + avr_disable(pdi, PDI_NVM); + avr_disable(pdi, PDI_DEBUG); + jtag_dev_write_ir(pdi->dev_index, IR_BYPASS); +} + +static void avr_reset(target_s *const target) +{ + avr_pdi_s *const pdi = avr_pdi_struct(target); + /* + * We only actually want to do this if the target is not presently attached as + * this resets the NVM and debug enables + */ + if (target->attached) + return; + /* Write the reset register to initiate reset */ + if (!avr_pdi_reg_write(pdi, PDI_REG_RESET, PDI_RESET)) + raise_exception(EXCEPTION_ERROR, "Error resetting device, device in incorrect state"); + /* If any PDI controller units are presently enabled, disable them */ + if (avr_pdi_reg_read(pdi, PDI_REG_STATUS) != 0x00) { + avr_disable(pdi, PDI_NVM); + avr_disable(pdi, PDI_DEBUG); + } +} + +static void avr_halt_request(target_s *const target) +{ + avr_pdi_s *const pdi = avr_pdi_struct(target); + /* + * To halt the processor we go through a few really specific steps: + * Write r4 to 1 to indicate we want to put the processor into debug-based pause + * Read r3 and check it's 0x10 which indicates the processor is held in reset and no debugging is active + * Release reset + * Read r3 twice more, the first time should respond 0x14 to indicate the processor is still reset + * but that debug pause is requested, and the second should respond 0x04 to indicate the processor is now + * in debug pause state (halted) + */ + if (!avr_pdi_reg_write(pdi, PDI_REG_R4, 1) || avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x10U || + !avr_pdi_reg_write(pdi, PDI_REG_RESET, 0) || avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x14U || + avr_pdi_reg_read(pdi, PDI_REG_R3) != 0x04U) + raise_exception(EXCEPTION_ERROR, "Error halting device, device in incorrect state"); + pdi->halt_reason = TARGET_HALT_REQUEST; +} + +static target_halt_reason_e avr_halt_poll(target_s *const target, target_addr_t *const watch) +{ + avr_pdi_s *const pdi = avr_pdi_struct(target); + (void)watch; + + /* If we're running but the processor stops because it's hit a breakpoint, update */ + if (pdi->halt_reason == TARGET_HALT_RUNNING && avr_pdi_reg_read(pdi, PDI_REG_R3) == 0x04U) + pdi->halt_reason = TARGET_HALT_BREAKPOINT; + return pdi->halt_reason; +} + +/* + * 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 avr_breakwatch_set(target_s *const target, breakwatch_s *const breakwatch) +{ + avr_pdi_s *const pdi = avr_pdi_struct(target); + switch (breakwatch->type) { + case TARGET_BREAK_HARD: { + /* First try and find a unused breakpoint slot */ + size_t breakpoint = 0; + for (; breakpoint < pdi->breakpoints_available; ++breakpoint) { + /* Check if the slot is presently in use, breaking if it is not */ + if (!(pdi->breakpoints[breakpoint] & AVR_BREAKPOINT_ENABLED)) + break; + } + /* If none was available, return an error */ + if (breakpoint == pdi->breakpoints_available) + return -1; + + /* Store the address to set the breakpoint, and store the index of the slot used in the breakwatch structure */ + pdi->breakpoints[breakpoint] = AVR_BREAKPOINT_ENABLED | (breakwatch->addr & AVR_BREAKPOINT_MASK); + breakwatch->reserved[0] = breakpoint; + /* Tell the debugger that it was successfully able to "set" the breakpoint */ + return 0; + } + default: + /* If the breakwatch type is not one of the above, tell the debugger we don't support it */ + return 1; + } +} + +static int avr_breakwatch_clear(target_s *const target, breakwatch_s *const breakwatch) +{ + avr_pdi_s *const pdi = avr_pdi_struct(target); + /* Clear the breakpoint slot this used */ + pdi->breakpoints[breakwatch->reserved[0]] = 0U; + /* Tell the debugger that it was successfully able to "clear" the breakpoint */ + return 0; +} diff --git a/src/target/avr_pdi.h b/src/target/avr_pdi.h new file mode 100644 index 00000000000..578d108fdee --- /dev/null +++ b/src/target/avr_pdi.h @@ -0,0 +1,106 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2022 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_AVR_PDI_H +#define TARGET_AVR_PDI_H + +#include +#include "target.h" + +typedef enum avr_error { + pdi_ok, + pdi_failure, +} avr_error_e; + +#define AVR_MAX_BREAKPOINTS 2U +#define AVR_BREAKPOINT_ENABLED 0x80000000U +#define AVR_BREAKPOINT_MASK 0x00ffffffU + +typedef struct avr_pdi avr_pdi_s; + +struct avr_pdi { + uint32_t idcode; + + uint8_t dev_index; + target_halt_reason_e halt_reason; + avr_error_e error_state; + uint32_t program_counter; + + /* Storage slots for the current breakpoints configuration */ + uint32_t breakpoints[AVR_MAX_BREAKPOINTS]; + /* A count of the total available breakpoints on the target */ + uint8_t breakpoints_available; + + bool (*ensure_nvm_idle)(const avr_pdi_s *pdi); +}; + +typedef struct __attribute__((packed)) avr_regs { + uint8_t general[32]; /* r0-r31 */ + uint8_t sreg; /* r32 */ + uint16_t sp; /* r33 */ + uint32_t pc; /* r34 */ +} avr_regs_s; + +#define PDI_DATA_8 0x00U +#define PDI_DATA_16 0x01U +#define PDI_DATA_24 0x02U +#define PDI_DATA_32 0x03U + +#define PDI_REG_STATUS 0U +#define PDI_REG_RESET 1U +#define PDI_REG_CTRL 2U +#define PDI_REG_R3 3U +#define PDI_REG_R4 4U + +#define PDI_MODE_IND_PTR 0x00U +#define PDI_MODE_IND_INCPTR 0x04U +#define PDI_MODE_DIR_PTR 0x08U +#define PDI_MODE_DIR_INCPTR 0x0cU /* "Reserved" */ + +#define PDI_FLASH_OFFSET 0x00800000U + +void avr_jtag_pdi_handler(uint8_t dev_index); +avr_pdi_s *avr_pdi_struct(target_s *target); + +bool avr_pdi_reg_write(const avr_pdi_s *pdi, uint8_t reg, uint8_t value); +uint8_t avr_pdi_reg_read(const avr_pdi_s *pdi, uint8_t reg); + +bool avr_pdi_write(const avr_pdi_s *pdi, uint8_t bytes, uint32_t reg, uint32_t value); +bool avr_pdi_read8(const avr_pdi_s *pdi, uint32_t reg, uint8_t *value); +bool avr_pdi_read16(const avr_pdi_s *pdi, uint32_t reg, uint16_t *value); +bool avr_pdi_read24(const avr_pdi_s *pdi, uint32_t reg, uint32_t *value); +bool avr_pdi_read32(const avr_pdi_s *pdi, uint32_t reg, uint32_t *value); +bool avr_pdi_write_ind(const avr_pdi_s *pdi, uint32_t addr, uint8_t ptr_mode, const void *src, uint32_t count); +bool avr_pdi_read_ind(const avr_pdi_s *pdi, uint32_t addr, uint8_t ptr_mode, void *dst, uint32_t count); + +#endif /*TARGET_AVR_PDI_H*/ diff --git a/src/target/jtag_devs.c b/src/target/jtag_devs.c index ecd6eca761a..ac71ebf3300 100644 --- a/src/target/jtag_devs.c +++ b/src/target/jtag_devs.c @@ -23,6 +23,7 @@ #include "general.h" #include "jtag_scan.h" #include "adiv5.h" +#include "avr_pdi.h" #include "riscv_debug.h" #include "jtag_devs.h" @@ -423,6 +424,16 @@ const jtag_dev_descr_s dev_descr[] = { #endif }, #endif +#ifdef ENABLE_AVR + { + .idcode = 0x0000003fU, + .idmask = 0x00000fffU, +#if ENABLE_DEBUG == 1 + .descr = "AVR JTAG-PDI port.", +#endif + .handler = avr_jtag_pdi_handler, + }, +#endif #if ENABLE_DEBUG == 1 { .idcode = 0x000007a3U, diff --git a/src/target/meson.build b/src/target/meson.build index 4e11cb99ef0..64eb3f16ec2 100644 --- a/src/target/meson.build +++ b/src/target/meson.build @@ -75,7 +75,9 @@ if is_firmware_build 'cortexm': 'Cortex-M support', 'riscv32': 'RISC-V 32-bit support', 'riscv64': 'RISC-V 64-bit support', + 'avr': '8-bit AVR support', 'at32f4': 'Arterytek parts', + 'atxmega': 'ATXMega support', 'apollo3': 'Ambiq Apollo3 parts', 'ch32': 'WinChipHead CH32 parts', 'ch579': 'WinChipHead CH579 parts', @@ -162,11 +164,21 @@ target_riscv64 = declare_dependency( dependencies: target_riscv, ) +target_avr = declare_dependency( + sources: files('avr_pdi.c'), + compile_args: ['-DENABLE_AVR=1'], +) + target_apollo3 = declare_dependency( sources: files('apollo3.c'), dependencies: target_cortexm, ) +target_atxmega = declare_dependency( + sources: files('atxmega.c'), + dependencies: target_avr, +) + target_ch579 = declare_dependency( sources: files('ch579.c'), dependencies: target_cortexm, @@ -378,8 +390,10 @@ libbmd_target_deps = [ target_cortexm, target_riscv32, target_riscv64, + target_avr, # Enable all targets for libbmd target_apollo3, + target_atxmega, target_at32f4, target_ch32, target_ch579, diff --git a/src/target/target_probe.c b/src/target/target_probe.c index 2fee2a324e1..c5fbc723674 100644 --- a/src/target/target_probe.c +++ b/src/target/target_probe.c @@ -156,4 +156,6 @@ TARGET_PROBE_WEAK_NOP(zynq7_probe) LPC55_DP_PREPARE_WEAK_NOP(lpc55_dp_prepare) +TARGET_PROBE_WEAK_NOP(atxmega_probe) + #endif /* _WIN32 */ diff --git a/src/target/target_probe.h b/src/target/target_probe.h index a68693dea8c..d3f7da2452e 100644 --- a/src/target/target_probe.h +++ b/src/target/target_probe.h @@ -107,4 +107,6 @@ bool zynq7_probe(target_s *target); void lpc55_dp_prepare(adiv5_debug_port_s *dp); +bool atxmega_probe(target_s *t); + #endif /* TARGET_TARGET_PROBE_H */