diff options
author | Tim Newsome <tim@sifive.com> | 2018-06-11 14:38:00 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-06-11 14:38:00 -0700 |
commit | f098b553c928d38409fb1823c1d47b1853c5062a (patch) | |
tree | 9969e4575f44f9f9f0cd3b64e33a2679dc730ed4 | |
parent | 6766fa1dda84e5b9243a2ab60bca77de92ea178d (diff) | |
parent | 17a0523736e25b352f82d62e1b16bd1c2d87646b (diff) | |
download | riscv-openocd-f098b553c928d38409fb1823c1d47b1853c5062a.zip riscv-openocd-f098b553c928d38409fb1823c1d47b1853c5062a.tar.gz riscv-openocd-f098b553c928d38409fb1823c1d47b1853c5062a.tar.bz2 |
Merge pull request #264 from riscv/from_upstream
From upstream
41 files changed, 6568 insertions, 823 deletions
@@ -125,8 +125,8 @@ Flash drivers ADUC702x, AT91SAM, ATH79, AVR, CFI, DSP5680xx, EFM32, EM357, FM3, FM4, Kinetis, LPC8xx/LPC1xxx/LPC2xxx/LPC541xx, LPC2900, LPCSPIFI, Marvell QSPI, -Milandr, NIIET, NuMicro, PIC32mx, PSoC4, SiM3x, Stellaris, STM32, STMSMI, -STR7x, STR9x, nRF51; NAND controllers of AT91SAM9, LPC3180, LPC32xx, +Milandr, NIIET, NuMicro, PIC32mx, PSoC4, PSoC5LP, SiM3x, Stellaris, STM32, +STMSMI, STR7x, STR9x, nRF51; NAND controllers of AT91SAM9, LPC3180, LPC32xx, i.MX31, MXC, NUC910, Orion/Kirkwood, S3C24xx, S3C6400, XMC1xxx, XMC4xxx. diff --git a/configure.ac b/configure.ac index 11f8042..d4338df 100644 --- a/configure.ac +++ b/configure.ac @@ -115,7 +115,8 @@ m4_define([USB1_ADAPTERS], [[ulink], [Keil ULINK JTAG Programmer], [ULINK]], [[usb_blaster_2], [Altera USB-Blaster II Compatible], [USB_BLASTER_2]], [[ft232r], [Bitbang mode of FT232R based devices], [FT232R]], - [[vsllink], [Versaloon-Link JTAG Programmer], [VSLLINK]]]) + [[vsllink], [Versaloon-Link JTAG Programmer], [VSLLINK]], + [[xds110], [TI XDS110 Debug Probe], [XDS110]]]) m4_define([USB_ADAPTERS], [[[osbdm], [OSBDM (JTAG only) Programmer], [OSBDM]], diff --git a/contrib/60-openocd.rules b/contrib/60-openocd.rules index da760f8..af092c1 100644 --- a/contrib/60-openocd.rules +++ b/contrib/60-openocd.rules @@ -128,6 +128,12 @@ ATTRS{idVendor}=="1781", ATTRS{idProduct}=="0c63", MODE="660", GROUP="plugdev", # TI/Luminary Stellaris In-Circuit Debug Interface (ICDI) Board ATTRS{idVendor}=="1cbe", ATTRS{idProduct}=="00fd", MODE="660", GROUP="plugdev", TAG+="uaccess" +# TI XDS110 Debug Probe (Launchpads and Standalone) +ATTRS{idVendor}=="0451", ATTRS{idProduct}=="bef3", MODE="660", GROUP="plugdev", TAG+="uaccess" + +# TI Tiva-based ICDI and XDS110 probes in DFU mode +ATTRS{idVendor}=="1cbe", ATTRS{idProduct}=="00ff", MODE="660", GROUP="plugdev", TAG+="uaccess" + # Ambiq Micro EVK and Debug boards. ATTRS{idVendor}=="2aec", ATTRS{idProduct}=="6010", MODE="664", GROUP="plugdev", TAG+="uaccess" ATTRS{idVendor}=="2aec", ATTRS{idProduct}=="6011", MODE="664", GROUP="plugdev", TAG+="uaccess" diff --git a/contrib/loaders/flash/cc3220sf/Makefile b/contrib/loaders/flash/cc3220sf/Makefile new file mode 100644 index 0000000..d1dcc25 --- /dev/null +++ b/contrib/loaders/flash/cc3220sf/Makefile @@ -0,0 +1,19 @@ +BIN2C = ../../../../src/helper/bin2char.sh + +CROSS_COMPILE ?= arm-none-eabi- +AS = $(CROSS_COMPILE)as +OBJCOPY = $(CROSS_COMPILE)objcopy + +all: cc3220sf.inc + +%.elf: %.s + $(AS) $< -o $@ + +%.bin: %.elf + $(OBJCOPY) -Obinary $< $@ + +%.inc: %.bin + $(BIN2C) < $< > $@ + +clean: + -rm -f *.elf *.bin *.inc diff --git a/contrib/loaders/flash/cc3220sf/cc3220sf.inc b/contrib/loaders/flash/cc3220sf/cc3220sf.inc new file mode 100644 index 0000000..29c54c6 --- /dev/null +++ b/contrib/loaders/flash/cc3220sf/cc3220sf.inc @@ -0,0 +1,10 @@ +/* Autogenerated with ../../../../src/helper/bin2char.sh */ +0xdf,0xf8,0x7c,0xa0,0xdf,0xf8,0x7c,0xb0,0xdf,0xf8,0x7c,0xc0,0x01,0xf0,0x7f,0x03, +0x00,0x2b,0x1e,0xd1,0x4f,0xf0,0x00,0x04,0xcc,0xf8,0x00,0x10,0x03,0x68,0xcb,0xf8, +0x00,0x30,0x0b,0xf1,0x04,0x0b,0x00,0xf1,0x04,0x00,0xa2,0xf1,0x01,0x02,0x04,0xf1, +0x01,0x04,0x01,0xf1,0x04,0x01,0x00,0x2a,0x01,0xd0,0x20,0x2c,0xee,0xd1,0xcc,0xf8, +0x20,0xa0,0xdc,0xf8,0x20,0x30,0x13,0xf0,0x01,0x0f,0xfa,0xd1,0x00,0x2a,0xd7,0xd1, +0x13,0xe0,0xcc,0xf8,0x00,0x10,0x03,0x68,0xcc,0xf8,0x04,0x30,0xcc,0xf8,0x08,0xa0, +0xdc,0xf8,0x08,0x30,0x13,0xf0,0x01,0x0f,0xfa,0xd1,0xa2,0xf1,0x01,0x02,0x00,0xf1, +0x04,0x00,0x01,0xf1,0x04,0x01,0x00,0x2a,0xc2,0xd1,0x00,0xbe,0x01,0xbe,0xfc,0xe7, +0x01,0x00,0x42,0xa4,0x00,0xd1,0x0f,0x40,0x00,0xd0,0x0f,0x40, diff --git a/contrib/loaders/flash/cc3220sf/cc3220sf.s b/contrib/loaders/flash/cc3220sf/cc3220sf.s new file mode 100644 index 0000000..cffcfa0 --- /dev/null +++ b/contrib/loaders/flash/cc3220sf/cc3220sf.s @@ -0,0 +1,93 @@ +/*************************************************************************** + * Copyright (C) 2017 by Texas Instruments, Inc. * + * * + * 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 2 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 <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + + /* Params: + * r0 = buffer start address (in) + * r1 = flash destination address (in) + * r2 = number of words to write (in/out) + */ + + .text + .cpu cortex-m4 + .code 16 + .thumb + .syntax unified + + .align 2 + + /* r3 = scratchpad + * r4 = buffer word counter + * r10 = flash programming key + * r11 = base FWB address + * r12 = base flash regs address + */ + +start: + ldr r10, =0xa4420001 /* flash programming key */ + ldr r11, =0x400fd100 /* base of FWB */ + ldr r12, =0x400fd000 /* base of flash regs */ + and r3, r1, #0x7f /* is the dest address 32 word aligned? */ + cmp r3, #0 + bne program_word /* if not aligned do one word at a time */ + + /* program using the write buffers */ +program_buffer: + mov r4, #0 /* start the buffer word counter at 0 */ + str r1, [r12] /* store the dest addr in FMA */ +fill_buffer: + ldr r3, [r0] /* get the word to write to FWB */ + str r3, [r11] /* store the word in the FWB */ + add r11, r11, #4 /* increment the FWB pointer */ + add r0, r0, #4 /* increment the source pointer */ + sub r2, r2, #1 /* decrement the total word counter */ + add r4, r4, #1 /* increment the buffer word counter */ + add r1, r1, #4 /* increment the dest pointer */ + cmp r2, #0 /* is the total word counter now 0? */ + beq buffer_ready /* go to end if total word counter is 0 */ + cmp r4, #32 /* is the buffer word counter now 32? */ + bne fill_buffer /* go to continue to fill buffer */ +buffer_ready: + str r10, [r12, #0x20] /* store the key and write bit to FMC2 */ +wait_buffer_done: + ldr r3, [r12, #0x20] /* read FMC2 */ + tst r3, #1 /* see if the write bit is cleared */ + bne wait_buffer_done /* go to read FMC2 if bit not cleared */ + cmp r2, #0 /* is the total word counter now 0? */ + bne start /* go if there is more to program */ + b exit + + /* program just one word */ +program_word: + str r1, [r12] /* store the dest addr in FMA */ + ldr r3, [r0] /* get the word to write to FMD */ + str r3, [r12, #0x4] /* store the word in FMD */ + str r10, [r12, #0x8] /* store the key and write bit to FMC */ +wait_word_done: + ldr r3, [r12, #0x8] /* read FMC */ + tst r3, #1 /* see if the write bit is cleared */ + bne wait_word_done /* go to read FMC if bit not cleared */ + sub r2, r2, #1 /* decrement the total word counter */ + add r0, r0, #4 /* increment the source pointer */ + add r1, r1, #4 /* increment the dest pointer */ + cmp r2, #0 /* is the total word counter now 0 */ + bne start /* go if there is more to program */ + + /* end */ +exit: + bkpt #0 + bkpt #1 + b exit diff --git a/doc/openocd.texi b/doc/openocd.texi index 3944572..0122224 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -531,6 +531,12 @@ debuggers to ARM Cortex based targets @url{http://www.keil.com/support/man/docs/ @item @b{Keil ULINK v1} @* Link: @url{http://www.keil.com/ulink1/} + +@item @b{TI XDS110 Debug Probe} +@* The XDS110 is included as the embedded debug probe on many Texas Instruments +LaunchPad evaluation boards. +@* Link: @url{http://processors.wiki.ti.com/index.php/XDS110} +@* Link: @url{http://processors.wiki.ti.com/index.php/XDS_Emulation_Software_Package#XDS110_Support_Utilities} @end itemize @section IBM PC Parallel Printer Port Based @@ -5572,6 +5578,20 @@ Triggering a mass erase is also useful when users want to disable readout protec @end deffn +@deffn {Flash Driver} cc3220sf +The CC3220SF version of the SimpleLink CC32xx microcontrollers from Texas +Instruments includes 1MB of internal flash. The cc3220sf flash driver only +supports the internal flash. The serial flash on SimpleLink boards is +programmed via the bootloader over a UART connection. Security features of +the CC3220SF may erase the internal flash during power on reset. Refer to +documentation at @url{www.ti.com/cc3220sf} for details on security features +and programming the serial flash. + +@example +flash bank $_FLASHNAME cc3220sf 0 0 0 0 $_TARGETNAME +@end example +@end deffn + @deffn {Flash Driver} efm32 All members of the EFM32 microcontroller family from Energy Micro include internal flash and use ARM Cortex-M3 cores. The driver automatically recognizes @@ -6122,6 +6142,68 @@ The @var{num} parameter is a value shown by @command{flash banks}. @end deffn @end deffn +@deffn {Flash Driver} psoc5lp +All members of the PSoC 5LP microcontroller family from Cypress +include internal program flash and use ARM Cortex-M3 cores. +The driver probes for a number of these chips and autoconfigures itself, +apart from the base address. + +@example +flash bank $_FLASHNAME psoc5lp 0x00000000 0 0 0 $_TARGETNAME +@end example + +@b{Note:} PSoC 5LP chips can be configured to have ECC enabled or disabled. +@quotation Attention +If flash operations are performed in ECC-disabled mode, they will also affect +the ECC flash region. Erasing a 16k flash sector in the 0x00000000 area will +then also erase the corresponding 2k data bytes in the 0x48000000 area. +Writing to the ECC data bytes in ECC-disabled mode is not implemented. +@end quotation + +Commands defined in the @var{psoc5lp} driver: + +@deffn Command {psoc5lp mass_erase} +Erases all flash data and ECC/configuration bytes, all flash protection rows, +and all row latches in all flash arrays on the device. +@end deffn +@end deffn + +@deffn {Flash Driver} psoc5lp_eeprom +All members of the PSoC 5LP microcontroller family from Cypress +include internal EEPROM and use ARM Cortex-M3 cores. +The driver probes for a number of these chips and autoconfigures itself, +apart from the base address. + +@example +flash bank $_CHIPNAME.eeprom psoc5lp_eeprom 0x40008000 0 0 0 $_TARGETNAME +@end example +@end deffn + +@deffn {Flash Driver} psoc5lp_nvl +All members of the PSoC 5LP microcontroller family from Cypress +include internal Nonvolatile Latches and use ARM Cortex-M3 cores. +The driver probes for a number of these chips and autoconfigures itself. + +@example +flash bank $_CHIPNAME.nvl psoc5lp_nvl 0 0 0 0 $_TARGETNAME +@end example + +PSoC 5LP chips have multiple NV Latches: + +@itemize +@item Device Configuration NV Latch - 4 bytes +@item Write Once (WO) NV Latch - 4 bytes +@end itemize + +@b{Note:} This driver only implements the Device Configuration NVL. + +The @var{psoc5lp} driver reads the ECC mode from Device Configuration NVL. +@quotation Attention +Switching ECC mode via write to Device Configuration NVL will require a reset +after successful write. +@end quotation +@end deffn + @deffn {Flash Driver} psoc6 Supports PSoC6 (CY8C6xxx) family of Cypress microcontrollers. PSoC6 is a dual-core device with CM0+ and CM4 cores. Both cores share @@ -8086,6 +8168,30 @@ interacting with remote files or displaying console messages in the debugger. @end deffn +@deffn Command {arm semihosting_resexit} [@option{enable}|@option{disable}] +@cindex ARM semihosting +Enable resumable SEMIHOSTING_SYS_EXIT. + +When SEMIHOSTING_SYS_EXIT is called outside a debug session, +things are simple, the openocd process calls exit() and passes +the value returned by the target. + +When SEMIHOSTING_SYS_EXIT is called during a debug session, +by default execution returns to the debugger, leaving the +debugger in a HALT state, similar to the state entered when +encountering a break. + +In some use cases, it is useful to have SEMIHOSTING_SYS_EXIT +return normally, as any semihosting call, and do not break +to the debugger. +The standard allows this to happen, but the condition +to trigger it is a bit obscure ("by performing an RDI_Execute +request or equivalent"). + +To make the SEMIHOSTING_SYS_EXIT call return normally, enable +this option (default: disabled). +@end deffn + @section ARMv4 and ARMv5 Architecture @cindex ARMv4 @cindex ARMv5 diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index 7121412..db936b8 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -19,6 +19,7 @@ NOR_DRIVERS = \ %D%/atsamv.c \ %D%/avrf.c \ %D%/bluenrg-x.c \ + %D%/cc3220sf.c \ %D%/cfi.c \ %D%/dsp5680xx_flash.c \ %D%/efm32.c \ @@ -43,6 +44,7 @@ NOR_DRIVERS = \ %D%/ocl.c \ %D%/pic32mx.c \ %D%/psoc4.c \ + %D%/psoc5lp.c \ %D%/psoc6.c \ %D%/sim3x.c \ %D%/spi.c \ @@ -64,6 +66,7 @@ NOR_DRIVERS = \ NORHEADERS = \ %D%/core.h \ + %D%/cc3220sf.h \ %D%/cfi.h \ %D%/driver.h \ %D%/imp.h \ diff --git a/src/flash/nor/cc3220sf.c b/src/flash/nor/cc3220sf.c new file mode 100644 index 0000000..af45743 --- /dev/null +++ b/src/flash/nor/cc3220sf.c @@ -0,0 +1,529 @@ +/*************************************************************************** + * Copyright (C) 2017 by Texas Instruments, Inc. * + * * + * 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 2 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 <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imp.h" +#include "cc3220sf.h" +#include <helper/time_support.h> +#include <target/algorithm.h> +#include <target/armv7m.h> + +#define FLASH_TIMEOUT 5000 + +struct cc3220sf_bank { + bool probed; + struct armv7m_algorithm armv7m_info; +}; + +static int cc3220sf_mass_erase(struct flash_bank *bank) +{ + struct target *target = bank->target; + bool done; + long long start_ms; + long long elapsed_ms; + uint32_t value; + + int retval = ERROR_OK; + + if (TARGET_HALTED != target->state) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + /* Set starting address to erase to zero */ + retval = target_write_u32(target, FMA_REGISTER_ADDR, 0); + if (ERROR_OK != retval) + return retval; + + /* Write the MERASE bit of the FMC register */ + retval = target_write_u32(target, FMC_REGISTER_ADDR, FMC_MERASE_VALUE); + if (ERROR_OK != retval) + return retval; + + /* Poll the MERASE bit until the mass erase is complete */ + done = false; + start_ms = timeval_ms(); + while (!done) { + retval = target_read_u32(target, FMC_REGISTER_ADDR, &value); + if (ERROR_OK != retval) + return retval; + + if ((value & FMC_MERASE_BIT) == 0) { + /* Bit clears when mass erase is finished */ + done = true; + } else { + elapsed_ms = timeval_ms() - start_ms; + if (elapsed_ms > 500) + keep_alive(); + if (elapsed_ms > FLASH_TIMEOUT) + break; + } + } + + if (!done) { + /* Mass erase timed out waiting for confirmation */ + return ERROR_FAIL; + } + + return retval; +} + +FLASH_BANK_COMMAND_HANDLER(cc3220sf_flash_bank_command) +{ + struct cc3220sf_bank *cc3220sf_bank; + + if (CMD_ARGC < 6) + return ERROR_COMMAND_SYNTAX_ERROR; + + cc3220sf_bank = malloc(sizeof(struct cc3220sf_bank)); + if (NULL == cc3220sf_bank) + return ERROR_FAIL; + + /* Initialize private flash information */ + cc3220sf_bank->probed = false; + + /* Finish initialization of flash bank */ + bank->driver_priv = cc3220sf_bank; + bank->next = NULL; + + return ERROR_OK; +} + +static int cc3220sf_erase(struct flash_bank *bank, int first, int last) +{ + struct target *target = bank->target; + bool done; + long long start_ms; + long long elapsed_ms; + uint32_t address; + uint32_t value; + + int retval = ERROR_OK; + + if (TARGET_HALTED != target->state) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + /* Do a mass erase if user requested all sectors of flash */ + if ((first == 0) && (last == (bank->num_sectors - 1))) { + /* Request mass erase of flash */ + return cc3220sf_mass_erase(bank); + } + + /* Erase requested sectors one by one */ + for (int i = first; i <= last; i++) { + + /* Determine address of sector to erase */ + address = FLASH_BASE_ADDR + i * FLASH_SECTOR_SIZE; + + /* Set starting address to erase */ + retval = target_write_u32(target, FMA_REGISTER_ADDR, address); + if (ERROR_OK != retval) + return retval; + + /* Write the ERASE bit of the FMC register */ + retval = target_write_u32(target, FMC_REGISTER_ADDR, FMC_ERASE_VALUE); + if (ERROR_OK != retval) + return retval; + + /* Poll the ERASE bit until the erase is complete */ + done = false; + start_ms = timeval_ms(); + while (!done) { + retval = target_read_u32(target, FMC_REGISTER_ADDR, &value); + if (ERROR_OK != retval) + return retval; + + if ((value & FMC_ERASE_BIT) == 0) { + /* Bit clears when mass erase is finished */ + done = true; + } else { + elapsed_ms = timeval_ms() - start_ms; + if (elapsed_ms > 500) + keep_alive(); + if (elapsed_ms > FLASH_TIMEOUT) + break; + } + } + + if (!done) { + /* Sector erase timed out waiting for confirmation */ + return ERROR_FAIL; + } + } + + return retval; +} + +static int cc3220sf_protect(struct flash_bank *bank, int set, int first, + int last) +{ + return ERROR_OK; +} + +static int cc3220sf_write(struct flash_bank *bank, const uint8_t *buffer, + uint32_t offset, uint32_t count) +{ + struct target *target = bank->target; + struct cc3220sf_bank *cc3220sf_bank = bank->driver_priv; + struct working_area *algo_working_area; + struct working_area *buffer_working_area; + struct reg_param reg_params[3]; + uint32_t algo_base_address; + uint32_t algo_buffer_address; + uint32_t algo_buffer_size; + uint32_t address; + uint32_t remaining; + uint32_t words; + uint32_t result; + + int retval = ERROR_OK; + + if (TARGET_HALTED != target->state) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + /* Obtain working area to use for flash helper algorithm */ + retval = target_alloc_working_area(target, sizeof(cc3220sf_algo), + &algo_working_area); + if (ERROR_OK != retval) + return retval; + + /* Obtain working area to use for flash buffer */ + retval = target_alloc_working_area(target, + target_get_working_area_avail(target), &buffer_working_area); + if (ERROR_OK != retval) { + target_free_working_area(target, algo_working_area); + return retval; + } + + algo_base_address = algo_working_area->address; + algo_buffer_address = buffer_working_area->address; + algo_buffer_size = buffer_working_area->size; + + /* Make sure buffer size is a multiple of 32 word (0x80 byte) chunks */ + /* (algo runs more efficiently if it operates on 32 words at a time) */ + if (algo_buffer_size > 0x80) + algo_buffer_size &= ~0x7f; + + /* Write flash helper algorithm into target memory */ + retval = target_write_buffer(target, algo_base_address, + sizeof(cc3220sf_algo), cc3220sf_algo); + if (ERROR_OK != retval) { + target_free_working_area(target, algo_working_area); + target_free_working_area(target, buffer_working_area); + return retval; + } + + /* Initialize the ARMv7m specific info to run the algorithm */ + cc3220sf_bank->armv7m_info.common_magic = ARMV7M_COMMON_MAGIC; + cc3220sf_bank->armv7m_info.core_mode = ARM_MODE_THREAD; + + /* Initialize register params for flash helper algorithm */ + init_reg_param(®_params[0], "r0", 32, PARAM_OUT); + init_reg_param(®_params[1], "r1", 32, PARAM_OUT); + init_reg_param(®_params[2], "r2", 32, PARAM_IN_OUT); + + /* Prepare to write to flash */ + address = FLASH_BASE_ADDR + offset; + remaining = count; + + /* The flash hardware can only write complete words to flash. If + * an unaligned address is passed in, we must do a read-modify-write + * on a word with enough bytes to align the rest of the buffer. And + * if less than a whole word remains at the end, we must also do a + * read-modify-write on a final word to finish up. + */ + + /* Do one word write to align address on 32-bit boundary if needed */ + if (0 != (address & 0x3)) { + uint8_t head[4]; + + /* Get starting offset for data to write (will be 1 to 3) */ + uint32_t head_offset = address & 0x03; + + /* Get the aligned address to write this first word to */ + uint32_t head_address = address & 0xfffffffc; + + /* Retrieve what is already in flash at the head address */ + retval = target_read_buffer(target, head_address, sizeof(head), head); + + if (ERROR_OK == retval) { + /* Substitute in the new data to write */ + while ((remaining > 0) && (head_offset < 4)) { + head[head_offset] = *buffer; + head_offset++; + address++; + buffer++; + remaining--; + } + } + + if (ERROR_OK == retval) { + /* Helper parameters are passed in registers R0-R2 */ + /* Set start of data buffer, address to write to, and word count */ + buf_set_u32(reg_params[0].value, 0, 32, algo_buffer_address); + buf_set_u32(reg_params[1].value, 0, 32, head_address); + buf_set_u32(reg_params[2].value, 0, 32, 1); + + /* Write head value into buffer to flash */ + retval = target_write_buffer(target, algo_buffer_address, + sizeof(head), head); + } + + if (ERROR_OK == retval) { + /* Execute the flash helper algorithm */ + retval = target_run_algorithm(target, 0, NULL, 3, reg_params, + algo_base_address, 0, FLASH_TIMEOUT, + &cc3220sf_bank->armv7m_info); + if (ERROR_OK != retval) + LOG_ERROR("cc3220sf: Flash algorithm failed to run"); + + /* Check that the head value was written to flash */ + result = buf_get_u32(reg_params[2].value, 0, 32); + if (0 != result) { + retval = ERROR_FAIL; + LOG_ERROR("cc3220sf: Flash operation failed"); + } + } + } + + /* Check if there's data at end of buffer that isn't a full word */ + uint32_t tail_count = remaining & 0x03; + /* Adjust remaining so it is a multiple of whole words */ + remaining -= tail_count; + + while ((ERROR_OK == retval) && (remaining > 0)) { + /* Set start of data buffer and address to write to */ + buf_set_u32(reg_params[0].value, 0, 32, algo_buffer_address); + buf_set_u32(reg_params[1].value, 0, 32, address); + + /* Download data to write into memory buffer */ + if (remaining >= algo_buffer_size) { + /* Fill up buffer with data to flash */ + retval = target_write_buffer(target, algo_buffer_address, + algo_buffer_size, buffer); + if (ERROR_OK != retval) + break; + + /* Count to write is in 32-bit words */ + words = algo_buffer_size / 4; + + /* Bump variables to next data */ + address += algo_buffer_size; + buffer += algo_buffer_size; + remaining -= algo_buffer_size; + } else { + /* Fill buffer with what's left of the data */ + retval = target_write_buffer(target, algo_buffer_address, + remaining, buffer); + if (ERROR_OK != retval) + break; + + /* Calculate the final word count to write */ + words = remaining / 4; + if (0 != (remaining % 4)) + words++; + + /* Bump variables to any final data */ + address += remaining; + buffer += remaining; + remaining = 0; + } + + /* Set number of words to write */ + buf_set_u32(reg_params[2].value, 0, 32, words); + + /* Execute the flash helper algorithm */ + retval = target_run_algorithm(target, 0, NULL, 3, reg_params, + algo_base_address, 0, FLASH_TIMEOUT, + &cc3220sf_bank->armv7m_info); + if (ERROR_OK != retval) { + LOG_ERROR("cc3220sf: Flash algorithm failed to run"); + break; + } + + /* Check that all words were written to flash */ + result = buf_get_u32(reg_params[2].value, 0, 32); + if (0 != result) { + retval = ERROR_FAIL; + LOG_ERROR("cc3220sf: Flash operation failed"); + break; + } + } + + /* Do one word write for any final bytes less than a full word */ + if ((ERROR_OK == retval) && (0 != tail_count)) { + uint8_t tail[4]; + + /* Set starting byte offset for data to write */ + uint32_t tail_offset = 0; + + /* Retrieve what is already in flash at the tail address */ + retval = target_read_buffer(target, address, sizeof(tail), tail); + + if (ERROR_OK == retval) { + /* Substitute in the new data to write */ + while (tail_count > 0) { + tail[tail_offset] = *buffer; + tail_offset++; + buffer++; + tail_count--; + } + } + + if (ERROR_OK == retval) { + /* Set start of data buffer, address to write to, and word count */ + buf_set_u32(reg_params[0].value, 0, 32, algo_buffer_address); + buf_set_u32(reg_params[1].value, 0, 32, address); + buf_set_u32(reg_params[2].value, 0, 32, 1); + + /* Write tail value into buffer to flash */ + retval = target_write_buffer(target, algo_buffer_address, + sizeof(tail), tail); + } + + if (ERROR_OK == retval) { + /* Execute the flash helper algorithm */ + retval = target_run_algorithm(target, 0, NULL, 3, reg_params, + algo_base_address, 0, FLASH_TIMEOUT, + &cc3220sf_bank->armv7m_info); + if (ERROR_OK != retval) + LOG_ERROR("cc3220sf: Flash algorithm failed to run"); + + /* Check that the tail was written to flash */ + result = buf_get_u32(reg_params[2].value, 0, 32); + if (0 != result) { + retval = ERROR_FAIL; + LOG_ERROR("cc3220sf: Flash operation failed"); + } + } + } + + /* Free resources */ + destroy_reg_param(®_params[0]); + destroy_reg_param(®_params[1]); + destroy_reg_param(®_params[2]); + target_free_working_area(target, algo_working_area); + target_free_working_area(target, buffer_working_area); + + return retval; +} + +static int cc3220sf_probe(struct flash_bank *bank) +{ + struct cc3220sf_bank *cc3220sf_bank = bank->driver_priv; + + uint32_t base; + uint32_t size; + int num_sectors; + int bank_id; + + bank_id = bank->bank_number; + + if (0 == bank_id) { + base = FLASH_BASE_ADDR; + size = FLASH_NUM_SECTORS * FLASH_SECTOR_SIZE; + num_sectors = FLASH_NUM_SECTORS; + } else { + /* Invalid bank number somehow */ + return ERROR_FAIL; + } + + if (NULL != bank->sectors) { + free(bank->sectors); + bank->sectors = NULL; + } + + bank->sectors = malloc(sizeof(struct flash_sector) * num_sectors); + if (NULL == bank->sectors) + return ERROR_FAIL; + + bank->base = base; + bank->size = size; + bank->write_start_alignment = 0; + bank->write_end_alignment = 0; + bank->num_sectors = num_sectors; + + for (int i = 0; i < num_sectors; i++) { + bank->sectors[i].offset = i * FLASH_SECTOR_SIZE; + bank->sectors[i].size = FLASH_SECTOR_SIZE; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = 0; + } + + /* We've successfully recorded the stats on this flash bank */ + cc3220sf_bank->probed = true; + + /* If we fall through to here, then all went well */ + + return ERROR_OK; +} + +static int cc3220sf_auto_probe(struct flash_bank *bank) +{ + struct cc3220sf_bank *cc3220sf_bank = bank->driver_priv; + + int retval = ERROR_OK; + + if (0 != bank->bank_number) { + /* Invalid bank number somehow */ + return ERROR_FAIL; + } + + if (!cc3220sf_bank->probed) + retval = cc3220sf_probe(bank); + + return retval; +} + +static int cc3220sf_protect_check(struct flash_bank *bank) +{ + return ERROR_OK; +} + +static int cc3220sf_info(struct flash_bank *bank, char *buf, int buf_size) +{ + int printed; + + printed = snprintf(buf, buf_size, "CC3220SF with 1MB internal flash\n"); + + if (printed >= buf_size) + return ERROR_BUF_TOO_SMALL; + + return ERROR_OK; +} + +struct flash_driver cc3220sf_flash = { + .name = "cc3220sf", + .flash_bank_command = cc3220sf_flash_bank_command, + .erase = cc3220sf_erase, + .protect = cc3220sf_protect, + .write = cc3220sf_write, + .read = default_flash_read, + .probe = cc3220sf_probe, + .auto_probe = cc3220sf_auto_probe, + .erase_check = default_flash_blank_check, + .protect_check = cc3220sf_protect_check, + .info = cc3220sf_info, + .free_driver_priv = default_flash_free_driver_priv, +}; diff --git a/src/flash/nor/cc3220sf.h b/src/flash/nor/cc3220sf.h new file mode 100644 index 0000000..36c17be --- /dev/null +++ b/src/flash/nor/cc3220sf.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * Copyright (C) 2017 by Texas Instruments, Inc. * + * * + * 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 2 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 <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#ifndef OPENOCD_FLASH_NOR_CC3220SF_H +#define OPENOCD_FLASH_NOR_CC3220SF_H + +/* CC3220SF device types */ +#define CC3220_NO_TYPE 0 /* Device type not determined yet */ +#define CC3220_OTHER 1 /* CC3220 variant without flash */ +#define CC3220SF 2 /* CC3220SF variant with flash */ + +/* Flash parameters */ +#define FLASH_BASE_ADDR 0x01000000 +#define FLASH_SECTOR_SIZE 2048 +#define FLASH_NUM_SECTORS 512 + +/* CC2200SF flash registers */ +#define FMA_REGISTER_ADDR 0x400FD000 +#define FMC_REGISTER_ADDR 0x400FD008 +#define FMC_DEFAULT_VALUE 0xA4420000 +#define FMC_ERASE_BIT 0x00000002 +#define FMC_MERASE_BIT 0x00000004 +#define FMC_ERASE_VALUE (FMC_DEFAULT_VALUE | FMC_ERASE_BIT) +#define FMC_MERASE_VALUE (FMC_DEFAULT_VALUE | FMC_MERASE_BIT) + +/* Flash helper algorithm for CC3220SF */ +const uint8_t cc3220sf_algo[] = { +#include "../../../contrib/loaders/flash/cc3220sf/cc3220sf.inc" +}; + +#endif /* OPENOCD_FLASH_NOR_CC3220SF_H */ diff --git a/src/flash/nor/core.c b/src/flash/nor/core.c index f05c68b..4941281 100644 --- a/src/flash/nor/core.c +++ b/src/flash/nor/core.c @@ -188,9 +188,17 @@ void flash_free_all_banks(void) else LOG_WARNING("Flash driver of %s does not support free_driver_priv()", bank->name); + /* For 'virtual' flash driver bank->sectors and bank->prot_blocks pointers are copied from + * master flash_bank structure. They point to memory locations allocated by master flash driver + * so master driver is responsible for releasing them. + * Avoid UB caused by double-free memory corruption if flash bank is 'virtual'. */ + + if (strcmp(bank->driver->name, "virtual") != 0) { + free(bank->sectors); + free(bank->prot_blocks); + } + free(bank->name); - free(bank->sectors); - free(bank->prot_blocks); free(bank); bank = next; } diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index 3d6dab2..a094c20 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -32,6 +32,7 @@ extern struct flash_driver ath79_flash; extern struct flash_driver atsamv_flash; extern struct flash_driver avr_flash; extern struct flash_driver bluenrgx_flash; +extern struct flash_driver cc3220sf_flash; extern struct flash_driver cfi_flash; extern struct flash_driver dsp5680xx_flash; extern struct flash_driver efm32_flash; @@ -56,6 +57,9 @@ extern struct flash_driver numicro_flash; extern struct flash_driver ocl_flash; extern struct flash_driver pic32mx_flash; extern struct flash_driver psoc4_flash; +extern struct flash_driver psoc5lp_flash; +extern struct flash_driver psoc5lp_eeprom_flash; +extern struct flash_driver psoc5lp_nvl_flash; extern struct flash_driver psoc6_flash; extern struct flash_driver sim3x_flash; extern struct flash_driver stellaris_flash; @@ -91,6 +95,7 @@ static struct flash_driver *flash_drivers[] = { &atsamv_flash, &avr_flash, &bluenrgx_flash, + &cc3220sf_flash, &cfi_flash, &dsp5680xx_flash, &efm32_flash, @@ -115,6 +120,9 @@ static struct flash_driver *flash_drivers[] = { &ocl_flash, &pic32mx_flash, &psoc4_flash, + &psoc5lp_flash, + &psoc5lp_eeprom_flash, + &psoc5lp_nvl_flash, &psoc6_flash, &sim3x_flash, &stellaris_flash, diff --git a/src/flash/nor/psoc5lp.c b/src/flash/nor/psoc5lp.c new file mode 100644 index 0000000..ae8e3d3 --- /dev/null +++ b/src/flash/nor/psoc5lp.c @@ -0,0 +1,1574 @@ +/* + * PSoC 5LP flash driver + * + * Copyright (c) 2016 Andreas Färber + * + * 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 2 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 <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imp.h" +#include <helper/time_support.h> +#include <target/armv7m.h> + +#define PM_ACT_CFG0 0x400043A0 +#define PM_ACT_CFG12 0x400043AC +#define SPC_CPU_DATA 0x40004720 +#define SPC_SR 0x40004722 +#define PRT1_PC2 0x4000500A +#define PHUB_CH0_BASIC_CFG 0x40007010 +#define PHUB_CH0_ACTION 0x40007014 +#define PHUB_CH0_BASIC_STATUS 0x40007018 +#define PHUB_CH1_BASIC_CFG 0x40007020 +#define PHUB_CH1_ACTION 0x40007024 +#define PHUB_CH1_BASIC_STATUS 0x40007028 +#define PHUB_CFGMEM0_CFG0 0x40007600 +#define PHUB_CFGMEM0_CFG1 0x40007604 +#define PHUB_CFGMEM1_CFG0 0x40007608 +#define PHUB_CFGMEM1_CFG1 0x4000760C +#define PHUB_TDMEM0_ORIG_TD0 0x40007800 +#define PHUB_TDMEM0_ORIG_TD1 0x40007804 +#define PHUB_TDMEM1_ORIG_TD0 0x40007808 +#define PHUB_TDMEM1_ORIG_TD1 0x4000780C +#define PANTHER_DEVICE_ID 0x4008001C + +/* NVL is not actually mapped to the Cortex-M address space + * As we need a base addess different from other banks in the device + * we use the address of NVL programming data in Cypress images */ +#define NVL_META_BASE 0x90000000 + +#define PM_ACT_CFG12_EN_EE (1 << 4) + +#define SPC_KEY1 0xB6 +#define SPC_KEY2 0xD3 + +#define SPC_LOAD_BYTE 0x00 +#define SPC_LOAD_MULTI_BYTE 0x01 +#define SPC_LOAD_ROW 0x02 +#define SPC_READ_BYTE 0x03 +#define SPC_READ_MULTI_BYTE 0x04 +#define SPC_WRITE_ROW 0x05 +#define SPC_WRITE_USER_NVL 0x06 +#define SPC_PRG_ROW 0x07 +#define SPC_ERASE_SECTOR 0x08 +#define SPC_ERASE_ALL 0x09 +#define SPC_READ_HIDDEN_ROW 0x0A +#define SPC_PROGRAM_PROTECT_ROW 0x0B +#define SPC_GET_CHECKSUM 0x0C +#define SPC_GET_TEMP 0x0E +#define SPC_READ_VOLATILE_BYTE 0x10 + +#define SPC_ARRAY_ALL 0x3F +#define SPC_ARRAY_EEPROM 0x40 +#define SPC_ARRAY_NVL_USER 0x80 +#define SPC_ARRAY_NVL_WO 0xF8 + +#define SPC_ROW_PROTECTION 0 + +#define SPC_OPCODE_LEN 3 + +#define SPC_SR_DATA_READY (1 << 0) +#define SPC_SR_IDLE (1 << 1) + +#define PM_ACT_CFG0_EN_CLK_SPC (1 << 3) + +#define PHUB_CHx_BASIC_CFG_EN (1 << 0) +#define PHUB_CHx_BASIC_CFG_WORK_SEP (1 << 5) + +#define PHUB_CHx_ACTION_CPU_REQ (1 << 0) + +#define PHUB_CFGMEMx_CFG0 (1 << 7) + +#define PHUB_TDMEMx_ORIG_TD0_NEXT_TD_PTR_LAST (0xff << 16) +#define PHUB_TDMEMx_ORIG_TD0_INC_SRC_ADDR (1 << 24) + +#define NVL_3_ECCEN (1 << 3) + +#define ROW_SIZE 256 +#define ROW_ECC_SIZE 32 +#define ROWS_PER_SECTOR 64 +#define SECTOR_SIZE (ROWS_PER_SECTOR * ROW_SIZE) +#define ROWS_PER_BLOCK 256 +#define BLOCK_SIZE (ROWS_PER_BLOCK * ROW_SIZE) +#define SECTORS_PER_BLOCK (BLOCK_SIZE / SECTOR_SIZE) +#define EEPROM_ROW_SIZE 16 +#define EEPROM_SECTOR_SIZE (ROWS_PER_SECTOR * EEPROM_ROW_SIZE) +#define EEPROM_BLOCK_SIZE (ROWS_PER_BLOCK * EEPROM_ROW_SIZE) + +#define PART_NUMBER_LEN (17 + 1) + +struct psoc5lp_device { + uint32_t id; + unsigned fam; + unsigned speed_mhz; + unsigned flash_kb; + unsigned eeprom_kb; +}; + +/* + * Device information collected from datasheets. + * Different temperature ranges (C/I/Q/A) may share IDs, not differing otherwise. + */ +static const struct psoc5lp_device psoc5lp_devices[] = { + /* CY8C58LP Family Datasheet */ + { .id = 0x2E11F069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E120069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E123069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E124069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E126069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E127069, .fam = 8, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E117069, .fam = 8, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E118069, .fam = 8, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E119069, .fam = 8, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E11C069, .fam = 8, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E114069, .fam = 8, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E115069, .fam = 8, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E116069, .fam = 8, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E160069, .fam = 8, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + /* '' */ + { .id = 0x2E161069, .fam = 8, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + /* '' */ + { .id = 0x2E1D2069, .fam = 8, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E1D6069, .fam = 8, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + + /* CY8C56LP Family Datasheet */ + { .id = 0x2E10A069, .fam = 6, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E10D069, .fam = 6, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E10E069, .fam = 6, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E106069, .fam = 6, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E108069, .fam = 6, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E109069, .fam = 6, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E101069, .fam = 6, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E104069, .fam = 6, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + /* '' */ + { .id = 0x2E105069, .fam = 6, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E128069, .fam = 6, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + /* '' */ + { .id = 0x2E122069, .fam = 6, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E129069, .fam = 6, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E163069, .fam = 6, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E156069, .fam = 6, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E1D3069, .fam = 6, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + + /* CY8C54LP Family Datasheet */ + { .id = 0x2E11A069, .fam = 4, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E16A069, .fam = 4, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E12A069, .fam = 4, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E103069, .fam = 4, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E16C069, .fam = 4, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E102069, .fam = 4, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E148069, .fam = 4, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E155069, .fam = 4, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E16B069, .fam = 4, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E12B069, .fam = 4, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E168069, .fam = 4, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E178069, .fam = 4, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E15D069, .fam = 4, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E1D4069, .fam = 4, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + + /* CY8C52LP Family Datasheet */ + { .id = 0x2E11E069, .fam = 2, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E12F069, .fam = 2, .speed_mhz = 67, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E133069, .fam = 2, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E159069, .fam = 2, .speed_mhz = 67, .flash_kb = 128, .eeprom_kb = 2 }, + { .id = 0x2E11D069, .fam = 2, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E121069, .fam = 2, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E184069, .fam = 2, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E196069, .fam = 2, .speed_mhz = 67, .flash_kb = 64, .eeprom_kb = 2 }, + { .id = 0x2E132069, .fam = 2, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E138069, .fam = 2, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E13A069, .fam = 2, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E152069, .fam = 2, .speed_mhz = 67, .flash_kb = 32, .eeprom_kb = 2 }, + { .id = 0x2E15F069, .fam = 2, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E15A069, .fam = 2, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, + { .id = 0x2E1D5069, .fam = 2, .speed_mhz = 80, .flash_kb = 256, .eeprom_kb = 2 }, +}; + +static void psoc5lp_get_part_number(const struct psoc5lp_device *dev, char *str) +{ + strcpy(str, "CY8Cabcdefg-LPxxx"); + + str[4] = '5'; + str[5] = '0' + dev->fam; + + switch (dev->speed_mhz) { + case 67: + str[6] = '6'; + break; + case 80: + str[6] = '8'; + break; + default: + str[6] = '?'; + } + + switch (dev->flash_kb) { + case 32: + str[7] = '5'; + break; + case 64: + str[7] = '6'; + break; + case 128: + str[7] = '7'; + break; + case 256: + str[7] = '8'; + break; + default: + str[7] = '?'; + } + + /* Package does not matter. */ + strncpy(str + 8, "xx", 2); + + /* Temperate range cannot uniquely be identified. */ + str[10] = 'x'; +} + +static int psoc5lp_get_device_id(struct target *target, uint32_t *id) +{ + int retval; + + retval = target_read_u32(target, PANTHER_DEVICE_ID, id); /* dummy read */ + if (retval != ERROR_OK) + return retval; + retval = target_read_u32(target, PANTHER_DEVICE_ID, id); + return retval; +} + +static int psoc5lp_find_device(struct target *target, + const struct psoc5lp_device **device) +{ + uint32_t device_id; + unsigned i; + int retval; + + *device = NULL; + + retval = psoc5lp_get_device_id(target, &device_id); + if (retval != ERROR_OK) + return retval; + LOG_DEBUG("PANTHER_DEVICE_ID = 0x%08" PRIX32, device_id); + + for (i = 0; i < ARRAY_SIZE(psoc5lp_devices); i++) { + if (psoc5lp_devices[i].id == device_id) { + *device = &psoc5lp_devices[i]; + return ERROR_OK; + } + } + + LOG_ERROR("Device 0x%08" PRIX32 " not supported", device_id); + return ERROR_FLASH_OPER_UNSUPPORTED; +} + +static int psoc5lp_spc_enable_clock(struct target *target) +{ + int retval; + uint8_t pm_act_cfg0; + + retval = target_read_u8(target, PM_ACT_CFG0, &pm_act_cfg0); + if (retval != ERROR_OK) { + LOG_ERROR("Cannot read PM_ACT_CFG0"); + return retval; + } + + if (pm_act_cfg0 & PM_ACT_CFG0_EN_CLK_SPC) + return ERROR_OK; /* clock already enabled */ + + retval = target_write_u8(target, PM_ACT_CFG0, pm_act_cfg0 | PM_ACT_CFG0_EN_CLK_SPC); + if (retval != ERROR_OK) + LOG_ERROR("Cannot enable SPC clock"); + + return retval; +} + +static int psoc5lp_spc_write_opcode(struct target *target, uint8_t opcode) +{ + int retval; + + retval = target_write_u8(target, SPC_CPU_DATA, SPC_KEY1); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, SPC_KEY2 + opcode); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, opcode); + return retval; +} + +static void psoc5lp_spc_write_opcode_buffer(struct target *target, + uint8_t *buf, uint8_t opcode) +{ + buf[0] = SPC_KEY1; + buf[1] = SPC_KEY2 + opcode; + buf[2] = opcode; +} + +static int psoc5lp_spc_busy_wait_data(struct target *target) +{ + int64_t endtime; + uint8_t sr; + int retval; + + retval = target_read_u8(target, SPC_SR, &sr); /* dummy read */ + if (retval != ERROR_OK) + return retval; + + endtime = timeval_ms() + 1000; /* 1 second timeout */ + do { + alive_sleep(1); + retval = target_read_u8(target, SPC_SR, &sr); + if (retval != ERROR_OK) + return retval; + if (sr == SPC_SR_DATA_READY) + return ERROR_OK; + } while (timeval_ms() < endtime); + + return ERROR_FLASH_OPERATION_FAILED; +} + +static int psoc5lp_spc_busy_wait_idle(struct target *target) +{ + int64_t endtime; + uint8_t sr; + int retval; + + retval = target_read_u8(target, SPC_SR, &sr); /* dummy read */ + if (retval != ERROR_OK) + return retval; + + endtime = timeval_ms() + 1000; /* 1 second timeout */ + do { + alive_sleep(1); + retval = target_read_u8(target, SPC_SR, &sr); + if (retval != ERROR_OK) + return retval; + if (sr == SPC_SR_IDLE) + return ERROR_OK; + } while (timeval_ms() < endtime); + + return ERROR_FLASH_OPERATION_FAILED; +} + +static int psoc5lp_spc_load_byte(struct target *target, + uint8_t array_id, uint8_t offset, uint8_t value) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_LOAD_BYTE); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, offset); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, value); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_load_row(struct target *target, + uint8_t array_id, const uint8_t *data, unsigned row_size) +{ + unsigned i; + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_LOAD_ROW); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + + for (i = 0; i < row_size; i++) { + retval = target_write_u8(target, SPC_CPU_DATA, data[i]); + if (retval != ERROR_OK) + return retval; + } + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_read_byte(struct target *target, + uint8_t array_id, uint8_t offset, uint8_t *data) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_READ_BYTE); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, offset); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_data(target); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u8(target, SPC_CPU_DATA, data); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_write_row(struct target *target, + uint8_t array_id, uint16_t row_id, const uint8_t *temp) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_WRITE_ROW); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, row_id >> 8); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, row_id & 0xff); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, temp[0]); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, temp[1]); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_write_user_nvl(struct target *target, + uint8_t array_id) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_WRITE_USER_NVL); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_erase_sector(struct target *target, + uint8_t array_id, uint8_t row_id) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_ERASE_SECTOR); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, row_id); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_erase_all(struct target *target) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_ERASE_ALL); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_read_hidden_row(struct target *target, + uint8_t array_id, uint8_t row_id, uint8_t *data) +{ + int i, retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_READ_HIDDEN_ROW); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, row_id); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_data(target); + if (retval != ERROR_OK) + return retval; + + for (i = 0; i < ROW_SIZE; i++) { + retval = target_read_u8(target, SPC_CPU_DATA, &data[i]); + if (retval != ERROR_OK) + return retval; + } + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_get_temp(struct target *target, uint8_t samples, + uint8_t *data) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_GET_TEMP); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, samples); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_data(target); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u8(target, SPC_CPU_DATA, &data[0]); + if (retval != ERROR_OK) + return retval; + retval = target_read_u8(target, SPC_CPU_DATA, &data[1]); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static int psoc5lp_spc_read_volatile_byte(struct target *target, + uint8_t array_id, uint8_t offset, uint8_t *data) +{ + int retval; + + retval = psoc5lp_spc_write_opcode(target, SPC_READ_VOLATILE_BYTE); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, array_id); + if (retval != ERROR_OK) + return retval; + retval = target_write_u8(target, SPC_CPU_DATA, offset); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_data(target); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u8(target, SPC_CPU_DATA, data); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +/* + * NV Latch + */ + +struct psoc5lp_nvl_flash_bank { + bool probed; + const struct psoc5lp_device *device; +}; + +static int psoc5lp_nvl_read(struct flash_bank *bank, + uint8_t *buffer, uint32_t offset, uint32_t count) +{ + int retval; + + retval = psoc5lp_spc_enable_clock(bank->target); + if (retval != ERROR_OK) + return retval; + + while (count > 0) { + retval = psoc5lp_spc_read_byte(bank->target, + SPC_ARRAY_NVL_USER, offset, buffer); + if (retval != ERROR_OK) + return retval; + buffer++; + offset++; + count--; + } + + return ERROR_OK; +} + +static int psoc5lp_nvl_erase(struct flash_bank *bank, int first, int last) +{ + LOG_WARNING("There is no erase operation for NV Latches"); + return ERROR_FLASH_OPER_UNSUPPORTED; +} + +static int psoc5lp_nvl_erase_check(struct flash_bank *bank) +{ + int i; + + for (i = 0; i < bank->num_sectors; i++) + bank->sectors[i].is_erased = 0; + + return ERROR_OK; +} + +static int psoc5lp_nvl_write(struct flash_bank *bank, + const uint8_t *buffer, uint32_t offset, uint32_t byte_count) +{ + struct target *target = bank->target; + uint8_t *current_data, val; + bool write_required = false, pullup_needed = false, ecc_changed = false; + uint32_t i; + int retval; + + if (offset != 0 || byte_count != bank->size) { + LOG_ERROR("NVL can only be written in whole"); + return ERROR_FLASH_OPER_UNSUPPORTED; + } + + current_data = calloc(1, bank->size); + if (!current_data) + return ERROR_FAIL; + retval = psoc5lp_nvl_read(bank, current_data, offset, byte_count); + if (retval != ERROR_OK) { + free(current_data); + return retval; + } + for (i = offset; i < byte_count; i++) { + if (current_data[i] != buffer[i]) { + write_required = true; + break; + } + } + if (((buffer[2] & 0x80) == 0x80) && ((current_data[0] & 0x0C) != 0x08)) + pullup_needed = true; + if (((buffer[3] ^ current_data[3]) & 0x08) == 0x08) + ecc_changed = true; + free(current_data); + + if (!write_required) { + LOG_INFO("Unchanged, skipping NVL write"); + return ERROR_OK; + } + if (pullup_needed) { + retval = target_read_u8(target, PRT1_PC2, &val); + if (retval != ERROR_OK) + return retval; + val &= 0xF0; + val |= 0x05; + retval = target_write_u8(target, PRT1_PC2, val); + if (retval != ERROR_OK) + return retval; + } + + for (i = offset; i < byte_count; i++) { + retval = psoc5lp_spc_load_byte(target, + SPC_ARRAY_NVL_USER, i, buffer[i]); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_read_volatile_byte(target, + SPC_ARRAY_NVL_USER, i, &val); + if (retval != ERROR_OK) + return retval; + if (val != buffer[i]) { + LOG_ERROR("Failed to load NVL byte %" PRIu32 ": " + "expected 0x%02" PRIx8 ", read 0x%02" PRIx8, + i, buffer[i], val); + return ERROR_FLASH_OPERATION_FAILED; + } + } + + retval = psoc5lp_spc_write_user_nvl(target, SPC_ARRAY_NVL_USER); + if (retval != ERROR_OK) + return retval; + + if (ecc_changed) { + retval = target_call_reset_callbacks(target, RESET_INIT); + if (retval != ERROR_OK) + LOG_WARNING("Reset failed after enabling or disabling ECC"); + } + + return ERROR_OK; +} + +static int psoc5lp_nvl_protect_check(struct flash_bank *bank) +{ + int i; + + for (i = 0; i < bank->num_sectors; i++) + bank->sectors[i].is_protected = -1; + + return ERROR_OK; +} + +static int psoc5lp_nvl_get_info_command(struct flash_bank *bank, + char *buf, int buf_size) +{ + struct psoc5lp_nvl_flash_bank *psoc_nvl_bank = bank->driver_priv; + char part_number[PART_NUMBER_LEN]; + + psoc5lp_get_part_number(psoc_nvl_bank->device, part_number); + + snprintf(buf, buf_size, "%s", part_number); + + return ERROR_OK; +} + +static int psoc5lp_nvl_probe(struct flash_bank *bank) +{ + struct psoc5lp_nvl_flash_bank *psoc_nvl_bank = bank->driver_priv; + int retval; + + if (psoc_nvl_bank->probed) + return ERROR_OK; + + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + retval = psoc5lp_find_device(bank->target, &psoc_nvl_bank->device); + if (retval != ERROR_OK) + return retval; + + bank->base = NVL_META_BASE; + bank->size = 4; + bank->num_sectors = 1; + bank->sectors = calloc(bank->num_sectors, + sizeof(struct flash_sector)); + bank->sectors[0].offset = 0; + bank->sectors[0].size = 4; + bank->sectors[0].is_erased = -1; + bank->sectors[0].is_protected = -1; + + psoc_nvl_bank->probed = true; + + return ERROR_OK; +} + +static int psoc5lp_nvl_auto_probe(struct flash_bank *bank) +{ + struct psoc5lp_nvl_flash_bank *psoc_nvl_bank = bank->driver_priv; + + if (psoc_nvl_bank->probed) + return ERROR_OK; + + return psoc5lp_nvl_probe(bank); +} + +FLASH_BANK_COMMAND_HANDLER(psoc5lp_nvl_flash_bank_command) +{ + struct psoc5lp_nvl_flash_bank *psoc_nvl_bank; + + psoc_nvl_bank = malloc(sizeof(struct psoc5lp_nvl_flash_bank)); + if (!psoc_nvl_bank) + return ERROR_FLASH_OPERATION_FAILED; + + psoc_nvl_bank->probed = false; + + bank->driver_priv = psoc_nvl_bank; + + return ERROR_OK; +} + +static const struct command_registration psoc5lp_nvl_exec_command_handlers[] = { + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration psoc5lp_nvl_command_handlers[] = { + { + .name = "psoc5lp_nvl", + .mode = COMMAND_ANY, + .help = "PSoC 5LP NV Latch command group", + .usage = "", + .chain = psoc5lp_nvl_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +struct flash_driver psoc5lp_nvl_flash = { + .name = "psoc5lp_nvl", + .commands = psoc5lp_nvl_command_handlers, + .flash_bank_command = psoc5lp_nvl_flash_bank_command, + .info = psoc5lp_nvl_get_info_command, + .probe = psoc5lp_nvl_probe, + .auto_probe = psoc5lp_nvl_auto_probe, + .protect_check = psoc5lp_nvl_protect_check, + .read = psoc5lp_nvl_read, + .erase = psoc5lp_nvl_erase, + .erase_check = psoc5lp_nvl_erase_check, + .write = psoc5lp_nvl_write, +}; + +/* + * EEPROM + */ + +struct psoc5lp_eeprom_flash_bank { + bool probed; + const struct psoc5lp_device *device; +}; + +static int psoc5lp_eeprom_erase(struct flash_bank *bank, int first, int last) +{ + int i, retval; + + for (i = first; i <= last; i++) { + retval = psoc5lp_spc_erase_sector(bank->target, + SPC_ARRAY_EEPROM, i); + if (retval != ERROR_OK) + return retval; + } + + return ERROR_OK; +} + +static int psoc5lp_eeprom_write(struct flash_bank *bank, + const uint8_t *buffer, uint32_t offset, uint32_t byte_count) +{ + struct target *target = bank->target; + uint8_t temp[2]; + unsigned row; + int retval; + + if (offset % EEPROM_ROW_SIZE != 0) { + LOG_ERROR("Writes must be row-aligned, got offset 0x%08" PRIx32, + offset); + return ERROR_FLASH_DST_BREAKS_ALIGNMENT; + } + + retval = psoc5lp_spc_get_temp(target, 3, temp); + if (retval != ERROR_OK) { + LOG_ERROR("Unable to read Die temperature"); + return retval; + } + LOG_DEBUG("Get_Temp: sign 0x%02" PRIx8 ", magnitude 0x%02" PRIx8, + temp[0], temp[1]); + + for (row = offset / EEPROM_ROW_SIZE; byte_count >= EEPROM_ROW_SIZE; row++) { + retval = psoc5lp_spc_load_row(target, SPC_ARRAY_EEPROM, + buffer, EEPROM_ROW_SIZE); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_write_row(target, SPC_ARRAY_EEPROM, + row, temp); + if (retval != ERROR_OK) + return retval; + + buffer += EEPROM_ROW_SIZE; + byte_count -= EEPROM_ROW_SIZE; + offset += EEPROM_ROW_SIZE; + } + if (byte_count > 0) { + uint8_t buf[EEPROM_ROW_SIZE]; + + memcpy(buf, buffer, byte_count); + memset(buf + byte_count, bank->default_padded_value, + EEPROM_ROW_SIZE - byte_count); + + LOG_DEBUG("Padding %d bytes", EEPROM_ROW_SIZE - byte_count); + retval = psoc5lp_spc_load_row(target, SPC_ARRAY_EEPROM, + buf, EEPROM_ROW_SIZE); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_write_row(target, SPC_ARRAY_EEPROM, + row, temp); + if (retval != ERROR_OK) + return retval; + } + + return ERROR_OK; +} + +static int psoc5lp_eeprom_protect_check(struct flash_bank *bank) +{ + int i; + + for (i = 0; i < bank->num_sectors; i++) + bank->sectors[i].is_protected = -1; + + return ERROR_OK; +} + +static int psoc5lp_eeprom_get_info_command(struct flash_bank *bank, char *buf, int buf_size) +{ + struct psoc5lp_eeprom_flash_bank *psoc_eeprom_bank = bank->driver_priv; + char part_number[PART_NUMBER_LEN]; + + psoc5lp_get_part_number(psoc_eeprom_bank->device, part_number); + + snprintf(buf, buf_size, "%s", part_number); + + return ERROR_OK; +} + +static int psoc5lp_eeprom_probe(struct flash_bank *bank) +{ + struct psoc5lp_eeprom_flash_bank *psoc_eeprom_bank = bank->driver_priv; + uint32_t flash_addr = bank->base; + uint32_t val; + int i, retval; + + if (psoc_eeprom_bank->probed) + return ERROR_OK; + + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + retval = psoc5lp_find_device(bank->target, &psoc_eeprom_bank->device); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u32(bank->target, PM_ACT_CFG12, &val); + if (retval != ERROR_OK) + return retval; + if (!(val & PM_ACT_CFG12_EN_EE)) { + val |= PM_ACT_CFG12_EN_EE; + retval = target_write_u32(bank->target, PM_ACT_CFG12, val); + if (retval != ERROR_OK) + return retval; + } + + bank->size = psoc_eeprom_bank->device->eeprom_kb * 1024; + bank->num_sectors = DIV_ROUND_UP(bank->size, EEPROM_SECTOR_SIZE); + bank->sectors = calloc(bank->num_sectors, + sizeof(struct flash_sector)); + for (i = 0; i < bank->num_sectors; i++) { + bank->sectors[i].size = EEPROM_SECTOR_SIZE; + bank->sectors[i].offset = flash_addr - bank->base; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = -1; + + flash_addr += bank->sectors[i].size; + } + + bank->default_padded_value = bank->erased_value = 0x00; + + psoc_eeprom_bank->probed = true; + + return ERROR_OK; +} + +static int psoc5lp_eeprom_auto_probe(struct flash_bank *bank) +{ + struct psoc5lp_eeprom_flash_bank *psoc_eeprom_bank = bank->driver_priv; + + if (psoc_eeprom_bank->probed) + return ERROR_OK; + + return psoc5lp_eeprom_probe(bank); +} + +FLASH_BANK_COMMAND_HANDLER(psoc5lp_eeprom_flash_bank_command) +{ + struct psoc5lp_eeprom_flash_bank *psoc_eeprom_bank; + + psoc_eeprom_bank = malloc(sizeof(struct psoc5lp_eeprom_flash_bank)); + if (!psoc_eeprom_bank) + return ERROR_FLASH_OPERATION_FAILED; + + psoc_eeprom_bank->probed = false; + psoc_eeprom_bank->device = NULL; + + bank->driver_priv = psoc_eeprom_bank; + + return ERROR_OK; +} + +static const struct command_registration psoc5lp_eeprom_exec_command_handlers[] = { + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration psoc5lp_eeprom_command_handlers[] = { + { + .name = "psoc5lp_eeprom", + .mode = COMMAND_ANY, + .help = "PSoC 5LP EEPROM command group", + .usage = "", + .chain = psoc5lp_eeprom_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +struct flash_driver psoc5lp_eeprom_flash = { + .name = "psoc5lp_eeprom", + .commands = psoc5lp_eeprom_command_handlers, + .flash_bank_command = psoc5lp_eeprom_flash_bank_command, + .info = psoc5lp_eeprom_get_info_command, + .probe = psoc5lp_eeprom_probe, + .auto_probe = psoc5lp_eeprom_auto_probe, + .protect_check = psoc5lp_eeprom_protect_check, + .read = default_flash_read, + .erase = psoc5lp_eeprom_erase, + .erase_check = default_flash_blank_check, + .write = psoc5lp_eeprom_write, +}; + +/* + * Program Flash + */ + +struct psoc5lp_flash_bank { + bool probed; + const struct psoc5lp_device *device; + bool ecc_enabled; +}; + +static int psoc5lp_erase(struct flash_bank *bank, int first, int last) +{ + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + int i, retval; + + if (!psoc_bank->ecc_enabled) { + /* Silently avoid erasing sectors twice */ + if (last >= first + bank->num_sectors / 2) { + LOG_DEBUG("Skipping duplicate erase of sectors %d to %d", + first + bank->num_sectors / 2, last); + last = first + (bank->num_sectors / 2) - 1; + } + /* Check for any remaining ECC sectors */ + if (last >= bank->num_sectors / 2) { + LOG_WARNING("Skipping erase of ECC region sectors %d to %d", + bank->num_sectors / 2, last); + last = (bank->num_sectors / 2) - 1; + } + } + + for (i = first; i <= last; i++) { + retval = psoc5lp_spc_erase_sector(bank->target, + i / SECTORS_PER_BLOCK, i % SECTORS_PER_BLOCK); + if (retval != ERROR_OK) + return retval; + } + + return ERROR_OK; +} + +/* Derived from core.c:default_flash_blank_check() */ +static int psoc5lp_erase_check(struct flash_bank *bank) +{ + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + struct target *target = bank->target; + int i, retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + struct target_memory_check_block *block_array; + block_array = malloc(bank->num_sectors * sizeof(struct target_memory_check_block)); + if (block_array == NULL) + return ERROR_FAIL; + + for (i = 0; i < bank->num_sectors; i++) { + block_array[i].address = bank->base + bank->sectors[i].offset; + block_array[i].size = bank->sectors[i].size; + block_array[i].result = UINT32_MAX; /* erase state unknown */ + } + + bool fast_check = true; + for (i = 0; i < bank->num_sectors; ) { + retval = armv7m_blank_check_memory(target, + block_array + i, bank->num_sectors - i, + bank->erased_value); + if (retval < 1) { + /* Run slow fallback if the first run gives no result + * otherwise use possibly incomplete results */ + if (i == 0) + fast_check = false; + break; + } + i += retval; /* add number of blocks done this round */ + } + + if (fast_check) { + if (!psoc_bank->ecc_enabled) { + int half_sectors = bank->num_sectors / 2; + for (i = 0; i < half_sectors / 2; i++) + bank->sectors[i].is_erased = + (block_array[i].result != 1) + ? block_array[i + half_sectors].result + : block_array[i].result; + } else { + for (i = 0; i < bank->num_sectors; i++) + bank->sectors[i].is_erased = block_array[i].result; + } + retval = ERROR_OK; + } else { + LOG_ERROR("Can't run erase check - add working memory"); + retval = ERROR_FAIL; + } + free(block_array); + + return retval; +} + +static int psoc5lp_write(struct flash_bank *bank, const uint8_t *buffer, + uint32_t offset, uint32_t byte_count) +{ + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + struct target *target = bank->target; + struct working_area *code_area, *even_row_area, *odd_row_area; + uint32_t row_size; + uint8_t temp[2], buf[12], ecc_bytes[ROW_ECC_SIZE]; + unsigned array_id, row; + int i, retval; + + if (offset + byte_count > bank->size) { + LOG_ERROR("Writing to ECC not supported"); + return ERROR_FLASH_DST_OUT_OF_BANK; + } + + if (offset % ROW_SIZE != 0) { + LOG_ERROR("Writes must be row-aligned, got offset 0x%08" PRIx32, + offset); + return ERROR_FLASH_DST_BREAKS_ALIGNMENT; + } + + row_size = ROW_SIZE; + if (!psoc_bank->ecc_enabled) { + row_size += ROW_ECC_SIZE; + memset(ecc_bytes, bank->default_padded_value, ROW_ECC_SIZE); + } + + retval = psoc5lp_spc_get_temp(target, 3, temp); + if (retval != ERROR_OK) { + LOG_ERROR("Unable to read Die temperature"); + return retval; + } + LOG_DEBUG("Get_Temp: sign 0x%02" PRIx8 ", magnitude 0x%02" PRIx8, + temp[0], temp[1]); + + assert(target_get_working_area_avail(target) == target->working_area_size); + retval = target_alloc_working_area(target, + target_get_working_area_avail(target) / 2, &code_area); + if (retval != ERROR_OK) { + LOG_ERROR("Could not allocate working area for program SRAM"); + return retval; + } + assert(code_area->address < 0x20000000); + + retval = target_alloc_working_area(target, + SPC_OPCODE_LEN + 1 + row_size + 3 + SPC_OPCODE_LEN + 6, + &even_row_area); + if (retval != ERROR_OK) { + LOG_ERROR("Could not allocate working area for even row"); + goto err_alloc_even; + } + assert(even_row_area->address >= 0x20000000); + + retval = target_alloc_working_area(target, even_row_area->size, + &odd_row_area); + if (retval != ERROR_OK) { + LOG_ERROR("Could not allocate working area for odd row"); + goto err_alloc_odd; + } + assert(odd_row_area->address >= 0x20000000); + + for (array_id = offset / BLOCK_SIZE; byte_count > 0; array_id++) { + for (row = (offset / ROW_SIZE) % ROWS_PER_BLOCK; + row < ROWS_PER_BLOCK && byte_count > 0; row++) { + bool even_row = (row % 2 == 0); + struct working_area *data_area = even_row ? even_row_area : odd_row_area; + unsigned len = MIN(ROW_SIZE, byte_count); + + LOG_DEBUG("Writing load command for array %u row %u at 0x%08" TARGET_PRIxADDR, + array_id, row, data_area->address); + + psoc5lp_spc_write_opcode_buffer(target, buf, SPC_LOAD_ROW); + buf[SPC_OPCODE_LEN] = array_id; + retval = target_write_buffer(target, data_area->address, 4, buf); + if (retval != ERROR_OK) + goto err_write; + + retval = target_write_buffer(target, + data_area->address + SPC_OPCODE_LEN + 1, + len, buffer); + if (retval != ERROR_OK) + goto err_write; + buffer += len; + byte_count -= len; + offset += len; + + if (len < ROW_SIZE) { + uint8_t padding[ROW_SIZE]; + + memset(padding, bank->default_padded_value, ROW_SIZE); + + LOG_DEBUG("Padding %d bytes", ROW_SIZE - len); + retval = target_write_buffer(target, + data_area->address + SPC_OPCODE_LEN + 1 + len, + ROW_SIZE - len, padding); + if (retval != ERROR_OK) + goto err_write; + } + + if (!psoc_bank->ecc_enabled) { + retval = target_write_buffer(target, + data_area->address + SPC_OPCODE_LEN + 1 + ROW_SIZE, + sizeof(ecc_bytes), ecc_bytes); + if (retval != ERROR_OK) + goto err_write; + } + + for (i = 0; i < 3; i++) + buf[i] = 0x00; /* 3 NOPs for short delay */ + psoc5lp_spc_write_opcode_buffer(target, buf + 3, SPC_PRG_ROW); + buf[3 + SPC_OPCODE_LEN] = array_id; + buf[3 + SPC_OPCODE_LEN + 1] = row >> 8; + buf[3 + SPC_OPCODE_LEN + 2] = row & 0xff; + memcpy(buf + 3 + SPC_OPCODE_LEN + 3, temp, 2); + buf[3 + SPC_OPCODE_LEN + 5] = 0x00; /* padding */ + retval = target_write_buffer(target, + data_area->address + SPC_OPCODE_LEN + 1 + row_size, + 12, buf); + if (retval != ERROR_OK) + goto err_write; + + retval = target_write_u32(target, + even_row ? PHUB_CH0_BASIC_STATUS : PHUB_CH1_BASIC_STATUS, + (even_row ? 0 : 1) << 8); + if (retval != ERROR_OK) + goto err_dma; + + retval = target_write_u32(target, + even_row ? PHUB_CH0_BASIC_CFG : PHUB_CH1_BASIC_CFG, + PHUB_CHx_BASIC_CFG_WORK_SEP | PHUB_CHx_BASIC_CFG_EN); + if (retval != ERROR_OK) + goto err_dma; + + retval = target_write_u32(target, + even_row ? PHUB_CFGMEM0_CFG0 : PHUB_CFGMEM1_CFG0, + PHUB_CFGMEMx_CFG0); + if (retval != ERROR_OK) + goto err_dma; + + retval = target_write_u32(target, + even_row ? PHUB_CFGMEM0_CFG1 : PHUB_CFGMEM1_CFG1, + ((SPC_CPU_DATA >> 16) << 16) | (data_area->address >> 16)); + if (retval != ERROR_OK) + goto err_dma; + + retval = target_write_u32(target, + even_row ? PHUB_TDMEM0_ORIG_TD0 : PHUB_TDMEM1_ORIG_TD0, + PHUB_TDMEMx_ORIG_TD0_INC_SRC_ADDR | + PHUB_TDMEMx_ORIG_TD0_NEXT_TD_PTR_LAST | + ((SPC_OPCODE_LEN + 1 + row_size + 3 + SPC_OPCODE_LEN + 5) & 0xfff)); + if (retval != ERROR_OK) + goto err_dma; + + retval = target_write_u32(target, + even_row ? PHUB_TDMEM0_ORIG_TD1 : PHUB_TDMEM1_ORIG_TD1, + ((SPC_CPU_DATA & 0xffff) << 16) | (data_area->address & 0xffff)); + if (retval != ERROR_OK) + goto err_dma; + + retval = psoc5lp_spc_busy_wait_idle(target); + if (retval != ERROR_OK) + goto err_idle; + + retval = target_write_u32(target, + even_row ? PHUB_CH0_ACTION : PHUB_CH1_ACTION, + PHUB_CHx_ACTION_CPU_REQ); + if (retval != ERROR_OK) + goto err_dma_action; + } + } + + retval = psoc5lp_spc_busy_wait_idle(target); + +err_dma_action: +err_idle: +err_dma: +err_write: + target_free_working_area(target, odd_row_area); +err_alloc_odd: + target_free_working_area(target, even_row_area); +err_alloc_even: + target_free_working_area(target, code_area); + + return retval; +} + +static int psoc5lp_protect_check(struct flash_bank *bank) +{ + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + uint8_t row_data[ROW_SIZE]; + const unsigned protection_bytes_per_sector = ROWS_PER_SECTOR * 2 / 8; + unsigned i, j, k, num_sectors; + int retval; + + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + for (i = 0; i < DIV_ROUND_UP(bank->size, BLOCK_SIZE); i++) { + retval = psoc5lp_spc_read_hidden_row(bank->target, i, + SPC_ROW_PROTECTION, row_data); + if (retval != ERROR_OK) + return retval; + + /* Last flash array may have less rows, but in practice full sectors. */ + if (i == bank->size / BLOCK_SIZE) + num_sectors = (bank->size % BLOCK_SIZE) / SECTOR_SIZE; + else + num_sectors = SECTORS_PER_BLOCK; + + for (j = 0; j < num_sectors; j++) { + int sector_nr = i * SECTORS_PER_BLOCK + j; + struct flash_sector *sector = &bank->sectors[sector_nr]; + struct flash_sector *ecc_sector; + + if (psoc_bank->ecc_enabled) + ecc_sector = &bank->sectors[bank->num_sectors + sector_nr]; + else + ecc_sector = &bank->sectors[bank->num_sectors / 2 + sector_nr]; + + sector->is_protected = ecc_sector->is_protected = 0; + for (k = protection_bytes_per_sector * j; + k < protection_bytes_per_sector * (j + 1); k++) { + assert(k < protection_bytes_per_sector * SECTORS_PER_BLOCK); + LOG_DEBUG("row[%u][%02u] = 0x%02" PRIx8, i, k, row_data[k]); + if (row_data[k] != 0x00) { + sector->is_protected = ecc_sector->is_protected = 1; + break; + } + } + } + } + + return ERROR_OK; +} + +static int psoc5lp_get_info_command(struct flash_bank *bank, char *buf, int buf_size) +{ + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + char part_number[PART_NUMBER_LEN]; + const char *ecc; + + psoc5lp_get_part_number(psoc_bank->device, part_number); + ecc = psoc_bank->ecc_enabled ? "ECC enabled" : "ECC disabled"; + + snprintf(buf, buf_size, "%s %s", part_number, ecc); + + return ERROR_OK; +} + +static int psoc5lp_probe(struct flash_bank *bank) +{ + struct target *target = bank->target; + struct psoc5lp_flash_bank *psoc_bank = bank->driver_priv; + uint32_t flash_addr = bank->base; + uint8_t nvl[4], temp[2]; + int i, retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if (!psoc_bank->device) { + retval = psoc5lp_find_device(target, &psoc_bank->device); + if (retval != ERROR_OK) + return retval; + + bank->size = psoc_bank->device->flash_kb * 1024; + } + + bank->num_sectors = DIV_ROUND_UP(bank->size, SECTOR_SIZE); + + if (!psoc_bank->probed) { + retval = psoc5lp_spc_enable_clock(target); + if (retval != ERROR_OK) + return retval; + + /* First values read are inaccurate, so do it once now. */ + retval = psoc5lp_spc_get_temp(target, 3, temp); + if (retval != ERROR_OK) { + LOG_ERROR("Unable to read Die temperature"); + return retval; + } + + bank->sectors = calloc(bank->num_sectors * 2, + sizeof(struct flash_sector)); + for (i = 0; i < bank->num_sectors; i++) { + bank->sectors[i].size = SECTOR_SIZE; + bank->sectors[i].offset = flash_addr - bank->base; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = -1; + + flash_addr += bank->sectors[i].size; + } + flash_addr = 0x48000000; + for (i = bank->num_sectors; i < bank->num_sectors * 2; i++) { + bank->sectors[i].size = ROWS_PER_SECTOR * ROW_ECC_SIZE; + bank->sectors[i].offset = flash_addr - bank->base; + bank->sectors[i].is_erased = -1; + bank->sectors[i].is_protected = -1; + + flash_addr += bank->sectors[i].size; + } + + bank->default_padded_value = bank->erased_value = 0x00; + + psoc_bank->probed = true; + } + + retval = psoc5lp_spc_read_byte(target, SPC_ARRAY_NVL_USER, 3, &nvl[3]); + if (retval != ERROR_OK) + return retval; + LOG_DEBUG("NVL[%d] = 0x%02" PRIx8, 3, nvl[3]); + psoc_bank->ecc_enabled = nvl[3] & NVL_3_ECCEN; + + if (!psoc_bank->ecc_enabled) + bank->num_sectors *= 2; + + return ERROR_OK; +} + +static int psoc5lp_auto_probe(struct flash_bank *bank) +{ + return psoc5lp_probe(bank); +} + +COMMAND_HANDLER(psoc5lp_handle_mass_erase_command) +{ + struct flash_bank *bank; + int retval; + + if (CMD_ARGC < 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); + if (retval != ERROR_OK) + return retval; + + retval = psoc5lp_spc_erase_all(bank->target); + if (retval == ERROR_OK) + command_print(CMD_CTX, "PSoC 5LP erase succeeded"); + else + command_print(CMD_CTX, "PSoC 5LP erase failed"); + + return retval; +} + +FLASH_BANK_COMMAND_HANDLER(psoc5lp_flash_bank_command) +{ + struct psoc5lp_flash_bank *psoc_bank; + + psoc_bank = malloc(sizeof(struct psoc5lp_flash_bank)); + if (!psoc_bank) + return ERROR_FLASH_OPERATION_FAILED; + + psoc_bank->probed = false; + psoc_bank->device = NULL; + + bank->driver_priv = psoc_bank; + + return ERROR_OK; +} + +static const struct command_registration psoc5lp_exec_command_handlers[] = { + { + .name = "mass_erase", + .handler = psoc5lp_handle_mass_erase_command, + .mode = COMMAND_EXEC, + .usage = "bank_id", + .help = "Erase all flash data and ECC/configuration bytes, " + "all flash protection rows, " + "and all row latches in all flash arrays on the device.", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration psoc5lp_command_handlers[] = { + { + .name = "psoc5lp", + .mode = COMMAND_ANY, + .help = "PSoC 5LP flash command group", + .usage = "", + .chain = psoc5lp_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +struct flash_driver psoc5lp_flash = { + .name = "psoc5lp", + .commands = psoc5lp_command_handlers, + .flash_bank_command = psoc5lp_flash_bank_command, + .info = psoc5lp_get_info_command, + .probe = psoc5lp_probe, + .auto_probe = psoc5lp_auto_probe, + .protect_check = psoc5lp_protect_check, + .read = default_flash_read, + .erase = psoc5lp_erase, + .erase_check = psoc5lp_erase_check, + .write = psoc5lp_write, +}; diff --git a/src/flash/nor/virtual.c b/src/flash/nor/virtual.c index d5d688b..15c4bff 100644 --- a/src/flash/nor/virtual.c +++ b/src/flash/nor/virtual.c @@ -46,8 +46,13 @@ static void virtual_update_bank_info(struct flash_bank *bank) bank->bus_width = master_bank->bus_width; bank->erased_value = master_bank->erased_value; bank->default_padded_value = master_bank->default_padded_value; + bank->write_start_alignment = master_bank->write_start_alignment; + bank->write_end_alignment = master_bank->write_end_alignment; + bank->minimal_write_gap = master_bank->minimal_write_gap; bank->num_sectors = master_bank->num_sectors; bank->sectors = master_bank->sectors; + bank->num_prot_blocks = master_bank->num_prot_blocks; + bank->prot_blocks = master_bank->prot_blocks; } FLASH_BANK_COMMAND_HANDLER(virtual_flash_bank_command) diff --git a/src/jtag/drivers/Makefile.am b/src/jtag/drivers/Makefile.am index b3b4d21..ccef018 100644 --- a/src/jtag/drivers/Makefile.am +++ b/src/jtag/drivers/Makefile.am @@ -156,10 +156,12 @@ endif if IMX_GPIO DRIVERFILES += %D%/imx_gpio.c endif - if KITPROG DRIVERFILES += %D%/kitprog.c endif +if XDS110 +DRIVERFILES += %D%/xds110.c +endif DRIVERHEADERS = \ %D%/bitbang.h \ diff --git a/src/jtag/drivers/sysfsgpio.c b/src/jtag/drivers/sysfsgpio.c index 5a4651d..5535c71 100644 --- a/src/jtag/drivers/sysfsgpio.c +++ b/src/jtag/drivers/sysfsgpio.c @@ -58,11 +58,11 @@ /* * Helper func to determine if gpio number valid * - * Assume here that there will be less than 1000 gpios on a system + * Assume here that there will be less than 10000 gpios on a system */ static int is_gpio_valid(int gpio) { - return gpio >= 0 && gpio < 1000; + return gpio >= 0 && gpio < 10000; } /* @@ -89,7 +89,7 @@ static int open_write_close(const char *name, const char *valstr) */ static void unexport_sysfs_gpio(int gpio) { - char gpiostr[4]; + char gpiostr[5]; if (!is_gpio_valid(gpio)) return; @@ -113,7 +113,7 @@ static void unexport_sysfs_gpio(int gpio) static int setup_sysfs_gpio(int gpio, int is_output, int init_high) { char buf[40]; - char gpiostr[4]; + char gpiostr[5]; int ret; if (!is_gpio_valid(gpio)) diff --git a/src/jtag/drivers/xds110.c b/src/jtag/drivers/xds110.c new file mode 100644 index 0000000..b97eef2 --- /dev/null +++ b/src/jtag/drivers/xds110.c @@ -0,0 +1,1973 @@ +/*************************************************************************** + * Copyright (C) 2017 by Texas Instruments, Inc. * + * * + * 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 2 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 <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <transport/transport.h> +#include <jtag/swd.h> +#include <jtag/interface.h> +#include <jtag/commands.h> +#include <jtag/tcl.h> +#include <libusb.h> + +/* XDS110 USB serial number length */ +#define XDS110_SERIAL_LEN 8 + +/* Firmware version that introduced OpenOCD support via block accesses */ +#define OCD_FIRMWARE_VERSION 0x02030011 +#define OCD_FIRMWARE_UPGRADE \ + "XDS110: upgrade to version 2.3.0.11+ for improved support" + +/*************************************************************************** + * USB Connection Buffer Definitions * + ***************************************************************************/ + +/* Max USB packet size for up to USB 3.0 */ +#define MAX_PACKET 1024 + +/* + * Maximum data payload that can be handled in a single call + * Limitation is the size of the buffers in the XDS110 firmware + */ +#define MAX_DATA_BLOCK 4096 + +#ifndef USB_PAYLOAD_SIZE +/* Largest data block plus parameters */ +#define USB_PAYLOAD_SIZE (MAX_DATA_BLOCK + 60) +#endif +#define MAX_RESULT_QUEUE (MAX_DATA_BLOCK / 4) + +/*************************************************************************** + * USB Connection Endpoints * + ***************************************************************************/ + +/* Bulk endpoints used by the XDS110 debug interface */ +#define INTERFACE_DEBUG (2) +#define ENDPOINT_DEBUG_IN (3 | LIBUSB_ENDPOINT_IN) +#define ENDPOINT_DEBUG_OUT (2 | LIBUSB_ENDPOINT_OUT) + +/*************************************************************************** + * XDS110 Firmware API Definitions * + ***************************************************************************/ + +/* + * Default values controlling how the host communicates commands + * with XDS110 firmware (automatic retry count and wait timeout) + */ +#define DEFAULT_ATTEMPTS (1) +#define DEFAULT_TIMEOUT (4000) + +/* XDS110 API error codes */ +#define SC_ERR_NONE 0 +#define SC_ERR_XDS110_FAIL -261 +#define SC_ERR_SWD_WAIT -613 +#define SC_ERR_SWD_FAULT -614 +#define SC_ERR_SWD_PROTOCOL -615 +#define SC_ERR_SWD_PARITY -616 +#define SC_ERR_SWD_DEVICE_ID -617 + +/* TCK frequency limits */ +#define XDS110_MIN_TCK_SPEED 100 /* kHz */ +#define XDS110_MAX_TCK_SPEED 2500 /* kHz */ +#define XDS110_TCK_PULSE_INCREMENT 66.0 + +/* Scan mode on connect */ +#define MODE_JTAG 1 + +/* XDS110 API JTAG state definitions */ +#define XDS_JTAG_STATE_RESET 1 +#define XDS_JTAG_STATE_IDLE 2 +#define XDS_JTAG_STATE_SHIFT_DR 3 +#define XDS_JTAG_STATE_SHIFT_IR 4 +#define XDS_JTAG_STATE_PAUSE_DR 5 +#define XDS_JTAG_STATE_PAUSE_IR 6 +#define XDS_JTAG_STATE_EXIT1_DR 8 +#define XDS_JTAG_STATE_EXIT1_IR 9 +#define XDS_JTAG_STATE_EXIT2_DR 10 +#define XDS_JTAG_STATE_EXIT2_IR 11 +#define XDS_JTAG_STATE_SELECT_DR 12 +#define XDS_JTAG_STATE_SELECT_IR 13 +#define XDS_JTAG_STATE_UPDATE_DR 14 +#define XDS_JTAG_STATE_UPDATE_IR 15 +#define XDS_JTAG_STATE_CAPTURE_DR 16 +#define XDS_JTAG_STATE_CAPTURE_IR 17 + +/* XDS110 API JTAG transit definitions */ +#define XDS_JTAG_TRANSIT_QUICKEST 1 +#define XDS_JTAG_TRANSIT_VIA_CAPTURE 2 +#define XDS_JTAG_TRANSIT_VIA_IDLE 3 + +/* DAP register definitions as used by XDS110 APIs */ + +#define DAP_AP 0 /* DAP AP register type */ +#define DAP_DP 1 /* DAP DP register type */ + +#define DAP_DP_IDCODE 0x0 /* DAP DP IDCODE register (read only) */ +#define DAP_DP_ABORT 0x0 /* DAP DP ABORT register (write only) */ +#define DAP_DP_STAT 0x4 /* DAP DP STAT register (for read only) */ +#define DAP_DP_CTRL 0x4 /* DAP DP CTRL register (for write only) */ +#define DAP_DP_ADDR 0x8 /* DAP DP SELECT register (legacy name) */ +#define DAP_DP_RESEND 0x8 /* DAP DP RESEND register (read only) */ +#define DAP_DP_SELECT 0x8 /* DAP DP SELECT register (write only) */ +#define DAP_DP_RDBUFF 0xc /* DAP DP RDBUFF Read Buffer register */ + +#define DAP_AP_CSW 0x00 /* DAP AP Control Status Word */ +#define DAP_AP_TAR 0x04 /* DAP AP Transfer Address */ +#define DAP_AP_DRW 0x0C /* DAP AP Data Read/Write */ +#define DAP_AP_BD0 0x10 /* DAP AP Banked Data 0 */ +#define DAP_AP_BD1 0x14 /* DAP AP Banked Data 1 */ +#define DAP_AP_BD2 0x18 /* DAP AP Banked Data 2 */ +#define DAP_AP_BD3 0x1C /* DAP AP Banked Data 3 */ +#define DAP_AP_RTBL 0xF8 /* DAP AP Debug ROM Table */ +#define DAP_AP_IDR 0xFC /* DAP AP Identification Register */ + +/* Command packet definitions */ + +#define XDS_OUT_LEN 1 /* command (byte) */ +#define XDS_IN_LEN 4 /* error code (int) */ + +/* XDS API Commands */ +#define XDS_CONNECT 0x01 /* Connect JTAG connection */ +#define XDS_DISCONNECT 0x02 /* Disconnect JTAG connection */ +#define XDS_VERSION 0x03 /* Get firmware version and hardware ID */ +#define XDS_SET_TCK 0x04 /* Set TCK delay (to set TCK frequency) */ +#define XDS_SET_TRST 0x05 /* Assert or deassert nTRST signal */ +#define XDS_CYCLE_TCK 0x07 /* Toggle TCK for a number of cycles */ +#define XDS_GOTO_STATE 0x09 /* Go to requested JTAG state */ +#define XDS_JTAG_SCAN 0x0c /* Send and receive JTAG scan */ +#define XDS_SET_SRST 0x0e /* Assert or deassert nSRST signal */ +#define CMAPI_CONNECT 0x0f /* CMAPI connect */ +#define CMAPI_DISCONNECT 0x10 /* CMAPI disconnect */ +#define CMAPI_ACQUIRE 0x11 /* CMAPI acquire */ +#define CMAPI_RELEASE 0x12 /* CMAPI release */ +#define CMAPI_REG_READ 0x15 /* CMAPI DAP register read */ +#define CMAPI_REG_WRITE 0x16 /* CMAPI DAP register write */ +#define SWD_CONNECT 0x17 /* Switch from JTAG to SWD connection */ +#define SWD_DISCONNECT 0x18 /* Switch from SWD to JTAG connection */ +#define CJTAG_CONNECT 0x2b /* Switch from JTAG to cJTAG connection */ +#define CJTAG_DISCONNECT 0x2c /* Switch from cJTAG to JTAG connection */ +#define OCD_DAP_REQUEST 0x3a /* Handle block of DAP requests */ +#define OCD_SCAN_REQUEST 0x3b /* Handle block of JTAG scan requests */ +#define OCD_PATHMOVE 0x3c /* Handle PATHMOVE to navigate JTAG states */ + +#define CMD_IR_SCAN 1 +#define CMD_DR_SCAN 2 +#define CMD_RUNTEST 3 +#define CMD_STABLECLOCKS 4 + +/* Array to convert from OpenOCD tap_state_t to XDS JTAG state */ +const uint32_t xds_jtag_state[] = { + XDS_JTAG_STATE_EXIT2_DR, /* TAP_DREXIT2 = 0x0 */ + XDS_JTAG_STATE_EXIT1_DR, /* TAP_DREXIT1 = 0x1 */ + XDS_JTAG_STATE_SHIFT_DR, /* TAP_DRSHIFT = 0x2 */ + XDS_JTAG_STATE_PAUSE_DR, /* TAP_DRPAUSE = 0x3 */ + XDS_JTAG_STATE_SELECT_IR, /* TAP_IRSELECT = 0x4 */ + XDS_JTAG_STATE_UPDATE_DR, /* TAP_DRUPDATE = 0x5 */ + XDS_JTAG_STATE_CAPTURE_DR, /* TAP_DRCAPTURE = 0x6 */ + XDS_JTAG_STATE_SELECT_DR, /* TAP_DRSELECT = 0x7 */ + XDS_JTAG_STATE_EXIT2_IR, /* TAP_IREXIT2 = 0x8 */ + XDS_JTAG_STATE_EXIT1_IR, /* TAP_IREXIT1 = 0x9 */ + XDS_JTAG_STATE_SHIFT_IR, /* TAP_IRSHIFT = 0xa */ + XDS_JTAG_STATE_PAUSE_IR, /* TAP_IRPAUSE = 0xb */ + XDS_JTAG_STATE_IDLE, /* TAP_IDLE = 0xc */ + XDS_JTAG_STATE_UPDATE_IR, /* TAP_IRUPDATE = 0xd */ + XDS_JTAG_STATE_CAPTURE_IR, /* TAP_IRCAPTURE = 0xe */ + XDS_JTAG_STATE_RESET, /* TAP_RESET = 0xf */ +}; + +struct scan_result { + bool first; + uint8_t *buffer; + uint32_t num_bits; +}; + +struct xds110_info { + /* USB connection handles and data buffers */ + libusb_context *ctx; + libusb_device_handle *dev; + unsigned char read_payload[USB_PAYLOAD_SIZE]; + unsigned char write_packet[3]; + unsigned char write_payload[USB_PAYLOAD_SIZE]; + /* Status flags */ + bool is_connected; + bool is_cmapi_connected; + bool is_cmapi_acquired; + bool is_swd_mode; + bool is_ap_dirty; + /* DAP register caches */ + uint32_t select; + uint32_t rdbuff; + bool use_rdbuff; + /* TCK speed and delay count*/ + uint32_t speed; + uint32_t delay_count; + /* XDS110 serial number */ + char serial[XDS110_SERIAL_LEN + 1]; + /* XDS110 firmware and hardware version */ + uint32_t firmware; + uint16_t hardware; + /* Transaction queues */ + unsigned char txn_requests[MAX_DATA_BLOCK]; + uint32_t *txn_dap_results[MAX_DATA_BLOCK / 4]; + struct scan_result txn_scan_results[MAX_DATA_BLOCK / 4]; + uint32_t txn_request_size; + uint32_t txn_result_size; + uint32_t txn_result_count; +}; + +static struct xds110_info xds110 = { + .ctx = NULL, + .dev = NULL, + .is_connected = false, + .is_cmapi_connected = false, + .is_cmapi_acquired = false, + .is_swd_mode = false, + .is_ap_dirty = false, + .speed = XDS110_MAX_TCK_SPEED, + .delay_count = 0, + .serial = {0}, + .firmware = 0, + .hardware = 0, + .txn_request_size = 0, + .txn_result_size = 0, + .txn_result_count = 0 +}; + +static inline void xds110_set_u32(uint8_t *buffer, uint32_t value) +{ + buffer[3] = (value >> 24) & 0xff; + buffer[2] = (value >> 16) & 0xff; + buffer[1] = (value >> 8) & 0xff; + buffer[0] = (value >> 0) & 0xff; +} + +static inline void xds110_set_u16(uint8_t *buffer, uint16_t value) +{ + buffer[1] = (value >> 8) & 0xff; + buffer[0] = (value >> 0) & 0xff; +} + +static inline uint32_t xds110_get_u32(uint8_t *buffer) +{ + uint32_t value = (((uint32_t)buffer[3]) << 24) | + (((uint32_t)buffer[2]) << 16) | + (((uint32_t)buffer[1]) << 8) | + (((uint32_t)buffer[0]) << 0); + return value; +} + +static inline uint16_t xds110_get_u16(uint8_t *buffer) +{ + uint16_t value = (((uint32_t)buffer[1]) << 8) | + (((uint32_t)buffer[0]) << 0); + return value; +} + +/*************************************************************************** + * usb connection routines * + * * + * The following functions handle connecting, reading, and writing to * + * the XDS110 over USB using the libusb library. * + ***************************************************************************/ + +static bool usb_connect(void) +{ + libusb_context *ctx = NULL; + libusb_device **list = NULL; + libusb_device_handle *dev = NULL; + + struct libusb_device_descriptor desc; + + uint16_t vid = 0x0451; + uint16_t pid = 0xbef3; + ssize_t count = 0; + ssize_t i = 0; + int result = 0; + bool found = false; + + /* Initialize libusb context */ + result = libusb_init(&ctx); + + if (0 == result) { + /* Get list of USB devices attached to system */ + count = libusb_get_device_list(ctx, &list); + if (count <= 0) { + result = -1; + list = NULL; + } + } + + if (0 == result) { + /* Scan through list of devices for any XDS110s */ + for (i = 0; i < count; i++) { + /* Check for device VID/PID match */ + libusb_get_device_descriptor(list[i], &desc); + if (desc.idVendor == vid && desc.idProduct == pid) { + result = libusb_open(list[i], &dev); + if (0 == result) { + const int MAX_DATA = 256; + unsigned char data[MAX_DATA + 1]; + *data = '\0'; + + /* May be the requested device if serial number matches */ + if (0 == xds110.serial[0]) { + /* No serial number given; match first XDS110 found */ + found = true; + break; + } else { + /* Get the device's serial number string */ + result = libusb_get_string_descriptor_ascii(dev, + desc.iSerialNumber, data, MAX_DATA); + if (0 < result && + 0 == strcmp((char *)data, (char *)xds110.serial)) { + found = true; + break; + } + } + + /* If we fall though to here, we don't want this device */ + libusb_close(dev); + dev = NULL; + } + } + } + } + + /* + * We can fall through the for() loop with two possible exit conditions: + * 1) found the right XDS110, and that device is open + * 2) didn't find the XDS110, and no devices are currently open + */ + + if (NULL != list) { + /* Free the device list, we're done with it */ + libusb_free_device_list(list, 1); + } + + if (found) { + /* Save the context and device handles */ + xds110.ctx = ctx; + xds110.dev = dev; + + /* Set libusb to auto detach kernel */ + (void)libusb_set_auto_detach_kernel_driver(dev, 1); + + /* Claim the debug interface on the XDS110 */ + result = libusb_claim_interface(dev, INTERFACE_DEBUG); + } else { + /* Couldn't find an XDS110, flag the error */ + result = -1; + } + + /* On an error, clean up what we can */ + if (0 != result) { + if (NULL != dev) { + /* Release the debug and data interface on the XDS110 */ + (void)libusb_release_interface(dev, INTERFACE_DEBUG); + libusb_close(dev); + } + if (NULL != ctx) + libusb_exit(ctx); + xds110.ctx = NULL; + xds110.dev = NULL; + } + + /* Log the results */ + if (0 == result) + LOG_INFO("XDS110: connected"); + else + LOG_ERROR("XDS110: failed to connect"); + + return (0 == result) ? true : false; +} + +static void usb_disconnect(void) +{ + if (NULL != xds110.dev) { + /* Release the debug and data interface on the XDS110 */ + (void)libusb_release_interface(xds110.dev, INTERFACE_DEBUG); + libusb_close(xds110.dev); + xds110.dev = NULL; + } + if (NULL != xds110.ctx) { + libusb_exit(xds110.ctx); + xds110.ctx = NULL; + } + + LOG_INFO("XDS110: disconnected"); +} + +static bool usb_read(unsigned char *buffer, int size, int *bytes_read, + int timeout) +{ + int result; + + if (NULL == xds110.dev || NULL == buffer || NULL == bytes_read) + return false; + + /* Force a non-zero timeout to prevent blocking */ + if (0 == timeout) + timeout = DEFAULT_TIMEOUT; + + result = libusb_bulk_transfer(xds110.dev, ENDPOINT_DEBUG_IN, buffer, size, + bytes_read, timeout); + + return (0 == result) ? true : false; +} + +static bool usb_write(unsigned char *buffer, int size, int *written) +{ + int bytes_written = 0; + int result = LIBUSB_SUCCESS; + int retries = 0; + + if (NULL == xds110.dev || NULL == buffer) + return false; + + result = libusb_bulk_transfer(xds110.dev, ENDPOINT_DEBUG_OUT, buffer, + size, &bytes_written, 0); + + while (LIBUSB_ERROR_PIPE == result && retries < 3) { + /* Try clearing the pipe stall and retry transfer */ + libusb_clear_halt(xds110.dev, ENDPOINT_DEBUG_OUT); + result = libusb_bulk_transfer(xds110.dev, ENDPOINT_DEBUG_OUT, buffer, + size, &bytes_written, 0); + retries++; + } + + if (NULL != written) + *written = bytes_written; + + return (0 == result && size == bytes_written) ? true : false; +} + +static bool usb_get_response(uint32_t *total_bytes_read, uint32_t timeout) +{ + static unsigned char buffer[MAX_PACKET]; + int bytes_read; + uint16_t size; + uint16_t count; + bool success; + + size = 0; + success = true; + while (success) { + success = usb_read(buffer, sizeof(buffer), &bytes_read, timeout); + if (success) { + /* + * Validate that this appears to be a good response packet + * First check it contains enough data for header and error + * code, plus the first character is the start character + */ + if (bytes_read >= 7 && '*' == buffer[0]) { + /* Extract the payload size */ + size = xds110_get_u16(&buffer[1]); + /* Sanity test on payload size */ + if (USB_PAYLOAD_SIZE >= size && 4 <= size) { + /* Check we didn't get more data than expected */ + if ((bytes_read - 3) <= size) { + /* Packet appears to be valid, move on */ + break; + } + } + } + } + /* + * Somehow received an invalid packet, retry till we + * time out or a valid response packet is received + */ + } + + /* Abort now if we didn't receive a valid response */ + if (!success) { + if (NULL != total_bytes_read) + *total_bytes_read = 0; + return false; + } + + /* Build the return payload into xds110.read_payload */ + + /* Copy over payload data from received buffer (skipping header) */ + count = 0; + bytes_read -= 3; + memcpy((void *)&xds110.read_payload[count], (void *)&buffer[3], bytes_read); + count += bytes_read; + /* + * Drop timeout to just 1/2 second. Once the XDS110 starts sending + * a response, the remaining packets should arrive in short order + */ + if (timeout > 500) + timeout = 500; /* ms */ + + /* If there's more data to retrieve, get it now */ + while ((count < size) && success) { + success = usb_read(buffer, sizeof(buffer), &bytes_read, timeout); + if (success) { + if ((count + bytes_read) > size) { + /* Read too much data, not a valid packet, abort */ + success = false; + } else { + /* Copy this data over to xds110.read_payload */ + memcpy((void *)&xds110.read_payload[count], (void *)buffer, + bytes_read); + count += bytes_read; + } + } + } + + if (!success) + count = 0; + if (NULL != total_bytes_read) + *total_bytes_read = count; + + return success; +} + +static bool usb_send_command(uint16_t size) +{ + int written; + bool success = true; + + /* Check the packet length */ + if (size > USB_PAYLOAD_SIZE) + return false; + + /* Place the start character into the packet buffer */ + xds110.write_packet[0] = '*'; + + /* Place the payload size into the packet buffer */ + xds110_set_u16(&xds110.write_packet[1], size); + + /* Adjust size to include header */ + size += 3; + + /* Send the data via the USB connection */ + success = usb_write(xds110.write_packet, (int)size, &written); + + /* Check if the correct number of bytes was written */ + if (written != (int)size) + success = false; + + return success; +} + +/*************************************************************************** + * XDS110 firmware API routines * + * * + * The following functions handle calling into the XDS110 firmware to * + * perform requested debug actions. * + ***************************************************************************/ + +static bool xds_execute(uint32_t out_length, uint32_t in_length, + uint32_t attempts, uint32_t timeout) +{ + bool done = false; + bool success = true; + int error = 0; + uint32_t bytes_read = 0; + + if (NULL == xds110.dev) + return false; + + while (!done && attempts > 0) { + attempts--; + + /* Send command to XDS110 */ + success = usb_send_command(out_length); + + if (success) { + /* Get response from XDS110 */ + success = usb_get_response(&bytes_read, timeout); + } + + if (success) { + /* Check for valid response from XDS code handling */ + if (bytes_read != in_length) { + /* Unexpected amount of data returned */ + success = false; + } else { + /* Extract error code from return packet */ + error = (int)xds110_get_u32(&xds110.read_payload[0]); + done = true; + } + } + } + + if (!success) + error = SC_ERR_XDS110_FAIL; + + if (0 != error) + success = false; + + return success; +} + +static bool xds_connect(void) +{ + bool success; + + xds110.write_payload[0] = XDS_CONNECT; + + success = xds_execute(XDS_OUT_LEN, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool xds_disconnect(void) +{ + bool success; + + xds110.write_payload[0] = XDS_DISCONNECT; + + success = xds_execute(XDS_OUT_LEN, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool xds_version(uint32_t *firmware_id, uint16_t *hardware_id) +{ + uint8_t *fw_id_pntr = &xds110.read_payload[XDS_IN_LEN + 0]; /* 32-bits */ + uint8_t *hw_id_pntr = &xds110.read_payload[XDS_IN_LEN + 4]; /* 16-bits */ + + bool success; + + xds110.write_payload[0] = XDS_VERSION; + + success = xds_execute(XDS_OUT_LEN, XDS_IN_LEN + 6, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + if (success) { + if (NULL != firmware_id) + *firmware_id = xds110_get_u32(fw_id_pntr); + if (NULL != hardware_id) + *hardware_id = xds110_get_u16(hw_id_pntr); + } + + return success; +} + +static bool xds_set_tck_delay(uint32_t delay) +{ + uint8_t *delay_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; /* 32-bits */ + + bool success; + + xds110.write_payload[0] = XDS_SET_TCK; + + xds110_set_u32(delay_pntr, delay); + + success = xds_execute(XDS_OUT_LEN + 4, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool xds_set_trst(uint8_t trst) +{ + uint8_t *trst_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; /* 8-bits */ + + bool success; + + xds110.write_payload[0] = XDS_SET_TRST; + + *trst_pntr = trst; + + success = xds_execute(XDS_OUT_LEN + 1, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool xds_cycle_tck(uint32_t count) +{ + uint8_t *count_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; /* 32-bits */ + + bool success; + + xds110.write_payload[0] = XDS_CYCLE_TCK; + + xds110_set_u32(count_pntr, count); + + success = xds_execute(XDS_OUT_LEN + 4, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool xds_goto_state(uint32_t state) +{ + uint8_t *state_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; /* 32-bits */ + uint8_t *transit_pntr = &xds110.write_payload[XDS_OUT_LEN+4]; /* 32-bits */ + + bool success; + + xds110.write_payload[0] = XDS_GOTO_STATE; + + xds110_set_u32(state_pntr, state); + xds110_set_u32(transit_pntr, XDS_JTAG_TRANSIT_QUICKEST); + + success = xds_execute(XDS_OUT_LEN+8, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool xds_jtag_scan(uint32_t shift_state, uint16_t shift_bits, + uint32_t end_state, uint8_t *data_out, uint8_t *data_in) +{ + uint8_t *bits_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; /* 16-bits */ + uint8_t *path_pntr = &xds110.write_payload[XDS_OUT_LEN + 2]; /* 8-bits */ + uint8_t *trans1_pntr = &xds110.write_payload[XDS_OUT_LEN + 3]; /* 8-bits */ + uint8_t *end_pntr = &xds110.write_payload[XDS_OUT_LEN + 4]; /* 8-bits */ + uint8_t *trans2_pntr = &xds110.write_payload[XDS_OUT_LEN + 5]; /* 8-bits */ + uint8_t *pre_pntr = &xds110.write_payload[XDS_OUT_LEN + 6]; /* 16-bits */ + uint8_t *pos_pntr = &xds110.write_payload[XDS_OUT_LEN + 8]; /* 16-bits */ + uint8_t *delay_pntr = &xds110.write_payload[XDS_OUT_LEN + 10]; /* 16-bits */ + uint8_t *rep_pntr = &xds110.write_payload[XDS_OUT_LEN + 12]; /* 16-bits */ + uint8_t *out_pntr = &xds110.write_payload[XDS_OUT_LEN + 14]; /* 16-bits */ + uint8_t *in_pntr = &xds110.write_payload[XDS_OUT_LEN + 16]; /* 16-bits */ + uint8_t *data_out_pntr = &xds110.write_payload[XDS_OUT_LEN + 18]; + uint8_t *data_in_pntr = &xds110.read_payload[XDS_IN_LEN+0]; + + uint16_t total_bytes = DIV_ROUND_UP(shift_bits, 8); + + bool success; + + xds110.write_payload[0] = XDS_JTAG_SCAN; + + xds110_set_u16(bits_pntr, shift_bits); /* bits to scan */ + *path_pntr = (uint8_t)(shift_state & 0xff); /* IR vs DR path */ + *trans1_pntr = (uint8_t)XDS_JTAG_TRANSIT_QUICKEST; /* start state route */ + *end_pntr = (uint8_t)(end_state & 0xff); /* JTAG state after scan */ + *trans2_pntr = (uint8_t)XDS_JTAG_TRANSIT_QUICKEST; /* end state route */ + xds110_set_u16(pre_pntr, 0); /* number of preamble bits */ + xds110_set_u16(pos_pntr, 0); /* number of postamble bits */ + xds110_set_u16(delay_pntr, 0); /* number of extra TCKs after scan */ + xds110_set_u16(rep_pntr, 1); /* number of repetitions */ + xds110_set_u16(out_pntr, total_bytes); /* out buffer offset (if repeats) */ + xds110_set_u16(in_pntr, total_bytes); /* in buffer offset (if repeats) */ + + memcpy((void *)data_out_pntr, (void *)data_out, total_bytes); + + success = xds_execute(XDS_OUT_LEN + 18 + total_bytes, + XDS_IN_LEN + total_bytes, DEFAULT_ATTEMPTS, DEFAULT_TIMEOUT); + + if (success) + memcpy((void *)data_in, (void *)data_in_pntr, total_bytes); + + return success; +} + +static bool xds_set_srst(uint8_t srst) +{ + uint8_t *srst_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; /* 8-bits */ + + bool success; + + xds110.write_payload[0] = XDS_SET_SRST; + + *srst_pntr = srst; + + success = xds_execute(XDS_OUT_LEN + 1, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool cmapi_connect(uint32_t *idcode) +{ + uint8_t *idcode_pntr = &xds110.read_payload[XDS_IN_LEN + 0]; /* 32-bits */ + + bool success; + + xds110.write_payload[0] = CMAPI_CONNECT; + + success = xds_execute(XDS_OUT_LEN, XDS_IN_LEN+4, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + if (success) { + if (NULL != idcode) + *idcode = xds110_get_u32(idcode_pntr); + } + + return success; +} + +static bool cmapi_disconnect(void) +{ + bool success; + + xds110.write_payload[0] = CMAPI_DISCONNECT; + + success = xds_execute(XDS_OUT_LEN, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool cmapi_acquire(void) +{ + bool success; + + xds110.write_payload[0] = CMAPI_ACQUIRE; + + success = xds_execute(XDS_OUT_LEN, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool cmapi_release(void) +{ + bool success; + + xds110.write_payload[0] = CMAPI_RELEASE; + + success = xds_execute(XDS_OUT_LEN, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool cmapi_read_dap_reg(uint32_t type, uint32_t ap_num, + uint32_t address, uint32_t *value) +{ + uint8_t *type_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; /* 8-bits */ + uint8_t *ap_num_pntr = &xds110.write_payload[XDS_OUT_LEN + 1]; /* 8-bits */ + uint8_t *address_pntr = &xds110.write_payload[XDS_OUT_LEN + 2]; /* 8-bits */ + uint8_t *value_pntr = &xds110.read_payload[XDS_IN_LEN + 0]; /* 32-bits */ + + bool success; + + xds110.write_payload[0] = CMAPI_REG_READ; + + *type_pntr = (uint8_t)(type & 0xff); + *ap_num_pntr = (uint8_t)(ap_num & 0xff); + *address_pntr = (uint8_t)(address & 0xff); + + success = xds_execute(XDS_OUT_LEN + 3, XDS_IN_LEN + 4, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + if (success) { + if (NULL != value) + *value = xds110_get_u32(value_pntr); + } + + return success; +} + +static bool cmapi_write_dap_reg(uint32_t type, uint32_t ap_num, + uint32_t address, uint32_t *value) +{ + uint8_t *type_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; /* 8-bits */ + uint8_t *ap_num_pntr = &xds110.write_payload[XDS_OUT_LEN + 1]; /* 8-bits */ + uint8_t *address_pntr = &xds110.write_payload[XDS_OUT_LEN + 2]; /* 8-bits */ + uint8_t *value_pntr = &xds110.write_payload[XDS_OUT_LEN + 3]; /* 32-bits */ + + bool success; + + if (NULL == value) + return false; + + xds110.write_payload[0] = CMAPI_REG_WRITE; + + *type_pntr = (uint8_t)(type & 0xff); + *ap_num_pntr = (uint8_t)(ap_num & 0xff); + *address_pntr = (uint8_t)(address & 0xff); + xds110_set_u32(value_pntr, *value); + + success = xds_execute(XDS_OUT_LEN + 7, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool swd_connect(void) +{ + bool success; + + xds110.write_payload[0] = SWD_CONNECT; + + success = xds_execute(XDS_OUT_LEN, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool swd_disconnect(void) +{ + bool success; + + xds110.write_payload[0] = SWD_DISCONNECT; + + success = xds_execute(XDS_OUT_LEN, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool cjtag_connect(uint32_t format) +{ + uint8_t *format_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; /* 32-bits */ + + bool success; + + xds110.write_payload[0] = CJTAG_CONNECT; + + xds110_set_u32(format_pntr, format); + + success = xds_execute(XDS_OUT_LEN + 4, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool cjtag_disconnect(void) +{ + bool success; + + xds110.write_payload[0] = CJTAG_DISCONNECT; + + success = xds_execute(XDS_OUT_LEN, XDS_IN_LEN, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + return success; +} + +static bool ocd_dap_request(uint8_t *dap_requests, uint32_t request_size, + uint32_t *dap_results, uint32_t result_count) +{ + uint8_t *request_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; + uint8_t *result_pntr = &xds110.read_payload[XDS_IN_LEN + 0]; + + bool success; + + if (NULL == dap_requests || NULL == dap_results) + return false; + + xds110.write_payload[0] = OCD_DAP_REQUEST; + + memcpy((void *)request_pntr, (void *)dap_requests, request_size); + + success = xds_execute(XDS_OUT_LEN + request_size, + XDS_IN_LEN + (result_count * 4), DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + if (success && (result_count > 0)) + memcpy((void *)dap_results, (void *)result_pntr, result_count * 4); + + return success; +} + +static bool ocd_scan_request(uint8_t *scan_requests, uint32_t request_size, + uint8_t *scan_results, uint32_t result_size) +{ + uint8_t *request_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; + uint8_t *result_pntr = &xds110.read_payload[XDS_IN_LEN + 0]; + + bool success; + + if (NULL == scan_requests || NULL == scan_results) + return false; + + xds110.write_payload[0] = OCD_SCAN_REQUEST; + + memcpy((void *)request_pntr, (void *)scan_requests, request_size); + + success = xds_execute(XDS_OUT_LEN + request_size, + XDS_IN_LEN + result_size, DEFAULT_ATTEMPTS, + DEFAULT_TIMEOUT); + + if (success && (result_size > 0)) + memcpy((void *)scan_results, (void *)result_pntr, result_size); + + return success; +} + +static bool ocd_pathmove(uint32_t num_states, uint8_t *path) +{ + uint8_t *num_pntr = &xds110.write_payload[XDS_OUT_LEN + 0]; /* 32-bits */ + uint8_t *path_pntr = &xds110.write_payload[XDS_OUT_LEN + 4]; + + bool success; + + if (NULL == path) + return false; + + xds110.write_payload[0] = OCD_PATHMOVE; + + xds110_set_u32(num_pntr, num_states); + + memcpy((void *)path_pntr, (void *)path, num_states); + + success = xds_execute(XDS_OUT_LEN + 4 + num_states, XDS_IN_LEN, + DEFAULT_ATTEMPTS, DEFAULT_TIMEOUT); + + return success; +} + +/*************************************************************************** + * swd driver interface * + * * + * The following functions provide SWD support to OpenOCD. * + ***************************************************************************/ + +static int xds110_swd_init(void) +{ + xds110.is_swd_mode = true; + return ERROR_OK; +} + +static int xds110_swd_switch_seq(enum swd_special_seq seq) +{ + uint32_t idcode; + bool success; + + switch (seq) { + case LINE_RESET: + LOG_ERROR("Sequence SWD line reset (%d) not supported", seq); + return ERROR_FAIL; + case JTAG_TO_SWD: + LOG_DEBUG("JTAG-to-SWD"); + xds110.is_swd_mode = false; + xds110.is_cmapi_connected = false; + xds110.is_cmapi_acquired = false; + /* Run sequence to put target in SWD mode */ + success = swd_connect(); + /* Re-iniitialize CMAPI API for DAP access */ + if (success) { + xds110.is_swd_mode = true; + success = cmapi_connect(&idcode); + if (success) { + xds110.is_cmapi_connected = true; + success = cmapi_acquire(); + } + } + break; + case SWD_TO_JTAG: + LOG_DEBUG("SWD-to-JTAG"); + xds110.is_swd_mode = false; + xds110.is_cmapi_connected = false; + xds110.is_cmapi_acquired = false; + /* Run sequence to put target in JTAG mode */ + success = swd_disconnect(); + if (success) { + /* Re-initialize JTAG interface */ + success = cjtag_connect(MODE_JTAG); + } + break; + default: + LOG_ERROR("Sequence %d not supported", seq); + return ERROR_FAIL; + } + + if (success) + return ERROR_OK; + else + return ERROR_FAIL; +} + +static bool xds110_legacy_read_reg(uint8_t cmd, uint32_t *value) +{ + /* Make sure this is a read request */ + bool is_read_request = (0 != (SWD_CMD_RnW & cmd)); + /* Determine whether this is a DP or AP register access */ + uint32_t type = (0 != (SWD_CMD_APnDP & cmd)) ? DAP_AP : DAP_DP; + /* Determine the AP number from cached SELECT value */ + uint32_t ap_num = (xds110.select & 0xff000000) >> 24; + /* Extract register address from command */ + uint32_t address = ((cmd & SWD_CMD_A32) >> 1); + /* Extract bank address from cached SELECT value */ + uint32_t bank = (xds110.select & 0x000000f0); + + uint32_t reg_value = 0; + uint32_t temp_value = 0; + + bool success; + + if (!is_read_request) + return false; + + if (DAP_AP == type) { + /* Add bank address to register address for CMAPI call */ + address |= bank; + } + + if (DAP_DP == type && DAP_DP_RDBUFF == address && xds110.use_rdbuff) { + /* If RDBUFF is cached and this is a DP RDBUFF read, use the cache */ + reg_value = xds110.rdbuff; + success = true; + } else if (DAP_AP == type && DAP_AP_DRW == address && xds110.use_rdbuff) { + /* If RDBUFF is cached and this is an AP DRW read, use the cache, */ + /* but still call into the firmware to get the next read. */ + reg_value = xds110.rdbuff; + success = cmapi_read_dap_reg(type, ap_num, address, &temp_value); + } else { + success = cmapi_read_dap_reg(type, ap_num, address, &temp_value); + if (success) + reg_value = temp_value; + } + + /* Mark that we have consumed or invalidated the RDBUFF cache */ + xds110.use_rdbuff = false; + + /* Handle result of read attempt */ + if (!success) + LOG_ERROR("XDS110: failed to read DAP register"); + else if (NULL != value) + *value = reg_value; + + if (success && DAP_AP == type) { + /* + * On a successful DAP AP read, we actually have the value from RDBUFF, + * the firmware will have run the AP request and made the RDBUFF read + */ + xds110.use_rdbuff = true; + xds110.rdbuff = temp_value; + } + + return success; +} + +static bool xds110_legacy_write_reg(uint8_t cmd, uint32_t value) +{ + /* Make sure this isn't a read request */ + bool is_read_request = (0 != (SWD_CMD_RnW & cmd)); + /* Determine whether this is a DP or AP register access */ + uint32_t type = (0 != (SWD_CMD_APnDP & cmd)) ? DAP_AP : DAP_DP; + /* Determine the AP number from cached SELECT value */ + uint32_t ap_num = (xds110.select & 0xff000000) >> 24; + /* Extract register address from command */ + uint32_t address = ((cmd & SWD_CMD_A32) >> 1); + /* Extract bank address from cached SELECT value */ + uint32_t bank = (xds110.select & 0x000000f0); + + bool success; + + if (is_read_request) + return false; + + /* Invalidate the RDBUFF cache */ + xds110.use_rdbuff = false; + + if (DAP_AP == type) { + /* Add bank address to register address for CMAPI call */ + address |= bank; + /* Any write to an AP register invalidates the firmware's cache */ + xds110.is_ap_dirty = true; + } else if (DAP_DP_SELECT == address) { + /* Any write to the SELECT register invalidates the firmware's cache */ + xds110.is_ap_dirty = true; + } + + success = cmapi_write_dap_reg(type, ap_num, address, &value); + + if (!success) { + LOG_ERROR("XDS110: failed to write DAP register"); + } else { + /* + * If the debugger wrote to SELECT, cache the value + * to use to build the apNum and address values above + */ + if ((DAP_DP == type) && (DAP_DP_SELECT == address)) + xds110.select = value; + } + + return success; +} + +static int xds110_swd_run_queue(void) +{ + static uint32_t dap_results[MAX_RESULT_QUEUE]; + uint8_t cmd; + uint32_t request; + uint32_t result; + uint32_t value; + bool success = true; + + if (0 == xds110.txn_request_size) + return ERROR_OK; + + /* Terminate request queue */ + xds110.txn_requests[xds110.txn_request_size++] = 0; + + if (xds110.firmware >= OCD_FIRMWARE_VERSION) { + /* XDS110 firmware has the API to directly handle the queue */ + success = ocd_dap_request(xds110.txn_requests, + xds110.txn_request_size, dap_results, xds110.txn_result_count); + } else { + /* Legacy firmware needs to handle queue via discrete DAP calls */ + request = 0; + result = 0; + while (xds110.txn_requests[request] != 0) { + cmd = xds110.txn_requests[request++]; + if (0 == (SWD_CMD_RnW & cmd)) { + /* DAP register write command */ + value = (uint32_t)(xds110.txn_requests[request++]) << 0; + value |= (uint32_t)(xds110.txn_requests[request++]) << 8; + value |= (uint32_t)(xds110.txn_requests[request++]) << 16; + value |= (uint32_t)(xds110.txn_requests[request++]) << 24; + if (success) + success = xds110_legacy_write_reg(cmd, value); + } else { + /* DAP register read command */ + value = 0; + if (success) + success = xds110_legacy_read_reg(cmd, &value); + dap_results[result++] = value; + } + } + } + + /* Transfer results into caller's buffers */ + for (result = 0; result < xds110.txn_result_count; result++) + if (0 != xds110.txn_dap_results[result]) + *xds110.txn_dap_results[result] = dap_results[result]; + + xds110.txn_request_size = 0; + xds110.txn_result_size = 0; + xds110.txn_result_count = 0; + + return (success) ? ERROR_OK : ERROR_FAIL; +} + +static void xds110_swd_queue_cmd(uint8_t cmd, uint32_t *value) +{ + /* Check if this is a read or write request */ + bool is_read_request = (0 != (SWD_CMD_RnW & cmd)); + /* Determine whether this is a DP or AP register access */ + uint32_t type = (0 != (SWD_CMD_APnDP & cmd)) ? DAP_AP : DAP_DP; + /* Extract register address from command */ + uint32_t address = ((cmd & SWD_CMD_A32) >> 1); + uint32_t request_size = (is_read_request) ? 1 : 5; + + /* Check if new request would be too large to fit */ + if (((xds110.txn_request_size + request_size + 1) > MAX_DATA_BLOCK) || + ((xds110.txn_result_count + 1) > MAX_RESULT_QUEUE)) + xds110_swd_run_queue(); + + /* Set the START bit in cmd to ensure cmd is not zero */ + /* (a value of zero is used to terminate the buffer) */ + cmd |= SWD_CMD_START; + + /* Add request to queue; queue is built marshalled for XDS110 call */ + if (is_read_request) { + /* Queue read request, save pointer to pass back result */ + xds110.txn_requests[xds110.txn_request_size++] = cmd; + xds110.txn_dap_results[xds110.txn_result_count++] = value; + xds110.txn_result_size += 4; + } else { + /* Check for and prevent sticky overrun detection */ + if (DAP_DP == type && DAP_DP_CTRL == address && + (*value & CORUNDETECT)) { + LOG_DEBUG("XDS110: refusing to enable sticky overrun detection"); + *value &= ~CORUNDETECT; + } + /* Queue write request, add value directly to queue buffer */ + xds110.txn_requests[xds110.txn_request_size++] = cmd; + xds110.txn_requests[xds110.txn_request_size++] = (*value >> 0) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = (*value >> 8) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = (*value >> 16) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = (*value >> 24) & 0xff; + } +} + +static void xds110_swd_read_reg(uint8_t cmd, uint32_t *value, + uint32_t ap_delay_clk) +{ + xds110_swd_queue_cmd(cmd, value); +} +static void xds110_swd_write_reg(uint8_t cmd, uint32_t value, + uint32_t ap_delay_clk) +{ + xds110_swd_queue_cmd(cmd, &value); +} + +/*************************************************************************** + * jtag interface * + * * + * The following functions provide XDS110 interface to OpenOCD. * + ***************************************************************************/ + +static void xds110_show_info(void) +{ + uint32_t firmware = xds110.firmware; + + LOG_INFO("XDS110: firmware version = %d.%d.%d.%d", + (((firmware >> 28) & 0xf) * 10) + ((firmware >> 24) & 0xf), + (((firmware >> 20) & 0xf) * 10) + ((firmware >> 16) & 0xf), + (((firmware >> 12) & 0xf) * 10) + ((firmware >> 8) & 0xf), + (((firmware >> 4) & 0xf) * 10) + ((firmware >> 0) & 0xf)); + LOG_INFO("XDS110: hardware version = 0x%04x", xds110.hardware); + if (0 != xds110.serial[0]) + LOG_INFO("XDS110: serial number = %s)", xds110.serial); + if (xds110.is_swd_mode) { + LOG_INFO("XDS110: connected to target via SWD"); + LOG_INFO("XDS110: SWCLK set to %d kHz", xds110.speed); + } else { + LOG_INFO("XDS110: connected to target via JTAG"); + LOG_INFO("XDS110: TCK set to %d kHz", xds110.speed); + } + + /* Alert user that there's a better firmware to use */ + if (firmware < OCD_FIRMWARE_VERSION) { + LOG_WARNING("XDS110: the firmware is not optimized for OpenOCD"); + LOG_WARNING(OCD_FIRMWARE_UPGRADE); + } +} + +static int xds110_quit(void) +{ + if (xds110.is_cmapi_acquired) { + (void)cmapi_release(); + xds110.is_cmapi_acquired = false; + } + if (xds110.is_cmapi_connected) { + (void)cmapi_disconnect(); + xds110.is_cmapi_connected = false; + } + if (xds110.is_connected) { + if (xds110.is_swd_mode) { + /* Switch out of SWD mode */ + (void)swd_disconnect(); + } else { + /* Switch out of cJTAG mode */ + (void)cjtag_disconnect(); + } + /* Tell firmware we're disconnecting */ + (void)xds_disconnect(); + xds110.is_connected = false; + } + /* Close down the USB connection to the XDS110 debug probe */ + usb_disconnect(); + + return ERROR_OK; +} + +static int xds110_init(void) +{ + bool success; + + /* Establish USB connection to the XDS110 debug probe */ + success = usb_connect(); + + if (success) { + /* Send connect message to XDS110 firmware */ + success = xds_connect(); + if (success) + xds110.is_connected = true; + } + + if (success) { + uint32_t firmware; + uint16_t hardware; + + /* Retrieve version IDs from firmware */ + /* Version numbers are stored in BCD format */ + success = xds_version(&firmware, &hardware); + if (success) { + /* Save the firmware and hardware version */ + xds110.firmware = firmware; + xds110.hardware = hardware; + } + } + + if (success) { + success = xds_set_trst(0); + if (success) + success = xds_cycle_tck(50); + if (success) + success = xds_set_trst(1); + if (success) + success = xds_cycle_tck(50); + } + + if (success) { + if (xds110.is_swd_mode) { + /* Switch to SWD if needed */ + success = swd_connect(); + } else { + success = cjtag_connect(MODE_JTAG); + } + } + + if (success && xds110.is_swd_mode) { + uint32_t idcode; + + /* Connect to CMAPI interface in XDS110 */ + success = cmapi_connect(&idcode); + + /* Acquire exclusive access to CMAPI interface */ + if (success) { + xds110.is_cmapi_connected = true; + success = cmapi_acquire(); + if (success) + xds110.is_cmapi_acquired = true; + } + } + + if (!success) + xds110_quit(); + + if (success) + xds110_show_info(); + + return (success) ? ERROR_OK : ERROR_FAIL; +} + +static void xds110_legacy_scan(uint32_t shift_state, uint32_t total_bits, + uint32_t end_state, uint8_t *data_out, uint8_t *data_in) +{ + (void)xds_jtag_scan(shift_state, total_bits, end_state, data_out, data_in); +} + +static void xds110_legacy_runtest(uint32_t clocks, uint32_t end_state) +{ + xds_goto_state(XDS_JTAG_STATE_IDLE); + xds_cycle_tck(clocks); + xds_goto_state(end_state); +} + +static void xds110_legacy_stableclocks(uint32_t clocks) +{ + xds_cycle_tck(clocks); +} + +static void xds110_flush(void) +{ + uint8_t command; + uint32_t clocks; + uint32_t shift_state; + uint32_t end_state; + uint32_t bits; + uint32_t bytes; + uint32_t request; + uint32_t result; + uint8_t *data_out; + uint8_t data_in[MAX_DATA_BLOCK]; + uint8_t *data_pntr; + + if (0 == xds110.txn_request_size) + return; + + /* Terminate request queue */ + xds110.txn_requests[xds110.txn_request_size++] = 0; + + if (xds110.firmware >= OCD_FIRMWARE_VERSION) { + /* Updated firmware has the API to directly handle the queue */ + (void)ocd_scan_request(xds110.txn_requests, xds110.txn_request_size, + data_in, xds110.txn_result_size); + } else { + /* Legacy firmware needs to handle queue via discrete JTAG calls */ + request = 0; + result = 0; + while (xds110.txn_requests[request] != 0) { + command = xds110.txn_requests[request++]; + switch (command) { + case CMD_IR_SCAN: + case CMD_DR_SCAN: + if (command == CMD_IR_SCAN) + shift_state = XDS_JTAG_STATE_SHIFT_IR; + else + shift_state = XDS_JTAG_STATE_SHIFT_DR; + end_state = (uint32_t)(xds110.txn_requests[request++]); + bits = (uint32_t)(xds110.txn_requests[request++]) << 0; + bits |= (uint32_t)(xds110.txn_requests[request++]) << 8; + data_out = &xds110.txn_requests[request]; + bytes = DIV_ROUND_UP(bits, 8); + xds110_legacy_scan(shift_state, bits, end_state, data_out, + &data_in[result]); + result += bytes; + request += bytes; + break; + case CMD_RUNTEST: + clocks = (uint32_t)(xds110.txn_requests[request++]) << 0; + clocks |= (uint32_t)(xds110.txn_requests[request++]) << 8; + clocks |= (uint32_t)(xds110.txn_requests[request++]) << 16; + clocks |= (uint32_t)(xds110.txn_requests[request++]) << 24; + end_state = (uint32_t)xds110.txn_requests[request++]; + xds110_legacy_runtest(clocks, end_state); + break; + case CMD_STABLECLOCKS: + clocks = (uint32_t)(xds110.txn_requests[request++]) << 0; + clocks |= (uint32_t)(xds110.txn_requests[request++]) << 8; + clocks |= (uint32_t)(xds110.txn_requests[request++]) << 16; + clocks |= (uint32_t)(xds110.txn_requests[request++]) << 24; + xds110_legacy_stableclocks(clocks); + break; + default: + LOG_ERROR("BUG: unknown JTAG command type 0x%x encountered", + command); + exit(-1); + break; + } + } + } + + /* Transfer results into caller's buffers from data_in buffer */ + bits = 0; /* Bit offset into current scan result */ + data_pntr = data_in; + for (result = 0; result < xds110.txn_result_count; result++) { + if (xds110.txn_scan_results[result].first) { + if (bits != 0) { + bytes = DIV_ROUND_UP(bits, 8); + data_pntr += bytes; + } + bits = 0; + } + if (xds110.txn_scan_results[result].buffer != 0) + bit_copy(xds110.txn_scan_results[result].buffer, 0, data_pntr, + bits, xds110.txn_scan_results[result].num_bits); + bits += xds110.txn_scan_results[result].num_bits; + } + + xds110.txn_request_size = 0; + xds110.txn_result_size = 0; + xds110.txn_result_count = 0; +} + +static void xds110_execute_reset(struct jtag_command *cmd) +{ + char trst; + char srst; + + if (cmd->cmd.reset->trst != -1) { + if (cmd->cmd.reset->trst == 0) { + /* Deassert nTRST (active low) */ + trst = 1; + } else { + /* Assert nTRST (active low) */ + trst = 0; + } + (void)xds_set_trst(trst); + } + + if (cmd->cmd.reset->srst != -1) { + if (cmd->cmd.reset->srst == 0) { + /* Deassert nSRST (active low) */ + srst = 1; + } else { + /* Assert nSRST (active low) */ + srst = 0; + } + (void)xds_set_srst(srst); + } +} + +static void xds110_execute_sleep(struct jtag_command *cmd) +{ + jtag_sleep(cmd->cmd.sleep->us); + return; +} + +static void xds110_execute_tlr_reset(struct jtag_command *cmd) +{ + (void)xds_goto_state(XDS_JTAG_STATE_RESET); + + return; +} + +static void xds110_execute_pathmove(struct jtag_command *cmd) +{ + uint32_t i; + uint32_t num_states; + uint8_t *path; + + num_states = (uint32_t)cmd->cmd.pathmove->num_states; + + if (num_states == 0) + return; + + path = (uint8_t *)malloc(num_states * sizeof(uint8_t)); + if (path == 0) { + LOG_ERROR("XDS110: unable to allocate memory"); + return; + } + + /* Convert requested path states into XDS API states */ + for (i = 0; i < num_states; i++) + path[i] = (uint8_t)xds_jtag_state[cmd->cmd.pathmove->path[i]]; + + if (xds110.firmware >= OCD_FIRMWARE_VERSION) { + /* Updated firmware fully supports pathmove */ + (void)ocd_pathmove(num_states, path); + } else { + /* Notify user that legacy firmware simply cannot handle pathmove */ + LOG_ERROR("XDS110: the firmware does not support pathmove command"); + LOG_ERROR(OCD_FIRMWARE_UPGRADE); + /* If pathmove is required, then debug is not possible */ + exit(-1); + } + + free((void *)path); + + return; +} + +static void xds110_queue_scan(struct jtag_command *cmd) +{ + int i; + uint32_t offset; + uint32_t total_fields; + uint32_t total_bits; + uint32_t total_bytes; + uint8_t end_state; + uint8_t *buffer; + + /* Calculate the total number of bits to scan */ + total_bits = 0; + total_fields = 0; + for (i = 0; i < cmd->cmd.scan->num_fields; i++) { + total_fields++; + total_bits += (uint32_t)cmd->cmd.scan->fields[i].num_bits; + } + + if (total_bits == 0) + return; + + total_bytes = DIV_ROUND_UP(total_bits, 8); + + /* Check if new request would be too large to fit */ + if (((xds110.txn_request_size + 1 + total_bytes + sizeof(end_state) + 1) + > MAX_DATA_BLOCK) || ((xds110.txn_result_count + total_fields) > + MAX_RESULT_QUEUE)) + xds110_flush(); + + /* Check if this single request is too large to fit */ + if ((1 + total_bytes + sizeof(end_state) + 1) > MAX_DATA_BLOCK) { + LOG_ERROR("BUG: JTAG scan request is too large to handle (%d bits)", + total_bits); + /* Failing to run this scan mucks up debug on this target */ + exit(-1); + } + + if (cmd->cmd.scan->ir_scan) + xds110.txn_requests[xds110.txn_request_size++] = CMD_IR_SCAN; + else + xds110.txn_requests[xds110.txn_request_size++] = CMD_DR_SCAN; + + end_state = (uint8_t)xds_jtag_state[cmd->cmd.scan->end_state]; + xds110.txn_requests[xds110.txn_request_size++] = end_state; + + xds110.txn_requests[xds110.txn_request_size++] = (total_bits >> 0) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = (total_bits >> 8) & 0xff; + + /* Build request data by flattening fields into single buffer */ + /* also populate the results array to return the results when run */ + offset = 0; + buffer = &xds110.txn_requests[xds110.txn_request_size]; + /* Clear data out buffer to default value of all zeros */ + memset((void *)buffer, 0x00, total_bytes); + for (i = 0; i < cmd->cmd.scan->num_fields; i++) { + if (cmd->cmd.scan->fields[i].out_value != 0) { + /* Copy over data to scan out into request buffer */ + bit_copy(buffer, offset, cmd->cmd.scan->fields[i].out_value, 0, + cmd->cmd.scan->fields[i].num_bits); + } + offset += cmd->cmd.scan->fields[i].num_bits; + xds110.txn_scan_results[xds110.txn_result_count].first = (i == 0); + xds110.txn_scan_results[xds110.txn_result_count].num_bits = + cmd->cmd.scan->fields[i].num_bits; + xds110.txn_scan_results[xds110.txn_result_count++].buffer = + cmd->cmd.scan->fields[i].in_value; + } + xds110.txn_request_size += total_bytes; + xds110.txn_result_size += total_bytes; + + return; +} + +static void xds110_queue_runtest(struct jtag_command *cmd) +{ + uint32_t clocks = (uint32_t)cmd->cmd.stableclocks->num_cycles; + uint8_t end_state = (uint8_t)xds_jtag_state[cmd->cmd.runtest->end_state]; + + /* Check if new request would be too large to fit */ + if ((xds110.txn_request_size + 1 + sizeof(clocks) + sizeof(end_state) + 1) + > MAX_DATA_BLOCK) + xds110_flush(); + + /* Queue request and cycle count directly to queue buffer */ + xds110.txn_requests[xds110.txn_request_size++] = CMD_RUNTEST; + xds110.txn_requests[xds110.txn_request_size++] = (clocks >> 0) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = (clocks >> 8) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = (clocks >> 16) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = (clocks >> 24) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = end_state; + + return; +} + +static void xds110_queue_stableclocks(struct jtag_command *cmd) +{ + uint32_t clocks = (uint32_t)cmd->cmd.stableclocks->num_cycles; + + /* Check if new request would be too large to fit */ + if ((xds110.txn_request_size + 1 + sizeof(clocks) + 1) > MAX_DATA_BLOCK) + xds110_flush(); + + /* Queue request and cycle count directly to queue buffer */ + xds110.txn_requests[xds110.txn_request_size++] = CMD_STABLECLOCKS; + xds110.txn_requests[xds110.txn_request_size++] = (clocks >> 0) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = (clocks >> 8) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = (clocks >> 16) & 0xff; + xds110.txn_requests[xds110.txn_request_size++] = (clocks >> 24) & 0xff; + + return; +} + +static void xds110_execute_command(struct jtag_command *cmd) +{ + switch (cmd->type) { + case JTAG_RESET: + xds110_flush(); + xds110_execute_reset(cmd); + break; + case JTAG_SLEEP: + xds110_flush(); + xds110_execute_sleep(cmd); + break; + case JTAG_TLR_RESET: + xds110_flush(); + xds110_execute_tlr_reset(cmd); + break; + case JTAG_PATHMOVE: + xds110_flush(); + xds110_execute_pathmove(cmd); + break; + case JTAG_SCAN: + xds110_queue_scan(cmd); + break; + case JTAG_RUNTEST: + xds110_queue_runtest(cmd); + break; + case JTAG_STABLECLOCKS: + xds110_queue_stableclocks(cmd); + break; + case JTAG_TMS: + default: + LOG_ERROR("BUG: unknown JTAG command type 0x%x encountered", + cmd->type); + exit(-1); + } +} + +static int xds110_execute_queue(void) +{ + struct jtag_command *cmd = jtag_command_queue; + + while (cmd != NULL) { + xds110_execute_command(cmd); + cmd = cmd->next; + } + + xds110_flush(); + + return ERROR_OK; +} + +static int xds110_speed(int speed) +{ + bool success; + + if (speed == 0) { + LOG_INFO("XDS110: RTCK not supported"); + return ERROR_JTAG_NOT_IMPLEMENTED; + } + + if (speed > XDS110_MAX_TCK_SPEED) { + LOG_INFO("XDS110: reduce speed request: %dkHz to %dkHz maximum", + speed, XDS110_MAX_TCK_SPEED); + speed = XDS110_MAX_TCK_SPEED; + } + + if (speed < XDS110_MIN_TCK_SPEED) { + LOG_INFO("XDS110: increase speed request: %dkHz to %dkHz minimum", + speed, XDS110_MIN_TCK_SPEED); + speed = XDS110_MIN_TCK_SPEED; + } + + /* The default is the maximum frequency the XDS110 can support */ + uint32_t freq_to_use = XDS110_MAX_TCK_SPEED * 1000; /* Hz */ + uint32_t delay_count = 0; + + if (XDS110_MAX_TCK_SPEED != speed) { + freq_to_use = speed * 1000; /* Hz */ + + /* Calculate the delay count value */ + double one_giga = 1000000000; + /* Get the pulse duration for the maximum frequency supported in ns */ + double max_freq_pulse_duration = one_giga / + (XDS110_MAX_TCK_SPEED * 1000); + + /* Convert frequency to pulse duration */ + double freq_to_pulse_width_in_ns = one_giga / freq_to_use; + + /* + * Start with the pulse duration for the maximum frequency. Keep + * decrementing the time added by each count value till the requested + * frequency pulse is less than the calculated value. + */ + double current_value = max_freq_pulse_duration; + + while (current_value < freq_to_pulse_width_in_ns) { + current_value += XDS110_TCK_PULSE_INCREMENT; + ++delay_count; + } + + /* + * Determine which delay count yields the best match. + * The one obtained above or one less. + */ + if (delay_count) { + double diff_freq_1 = freq_to_use - + (one_giga / (max_freq_pulse_duration + + (XDS110_TCK_PULSE_INCREMENT * delay_count))); + double diff_freq_2 = (one_giga / (max_freq_pulse_duration + + (XDS110_TCK_PULSE_INCREMENT * (delay_count - 1)))) - + freq_to_use; + + /* One less count value yields a better match */ + if (diff_freq_1 > diff_freq_2) + --delay_count; + } + } + + /* Send the delay count to the XDS110 firmware */ + success = xds_set_tck_delay(delay_count); + + if (success) { + xds110.delay_count = delay_count; + xds110.speed = speed; + } + + return (success) ? ERROR_OK : ERROR_FAIL; +} + +static int xds110_speed_div(int speed, int *khz) +{ + *khz = speed; + return ERROR_OK; +} + +static int xds110_khz(int khz, int *jtag_speed) +{ + *jtag_speed = khz; + return ERROR_OK; +} + +static int_least32_t xds110_swd_frequency(int_least32_t hz) +{ + if (hz > 0) + xds110_speed(hz / 1000); + return hz; +} + +COMMAND_HANDLER(xds110_handle_info_command) +{ + xds110_show_info(); + return ERROR_OK; +} + +COMMAND_HANDLER(xds110_handle_serial_command) +{ + wchar_t serial[XDS110_SERIAL_LEN + 1]; + + xds110.serial[0] = 0; + + if (CMD_ARGC == 1) { + size_t len = mbstowcs(0, CMD_ARGV[0], 0); + if (len > XDS110_SERIAL_LEN) { + LOG_ERROR("XDS110: serial number is limited to %d characters", + XDS110_SERIAL_LEN); + return ERROR_FAIL; + } + if ((size_t)-1 == mbstowcs(serial, CMD_ARGV[0], len + 1)) { + LOG_ERROR("XDS110: unable to convert serial number"); + return ERROR_FAIL; + } + + for (uint32_t i = 0; i < len; i++) + xds110.serial[i] = (char)serial[i]; + + xds110.serial[len] = 0; + } else { + LOG_ERROR("XDS110: expected exactly one argument to xds110_serial " + "<serial-number>"); + return ERROR_FAIL; + } + + return ERROR_OK; +} + +static const struct command_registration xds110_subcommand_handlers[] = { + { + .name = "info", + .handler = &xds110_handle_info_command, + .mode = COMMAND_EXEC, + .usage = "", + .help = "show XDS110 info", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration xds110_command_handlers[] = { + { + .name = "xds110", + .mode = COMMAND_ANY, + .help = "perform XDS110 management", + .usage = "<cmd>", + .chain = xds110_subcommand_handlers, + }, + { + .name = "xds110_serial", + .handler = &xds110_handle_serial_command, + .mode = COMMAND_CONFIG, + .help = "set the XDS110 probe serial number", + .usage = "serial_string", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct swd_driver xds110_swd_driver = { + .init = xds110_swd_init, + .frequency = xds110_swd_frequency, + .switch_seq = xds110_swd_switch_seq, + .read_reg = xds110_swd_read_reg, + .write_reg = xds110_swd_write_reg, + .run = xds110_swd_run_queue, +}; + +static const char * const xds110_transport[] = { "swd", "jtag", NULL }; + +struct jtag_interface xds110_interface = { + .name = "xds110", + .commands = xds110_command_handlers, + .swd = &xds110_swd_driver, + .transports = xds110_transport, + + .execute_queue = xds110_execute_queue, + .speed = xds110_speed, + .speed_div = xds110_speed_div, + .khz = xds110_khz, + .init = xds110_init, + .quit = xds110_quit, +}; diff --git a/src/jtag/interfaces.c b/src/jtag/interfaces.c index ddeadb4..286a73a 100644 --- a/src/jtag/interfaces.c +++ b/src/jtag/interfaces.c @@ -132,6 +132,9 @@ extern struct jtag_interface kitprog_interface; #if BUILD_IMX_GPIO == 1 extern struct jtag_interface imx_gpio_interface; #endif +#if BUILD_XDS110 == 1 +extern struct jtag_interface xds110_interface; +#endif #endif /* standard drivers */ /** @@ -234,6 +237,9 @@ struct jtag_interface *jtag_interfaces[] = { #if BUILD_IMX_GPIO == 1 &imx_gpio_interface, #endif +#if BUILD_XDS110 == 1 + &xds110_interface, +#endif #endif /* standard drivers */ NULL, }; diff --git a/src/server/gdb_server.c b/src/server/gdb_server.c index 81dc32d..8e2d2ed 100644 --- a/src/server/gdb_server.c +++ b/src/server/gdb_server.c @@ -791,64 +791,64 @@ static void gdb_fileio_reply(struct target *target, struct connection *connectio bool program_exited = false; if (strcmp(target->fileio_info->identifier, "open") == 0) - sprintf(fileio_command, "F%s,%" PRIx32 "/%" PRIx32 ",%" PRIx32 ",%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64 "/%" PRIx64 ",%" PRIx64 ",%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1, target->fileio_info->param_2, target->fileio_info->param_3, target->fileio_info->param_4); else if (strcmp(target->fileio_info->identifier, "close") == 0) - sprintf(fileio_command, "F%s,%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1); else if (strcmp(target->fileio_info->identifier, "read") == 0) - sprintf(fileio_command, "F%s,%" PRIx32 ",%" PRIx32 ",%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64 ",%" PRIx64 ",%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1, target->fileio_info->param_2, target->fileio_info->param_3); else if (strcmp(target->fileio_info->identifier, "write") == 0) - sprintf(fileio_command, "F%s,%" PRIx32 ",%" PRIx32 ",%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64 ",%" PRIx64 ",%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1, target->fileio_info->param_2, target->fileio_info->param_3); else if (strcmp(target->fileio_info->identifier, "lseek") == 0) - sprintf(fileio_command, "F%s,%" PRIx32 ",%" PRIx32 ",%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64 ",%" PRIx64 ",%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1, target->fileio_info->param_2, target->fileio_info->param_3); else if (strcmp(target->fileio_info->identifier, "rename") == 0) - sprintf(fileio_command, "F%s,%" PRIx32 "/%" PRIx32 ",%" PRIx32 "/%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64 "/%" PRIx64 ",%" PRIx64 "/%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1, target->fileio_info->param_2, target->fileio_info->param_3, target->fileio_info->param_4); else if (strcmp(target->fileio_info->identifier, "unlink") == 0) - sprintf(fileio_command, "F%s,%" PRIx32 "/%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64 "/%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1, target->fileio_info->param_2); else if (strcmp(target->fileio_info->identifier, "stat") == 0) - sprintf(fileio_command, "F%s,%" PRIx32 "/%" PRIx32 ",%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64 "/%" PRIx64 ",%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1, target->fileio_info->param_2, target->fileio_info->param_3); else if (strcmp(target->fileio_info->identifier, "fstat") == 0) - sprintf(fileio_command, "F%s,%" PRIx32 ",%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64 ",%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1, target->fileio_info->param_2); else if (strcmp(target->fileio_info->identifier, "gettimeofday") == 0) - sprintf(fileio_command, "F%s,%" PRIx32 ",%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64 ",%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1, target->fileio_info->param_2); else if (strcmp(target->fileio_info->identifier, "isatty") == 0) - sprintf(fileio_command, "F%s,%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1); else if (strcmp(target->fileio_info->identifier, "system") == 0) - sprintf(fileio_command, "F%s,%" PRIx32 "/%" PRIx32, target->fileio_info->identifier, + sprintf(fileio_command, "F%s,%" PRIx64 "/%" PRIx64, target->fileio_info->identifier, target->fileio_info->param_1, target->fileio_info->param_2); else if (strcmp(target->fileio_info->identifier, "exit") == 0) { /* If target hits exit syscall, report to GDB the program is terminated. * In addition, let target run its own exit syscall handler. */ program_exited = true; - sprintf(fileio_command, "W%02" PRIx32, target->fileio_info->param_1); + sprintf(fileio_command, "W%02" PRIx64, target->fileio_info->param_1); } else { LOG_DEBUG("Unknown syscall: %s", target->fileio_info->identifier); @@ -2704,6 +2704,7 @@ static bool gdb_handle_vcont_packet(struct connection *connection, const char *p /* simple case, a continue packet */ if (parse[0] == 'c') { + gdb_running_type = 'c'; LOG_DEBUG("target %s continue", target_name(target)); log_add_callback(gdb_log_callback, connection); retval = target_resume(target, 1, 0, 0, 0); @@ -2730,6 +2731,7 @@ static bool gdb_handle_vcont_packet(struct connection *connection, const char *p /* single-step or step-over-breakpoint */ if (parse[0] == 's') { + gdb_running_type = 's'; bool fake_step = false; if (strncmp(parse, "s:", 2) == 0) { diff --git a/src/target/Makefile.am b/src/target/Makefile.am index 030015a..a37b717 100644 --- a/src/target/Makefile.am +++ b/src/target/Makefile.am @@ -40,6 +40,7 @@ TARGET_CORE_SRC = \ %D%/target.c \ %D%/target_request.c \ %D%/testee.c \ + %D%/semihosting_common.c \ %D%/smp.c ARMV4_5_SRC = \ @@ -218,6 +219,7 @@ RISCV_SRC = \ %D%/nds32_v3.h \ %D%/nds32_v3m.h \ %D%/nds32_aice.h \ + %D%/semihosting_common.h \ %D%/stm8.h \ %D%/lakemont.h \ %D%/x86_32_common.h \ diff --git a/src/target/aarch64.c b/src/target/aarch64.c index cd83502..df1e49c 100644 --- a/src/target/aarch64.c +++ b/src/target/aarch64.c @@ -2590,6 +2590,143 @@ COMMAND_HANDLER(aarch64_mask_interrupts_command) return ERROR_OK; } +static int jim_mcrmrc(Jim_Interp *interp, int argc, Jim_Obj * const *argv) +{ + struct command_context *context; + struct target *target; + struct arm *arm; + int retval; + bool is_mcr = false; + int arg_cnt = 0; + + if (Jim_CompareStringImmediate(interp, argv[0], "mcr")) { + is_mcr = true; + arg_cnt = 7; + } else { + arg_cnt = 6; + } + + context = current_command_context(interp); + assert(context != NULL); + + target = get_current_target(context); + if (target == NULL) { + LOG_ERROR("%s: no current target", __func__); + return JIM_ERR; + } + if (!target_was_examined(target)) { + LOG_ERROR("%s: not yet examined", target_name(target)); + return JIM_ERR; + } + + arm = target_to_arm(target); + if (!is_arm(arm)) { + LOG_ERROR("%s: not an ARM", target_name(target)); + return JIM_ERR; + } + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + if (arm->core_state == ARM_STATE_AARCH64) { + LOG_ERROR("%s: not 32-bit arm target", target_name(target)); + return JIM_ERR; + } + + if (argc != arg_cnt) { + LOG_ERROR("%s: wrong number of arguments", __func__); + return JIM_ERR; + } + + int cpnum; + uint32_t op1; + uint32_t op2; + uint32_t CRn; + uint32_t CRm; + uint32_t value; + long l; + + /* NOTE: parameter sequence matches ARM instruction set usage: + * MCR pNUM, op1, rX, CRn, CRm, op2 ; write CP from rX + * MRC pNUM, op1, rX, CRn, CRm, op2 ; read CP into rX + * The "rX" is necessarily omitted; it uses Tcl mechanisms. + */ + retval = Jim_GetLong(interp, argv[1], &l); + if (retval != JIM_OK) + return retval; + if (l & ~0xf) { + LOG_ERROR("%s: %s %d out of range", __func__, + "coprocessor", (int) l); + return JIM_ERR; + } + cpnum = l; + + retval = Jim_GetLong(interp, argv[2], &l); + if (retval != JIM_OK) + return retval; + if (l & ~0x7) { + LOG_ERROR("%s: %s %d out of range", __func__, + "op1", (int) l); + return JIM_ERR; + } + op1 = l; + + retval = Jim_GetLong(interp, argv[3], &l); + if (retval != JIM_OK) + return retval; + if (l & ~0xf) { + LOG_ERROR("%s: %s %d out of range", __func__, + "CRn", (int) l); + return JIM_ERR; + } + CRn = l; + + retval = Jim_GetLong(interp, argv[4], &l); + if (retval != JIM_OK) + return retval; + if (l & ~0xf) { + LOG_ERROR("%s: %s %d out of range", __func__, + "CRm", (int) l); + return JIM_ERR; + } + CRm = l; + + retval = Jim_GetLong(interp, argv[5], &l); + if (retval != JIM_OK) + return retval; + if (l & ~0x7) { + LOG_ERROR("%s: %s %d out of range", __func__, + "op2", (int) l); + return JIM_ERR; + } + op2 = l; + + value = 0; + + if (is_mcr == true) { + retval = Jim_GetLong(interp, argv[6], &l); + if (retval != JIM_OK) + return retval; + value = l; + + /* NOTE: parameters reordered! */ + /* ARMV4_5_MCR(cpnum, op1, 0, CRn, CRm, op2) */ + retval = arm->mcr(target, cpnum, op1, op2, CRn, CRm, value); + if (retval != ERROR_OK) + return JIM_ERR; + } else { + /* NOTE: parameters reordered! */ + /* ARMV4_5_MRC(cpnum, op1, 0, CRn, CRm, op2) */ + retval = arm->mrc(target, cpnum, op1, op2, CRn, CRm, &value); + if (retval != ERROR_OK) + return JIM_ERR; + + Jim_SetResult(interp, Jim_NewIntObj(interp, value)); + } + + return JIM_OK; +} + static const struct command_registration aarch64_exec_command_handlers[] = { { .name = "cache_info", @@ -2625,9 +2762,25 @@ static const struct command_registration aarch64_exec_command_handlers[] = { .help = "mask aarch64 interrupts during single-step", .usage = "['on'|'off']", }, + { + .name = "mcr", + .mode = COMMAND_EXEC, + .jim_handler = jim_mcrmrc, + .help = "write coprocessor register", + .usage = "cpnum op1 CRn CRm op2 value", + }, + { + .name = "mrc", + .mode = COMMAND_EXEC, + .jim_handler = jim_mcrmrc, + .help = "read coprocessor register", + .usage = "cpnum op1 CRn CRm op2", + }, + COMMAND_REGISTRATION_DONE }; + static const struct command_registration aarch64_command_handlers[] = { { .chain = armv8_command_handlers, diff --git a/src/target/arm.h b/src/target/arm.h index dd25d53..316ff9a 100644 --- a/src/target/arm.h +++ b/src/target/arm.h @@ -8,6 +8,9 @@ * Copyright (C) 2009 by Øyvind Harboe * oyvind.harboe@zylin.com * + * Copyright (C) 2018 by Liviu Ionescu + * <ilg@livius.net> + * * 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 2 of the License, or @@ -28,7 +31,6 @@ #include <helper/command.h> #include "target.h" - /** * @file * Holds the interface to ARM cores. @@ -181,32 +183,11 @@ struct arm { /** Flag reporting armv6m based core. */ bool is_armv6m; - /** Flag reporting whether semihosting is active. */ - bool is_semihosting; - - /** Flag reporting whether semihosting fileio is active. */ - bool is_semihosting_fileio; - - /** Flag reporting whether semihosting fileio operation is active. */ - bool semihosting_hit_fileio; - /** Floating point or VFP version, 0 if disabled. */ int arm_vfp_version; - /** Current semihosting operation. */ - int semihosting_op; - - /** Current semihosting result. */ - int semihosting_result; - - /** Value to be returned by semihosting SYS_ERRNO request. */ - int semihosting_errno; - int (*setup_semihosting)(struct target *target, int enable); - /** Semihosting command line. */ - char *semihosting_cmdline; - /** Backpointer to the target. */ struct target *target; diff --git a/src/target/arm_semihosting.c b/src/target/arm_semihosting.c index f31f901..57f3139 100644 --- a/src/target/arm_semihosting.c +++ b/src/target/arm_semihosting.c @@ -8,6 +8,9 @@ * Copyright (C) 2016 by Square, Inc. * * Steven Stallion <stallion@squareup.com> * * * + * Copyright (C) 2018 by Liviu Ionescu * + * <ilg@livius.net> * + * * * 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 2 of the License, or * @@ -52,21 +55,6 @@ #include <helper/log.h> #include <sys/stat.h> -static const int open_modeflags[12] = { - O_RDONLY, - O_RDONLY | O_BINARY, - O_RDWR, - O_RDWR | O_BINARY, - O_WRONLY | O_CREAT | O_TRUNC, - O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, - O_RDWR | O_CREAT | O_TRUNC, - O_RDWR | O_CREAT | O_TRUNC | O_BINARY, - O_WRONLY | O_CREAT | O_APPEND, - O_WRONLY | O_CREAT | O_APPEND | O_BINARY, - O_RDWR | O_CREAT | O_APPEND, - O_RDWR | O_CREAT | O_APPEND | O_BINARY -}; - static int post_result(struct target *target) { struct arm *arm = target_to_arm(target); @@ -79,7 +67,7 @@ static int post_result(struct target *target) uint32_t spsr; /* return value in R0 */ - buf_set_u32(arm->core_cache->reg_list[0].value, 0, 32, arm->semihosting_result); + buf_set_u32(arm->core_cache->reg_list[0].value, 0, 32, target->semihosting->result); arm->core_cache->reg_list[0].dirty = 1; /* LR --> PC */ @@ -105,523 +93,13 @@ static int post_result(struct target *target) * bkpt instruction */ /* return result in R0 */ - buf_set_u32(arm->core_cache->reg_list[0].value, 0, 32, arm->semihosting_result); + buf_set_u32(arm->core_cache->reg_list[0].value, 0, 32, target->semihosting->result); arm->core_cache->reg_list[0].dirty = 1; } return ERROR_OK; } -static int do_semihosting(struct target *target) -{ - struct arm *arm = target_to_arm(target); - struct gdb_fileio_info *fileio_info = target->fileio_info; - uint32_t r0 = buf_get_u32(arm->core_cache->reg_list[0].value, 0, 32); - uint32_t r1 = buf_get_u32(arm->core_cache->reg_list[1].value, 0, 32); - uint8_t params[16]; - int retval; - - /* - * TODO: lots of security issues are not considered yet, such as: - * - no validation on target provided file descriptors - * - no safety checks on opened/deleted/renamed file paths - * Beware the target app you use this support with. - * - * TODO: unsupported semihosting fileio operations could be - * implemented if we had a small working area at our disposal. - */ - switch ((arm->semihosting_op = r0)) { - case 0x01: /* SYS_OPEN */ - retval = target_read_memory(target, r1, 4, 3, params); - if (retval != ERROR_OK) - return retval; - else { - uint32_t a = target_buffer_get_u32(target, params+0); - uint32_t m = target_buffer_get_u32(target, params+4); - uint32_t l = target_buffer_get_u32(target, params+8); - uint8_t fn[256]; - retval = target_read_memory(target, a, 1, l, fn); - if (retval != ERROR_OK) - return retval; - fn[l] = 0; - if (arm->is_semihosting_fileio) { - if (strcmp((char *)fn, ":tt") == 0) - arm->semihosting_result = 0; - else { - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "open"; - fileio_info->param_1 = a; - fileio_info->param_2 = l; - fileio_info->param_3 = open_modeflags[m]; - fileio_info->param_4 = 0644; - } - } else { - if (l <= 255 && m <= 11) { - if (strcmp((char *)fn, ":tt") == 0) { - if (m < 4) - arm->semihosting_result = dup(STDIN_FILENO); - else - arm->semihosting_result = dup(STDOUT_FILENO); - } else { - /* cygwin requires the permission setting - * otherwise it will fail to reopen a previously - * written file */ - arm->semihosting_result = open((char *)fn, open_modeflags[m], 0644); - } - arm->semihosting_errno = errno; - } else { - arm->semihosting_result = -1; - arm->semihosting_errno = EINVAL; - } - } - } - break; - - case 0x02: /* SYS_CLOSE */ - retval = target_read_memory(target, r1, 4, 1, params); - if (retval != ERROR_OK) - return retval; - else { - int fd = target_buffer_get_u32(target, params+0); - if (arm->is_semihosting_fileio) { - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "close"; - fileio_info->param_1 = fd; - } else { - arm->semihosting_result = close(fd); - arm->semihosting_errno = errno; - } - } - break; - - case 0x03: /* SYS_WRITEC */ - if (arm->is_semihosting_fileio) { - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "write"; - fileio_info->param_1 = 1; - fileio_info->param_2 = r1; - fileio_info->param_3 = 1; - } else { - unsigned char c; - retval = target_read_memory(target, r1, 1, 1, &c); - if (retval != ERROR_OK) - return retval; - putchar(c); - arm->semihosting_result = 0; - } - break; - - case 0x04: /* SYS_WRITE0 */ - if (arm->is_semihosting_fileio) { - size_t count = 0; - for (uint32_t a = r1;; a++) { - unsigned char c; - retval = target_read_memory(target, a, 1, 1, &c); - if (retval != ERROR_OK) - return retval; - if (c == '\0') - break; - count++; - } - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "write"; - fileio_info->param_1 = 1; - fileio_info->param_2 = r1; - fileio_info->param_3 = count; - } else { - do { - unsigned char c; - retval = target_read_memory(target, r1++, 1, 1, &c); - if (retval != ERROR_OK) - return retval; - if (!c) - break; - putchar(c); - } while (1); - arm->semihosting_result = 0; - } - break; - - case 0x05: /* SYS_WRITE */ - retval = target_read_memory(target, r1, 4, 3, params); - if (retval != ERROR_OK) - return retval; - else { - int fd = target_buffer_get_u32(target, params+0); - uint32_t a = target_buffer_get_u32(target, params+4); - size_t l = target_buffer_get_u32(target, params+8); - if (arm->is_semihosting_fileio) { - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "write"; - fileio_info->param_1 = fd; - fileio_info->param_2 = a; - fileio_info->param_3 = l; - } else { - uint8_t *buf = malloc(l); - if (!buf) { - arm->semihosting_result = -1; - arm->semihosting_errno = ENOMEM; - } else { - retval = target_read_buffer(target, a, l, buf); - if (retval != ERROR_OK) { - free(buf); - return retval; - } - arm->semihosting_result = write(fd, buf, l); - arm->semihosting_errno = errno; - if (arm->semihosting_result >= 0) - arm->semihosting_result = l - arm->semihosting_result; - free(buf); - } - } - } - break; - - case 0x06: /* SYS_READ */ - retval = target_read_memory(target, r1, 4, 3, params); - if (retval != ERROR_OK) - return retval; - else { - int fd = target_buffer_get_u32(target, params+0); - uint32_t a = target_buffer_get_u32(target, params+4); - ssize_t l = target_buffer_get_u32(target, params+8); - if (arm->is_semihosting_fileio) { - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "read"; - fileio_info->param_1 = fd; - fileio_info->param_2 = a; - fileio_info->param_3 = l; - } else { - uint8_t *buf = malloc(l); - if (!buf) { - arm->semihosting_result = -1; - arm->semihosting_errno = ENOMEM; - } else { - arm->semihosting_result = read(fd, buf, l); - arm->semihosting_errno = errno; - if (arm->semihosting_result >= 0) { - retval = target_write_buffer(target, a, arm->semihosting_result, buf); - if (retval != ERROR_OK) { - free(buf); - return retval; - } - arm->semihosting_result = l - arm->semihosting_result; - } - free(buf); - } - } - } - break; - - case 0x07: /* SYS_READC */ - if (arm->is_semihosting_fileio) { - LOG_ERROR("SYS_READC not supported by semihosting fileio"); - return ERROR_FAIL; - } - arm->semihosting_result = getchar(); - break; - - case 0x08: /* SYS_ISERROR */ - retval = target_read_memory(target, r1, 4, 1, params); - if (retval != ERROR_OK) - return retval; - arm->semihosting_result = (target_buffer_get_u32(target, params+0) != 0); - break; - - case 0x09: /* SYS_ISTTY */ - if (arm->is_semihosting_fileio) { - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "isatty"; - fileio_info->param_1 = r1; - } else { - retval = target_read_memory(target, r1, 4, 1, params); - if (retval != ERROR_OK) - return retval; - arm->semihosting_result = isatty(target_buffer_get_u32(target, params+0)); - } - break; - - case 0x0a: /* SYS_SEEK */ - retval = target_read_memory(target, r1, 4, 2, params); - if (retval != ERROR_OK) - return retval; - else { - int fd = target_buffer_get_u32(target, params+0); - off_t pos = target_buffer_get_u32(target, params+4); - if (arm->is_semihosting_fileio) { - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "lseek"; - fileio_info->param_1 = fd; - fileio_info->param_2 = pos; - fileio_info->param_3 = SEEK_SET; - } else { - arm->semihosting_result = lseek(fd, pos, SEEK_SET); - arm->semihosting_errno = errno; - if (arm->semihosting_result == pos) - arm->semihosting_result = 0; - } - } - break; - - case 0x0c: /* SYS_FLEN */ - if (arm->is_semihosting_fileio) { - LOG_ERROR("SYS_FLEN not supported by semihosting fileio"); - return ERROR_FAIL; - } - retval = target_read_memory(target, r1, 4, 1, params); - if (retval != ERROR_OK) - return retval; - else { - int fd = target_buffer_get_u32(target, params+0); - struct stat buf; - arm->semihosting_result = fstat(fd, &buf); - if (arm->semihosting_result == -1) { - arm->semihosting_errno = errno; - arm->semihosting_result = -1; - break; - } - arm->semihosting_result = buf.st_size; - } - break; - - case 0x0e: /* SYS_REMOVE */ - retval = target_read_memory(target, r1, 4, 2, params); - if (retval != ERROR_OK) - return retval; - else { - uint32_t a = target_buffer_get_u32(target, params+0); - uint32_t l = target_buffer_get_u32(target, params+4); - if (arm->is_semihosting_fileio) { - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "unlink"; - fileio_info->param_1 = a; - fileio_info->param_2 = l; - } else { - if (l <= 255) { - uint8_t fn[256]; - retval = target_read_memory(target, a, 1, l, fn); - if (retval != ERROR_OK) - return retval; - fn[l] = 0; - arm->semihosting_result = remove((char *)fn); - arm->semihosting_errno = errno; - } else { - arm->semihosting_result = -1; - arm->semihosting_errno = EINVAL; - } - } - } - break; - - case 0x0f: /* SYS_RENAME */ - retval = target_read_memory(target, r1, 4, 4, params); - if (retval != ERROR_OK) - return retval; - else { - uint32_t a1 = target_buffer_get_u32(target, params+0); - uint32_t l1 = target_buffer_get_u32(target, params+4); - uint32_t a2 = target_buffer_get_u32(target, params+8); - uint32_t l2 = target_buffer_get_u32(target, params+12); - if (arm->is_semihosting_fileio) { - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "rename"; - fileio_info->param_1 = a1; - fileio_info->param_2 = l1; - fileio_info->param_3 = a2; - fileio_info->param_4 = l2; - } else { - if (l1 <= 255 && l2 <= 255) { - uint8_t fn1[256], fn2[256]; - retval = target_read_memory(target, a1, 1, l1, fn1); - if (retval != ERROR_OK) - return retval; - retval = target_read_memory(target, a2, 1, l2, fn2); - if (retval != ERROR_OK) - return retval; - fn1[l1] = 0; - fn2[l2] = 0; - arm->semihosting_result = rename((char *)fn1, (char *)fn2); - arm->semihosting_errno = errno; - } else { - arm->semihosting_result = -1; - arm->semihosting_errno = EINVAL; - } - } - } - break; - - case 0x11: /* SYS_TIME */ - arm->semihosting_result = time(NULL); - break; - - case 0x13: /* SYS_ERRNO */ - arm->semihosting_result = arm->semihosting_errno; - break; - - case 0x15: /* SYS_GET_CMDLINE */ - retval = target_read_memory(target, r1, 4, 2, params); - if (retval != ERROR_OK) - return retval; - else { - uint32_t a = target_buffer_get_u32(target, params+0); - uint32_t l = target_buffer_get_u32(target, params+4); - char *arg = arm->semihosting_cmdline != NULL ? arm->semihosting_cmdline : ""; - uint32_t s = strlen(arg) + 1; - if (l < s) - arm->semihosting_result = -1; - else { - retval = target_write_buffer(target, a, s, (uint8_t *)arg); - if (retval != ERROR_OK) - return retval; - arm->semihosting_result = 0; - } - } - break; - - case 0x16: /* SYS_HEAPINFO */ - retval = target_read_memory(target, r1, 4, 1, params); - if (retval != ERROR_OK) - return retval; - else { - uint32_t a = target_buffer_get_u32(target, params+0); - /* tell the remote we have no idea */ - memset(params, 0, 4*4); - retval = target_write_memory(target, a, 4, 4, params); - if (retval != ERROR_OK) - return retval; - arm->semihosting_result = 0; - } - break; - - case 0x18: /* angel_SWIreason_ReportException */ - switch (r1) { - case 0x20026: /* ADP_Stopped_ApplicationExit */ - fprintf(stderr, "semihosting: *** application exited ***\n"); - break; - case 0x20000: /* ADP_Stopped_BranchThroughZero */ - case 0x20001: /* ADP_Stopped_UndefinedInstr */ - case 0x20002: /* ADP_Stopped_SoftwareInterrupt */ - case 0x20003: /* ADP_Stopped_PrefetchAbort */ - case 0x20004: /* ADP_Stopped_DataAbort */ - case 0x20005: /* ADP_Stopped_AddressException */ - case 0x20006: /* ADP_Stopped_IRQ */ - case 0x20007: /* ADP_Stopped_FIQ */ - case 0x20020: /* ADP_Stopped_BreakPoint */ - case 0x20021: /* ADP_Stopped_WatchPoint */ - case 0x20022: /* ADP_Stopped_StepComplete */ - case 0x20023: /* ADP_Stopped_RunTimeErrorUnknown */ - case 0x20024: /* ADP_Stopped_InternalError */ - case 0x20025: /* ADP_Stopped_UserInterruption */ - case 0x20027: /* ADP_Stopped_StackOverflow */ - case 0x20028: /* ADP_Stopped_DivisionByZero */ - case 0x20029: /* ADP_Stopped_OSSpecific */ - default: - fprintf(stderr, "semihosting: exception %#x\n", - (unsigned) r1); - } - return target_call_event_callbacks(target, TARGET_EVENT_HALTED); - - case 0x12: /* SYS_SYSTEM */ - /* Provide SYS_SYSTEM functionality. Uses the - * libc system command, there may be a reason *NOT* - * to use this, but as I can't think of one, I - * implemented it this way. - */ - retval = target_read_memory(target, r1, 4, 2, params); - if (retval != ERROR_OK) - return retval; - else { - uint32_t len = target_buffer_get_u32(target, params+4); - uint32_t c_ptr = target_buffer_get_u32(target, params); - if (arm->is_semihosting_fileio) { - arm->semihosting_hit_fileio = true; - fileio_info->identifier = "system"; - fileio_info->param_1 = c_ptr; - fileio_info->param_2 = len; - } else { - uint8_t cmd[256]; - if (len > 255) { - arm->semihosting_result = -1; - arm->semihosting_errno = EINVAL; - } else { - memset(cmd, 0x0, 256); - retval = target_read_memory(target, c_ptr, 1, len, cmd); - if (retval != ERROR_OK) - return retval; - else - arm->semihosting_result = system((const char *)cmd); - } - } - } - break; - case 0x0d: /* SYS_TMPNAM */ - case 0x10: /* SYS_CLOCK */ - case 0x17: /* angel_SWIreason_EnterSVC */ - case 0x30: /* SYS_ELAPSED */ - case 0x31: /* SYS_TICKFREQ */ - default: - fprintf(stderr, "semihosting: unsupported call %#x\n", - (unsigned) r0); - arm->semihosting_result = -1; - arm->semihosting_errno = ENOTSUP; - } - - return ERROR_OK; -} - -static int get_gdb_fileio_info(struct target *target, struct gdb_fileio_info *fileio_info) -{ - struct arm *arm = target_to_arm(target); - - /* To avoid uneccessary duplication, semihosting prepares the - * fileio_info structure out-of-band when the target halts. See - * do_semihosting for more detail. - */ - if (!arm->is_semihosting_fileio || !arm->semihosting_hit_fileio) - return ERROR_FAIL; - - return ERROR_OK; -} - -static int gdb_fileio_end(struct target *target, int result, int fileio_errno, bool ctrl_c) -{ - struct arm *arm = target_to_arm(target); - struct gdb_fileio_info *fileio_info = target->fileio_info; - - /* clear pending status */ - arm->semihosting_hit_fileio = false; - - arm->semihosting_result = result; - arm->semihosting_errno = fileio_errno; - - /* Some fileio results do not match up with what the semihosting - * operation expects; for these operations, we munge the results - * below: - */ - switch (arm->semihosting_op) { - case 0x05: /* SYS_WRITE */ - if (result < 0) - arm->semihosting_result = fileio_info->param_3; - else - arm->semihosting_result = 0; - break; - - case 0x06: /* SYS_READ */ - if (result == (int)fileio_info->param_3) - arm->semihosting_result = 0; - if (result <= 0) - arm->semihosting_result = fileio_info->param_3; - break; - - case 0x0a: /* SYS_SEEK */ - if (result > 0) - arm->semihosting_result = 0; - break; - } - - return post_result(target); -} - /** * Initialize ARM semihosting support. * @@ -630,14 +108,9 @@ static int gdb_fileio_end(struct target *target, int result, int fileio_errno, b */ int arm_semihosting_init(struct target *target) { - target->fileio_info = malloc(sizeof(*target->fileio_info)); - if (target->fileio_info == NULL) { - LOG_ERROR("out of memory"); - return ERROR_FAIL; - } - - target->type->get_gdb_fileio_info = get_gdb_fileio_info; - target->type->gdb_fileio_end = gdb_fileio_end; + struct arm *arm = target_to_arm(target); + assert(arm->setup_semihosting); + semihosting_common_init(target, arm->setup_semihosting, post_result); return ERROR_OK; } @@ -662,7 +135,11 @@ int arm_semihosting(struct target *target, int *retval) uint32_t pc, lr, spsr; struct reg *r; - if (!arm->is_semihosting) + struct semihosting *semihosting = target->semihosting; + if (!semihosting) + return 0; + + if (!semihosting->is_active) return 0; if (is_arm7_9(target_to_arm7_9(target)) || @@ -766,10 +243,24 @@ int arm_semihosting(struct target *target, int *retval) /* Perform semihosting if we are not waiting on a fileio * operation to complete. */ - if (!arm->semihosting_hit_fileio) { - *retval = do_semihosting(target); - if (*retval != ERROR_OK) { - LOG_ERROR("Failed semihosting operation"); + if (!semihosting->hit_fileio) { + /* TODO: update for 64-bits */ + uint32_t r0 = buf_get_u32(arm->core_cache->reg_list[0].value, 0, 32); + uint32_t r1 = buf_get_u32(arm->core_cache->reg_list[1].value, 0, 32); + + semihosting->op = r0; + semihosting->param = r1; + semihosting->word_size_bytes = 4; + + /* Check for ARM operation numbers. */ + if (0 <= semihosting->op && semihosting->op <= 0x31) { + *retval = semihosting_common(target); + if (*retval != ERROR_OK) { + LOG_ERROR("Failed semihosting operation"); + return 0; + } + } else { + /* Unknown operation number, not a semihosting call. */ return 0; } } @@ -777,13 +268,8 @@ int arm_semihosting(struct target *target, int *retval) /* Post result to target if we are not waiting on a fileio * operation to complete: */ - if (!arm->semihosting_hit_fileio) { - *retval = post_result(target); - if (*retval != ERROR_OK) { - LOG_ERROR("Failed to post semihosting result"); - return 0; - } - + if (semihosting->is_resumable && !semihosting->hit_fileio) { + /* Resume right after the BRK instruction. */ *retval = target_resume(target, 1, 0, 0, 0); if (*retval != ERROR_OK) { LOG_ERROR("Failed to resume target"); diff --git a/src/target/arm_semihosting.h b/src/target/arm_semihosting.h index 011f19f..cf1f8de 100644 --- a/src/target/arm_semihosting.h +++ b/src/target/arm_semihosting.h @@ -19,6 +19,8 @@ #ifndef OPENOCD_TARGET_ARM_SEMIHOSTING_H #define OPENOCD_TARGET_ARM_SEMIHOSTING_H +#include "semihosting_common.h" + int arm_semihosting_init(struct target *target); int arm_semihosting(struct target *target, int *retval); diff --git a/src/target/armv4_5.c b/src/target/armv4_5.c index 06994ca..5ee8ead 100644 --- a/src/target/armv4_5.c +++ b/src/target/armv4_5.c @@ -8,6 +8,9 @@ * Copyright (C) 2008 by Oyvind Harboe * * oyvind.harboe@zylin.com * * * + * Copyright (C) 2018 by Liviu Ionescu * + * <ilg@livius.net> * + * * * 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 2 of the License, or * @@ -34,6 +37,7 @@ #include <helper/binarybuffer.h> #include "algorithm.h" #include "register.h" +#include "semihosting_common.h" /* offsets into armv4_5 core register cache */ enum { @@ -748,7 +752,7 @@ int arm_arch_state(struct target *target) } /* avoid filling log waiting for fileio reply */ - if (arm->semihosting_hit_fileio) + if (target->semihosting->hit_fileio) return ERROR_OK; LOG_USER("target halted in %s state due to %s, current mode: %s\n" @@ -758,8 +762,8 @@ int arm_arch_state(struct target *target) arm_mode_name(arm->core_mode), buf_get_u32(arm->cpsr->value, 0, 32), buf_get_u32(arm->pc->value, 0, 32), - arm->is_semihosting ? ", semihosting" : "", - arm->is_semihosting_fileio ? " fileio" : ""); + target->semihosting->is_active ? ", semihosting" : "", + target->semihosting->is_fileio ? " fileio" : ""); return ERROR_OK; } @@ -1094,119 +1098,10 @@ static int jim_mcrmrc(Jim_Interp *interp, int argc, Jim_Obj * const *argv) return JIM_OK; } -COMMAND_HANDLER(handle_arm_semihosting_command) -{ - struct target *target = get_current_target(CMD_CTX); - - if (target == NULL) { - LOG_ERROR("No target selected"); - return ERROR_FAIL; - } - - struct arm *arm = target_to_arm(target); - - if (!is_arm(arm)) { - command_print(CMD_CTX, "current target isn't an ARM"); - return ERROR_FAIL; - } - - if (!arm->setup_semihosting) { - command_print(CMD_CTX, "semihosting not supported for current target"); - return ERROR_FAIL; - } - - if (CMD_ARGC > 0) { - int semihosting; - - COMMAND_PARSE_ENABLE(CMD_ARGV[0], semihosting); - - if (!target_was_examined(target)) { - LOG_ERROR("Target not examined yet"); - return ERROR_FAIL; - } - - if (arm->setup_semihosting(target, semihosting) != ERROR_OK) { - LOG_ERROR("Failed to Configure semihosting"); - return ERROR_FAIL; - } - - /* FIXME never let that "catch" be dropped! */ - arm->is_semihosting = semihosting; - } - - command_print(CMD_CTX, "semihosting is %s", - arm->is_semihosting - ? "enabled" : "disabled"); - - return ERROR_OK; -} - -COMMAND_HANDLER(handle_arm_semihosting_fileio_command) -{ - struct target *target = get_current_target(CMD_CTX); - - if (target == NULL) { - LOG_ERROR("No target selected"); - return ERROR_FAIL; - } - - struct arm *arm = target_to_arm(target); - - if (!is_arm(arm)) { - command_print(CMD_CTX, "current target isn't an ARM"); - return ERROR_FAIL; - } - - if (!arm->is_semihosting) { - command_print(CMD_CTX, "semihosting is not enabled"); - return ERROR_FAIL; - } - - if (CMD_ARGC > 0) - COMMAND_PARSE_ENABLE(CMD_ARGV[0], arm->is_semihosting_fileio); - - command_print(CMD_CTX, "semihosting fileio is %s", - arm->is_semihosting_fileio - ? "enabled" : "disabled"); - - return ERROR_OK; -} - -COMMAND_HANDLER(handle_arm_semihosting_cmdline) -{ - struct target *target = get_current_target(CMD_CTX); - unsigned int i; - - if (target == NULL) { - LOG_ERROR("No target selected"); - return ERROR_FAIL; - } - - struct arm *arm = target_to_arm(target); - - if (!is_arm(arm)) { - command_print(CMD_CTX, "current target isn't an ARM"); - return ERROR_FAIL; - } - - if (!arm->setup_semihosting) { - command_print(CMD_CTX, "semihosting not supported for current target"); - return ERROR_FAIL; - } - - free(arm->semihosting_cmdline); - arm->semihosting_cmdline = CMD_ARGC > 0 ? strdup(CMD_ARGV[0]) : NULL; - - for (i = 1; i < CMD_ARGC; i++) { - char *cmdline = alloc_printf("%s %s", arm->semihosting_cmdline, CMD_ARGV[i]); - if (cmdline == NULL) - break; - free(arm->semihosting_cmdline); - arm->semihosting_cmdline = cmdline; - } - - return ERROR_OK; -} +extern __COMMAND_HANDLER(handle_common_semihosting_command); +extern __COMMAND_HANDLER(handle_common_semihosting_fileio_command); +extern __COMMAND_HANDLER(handle_common_semihosting_resumable_exit_command); +extern __COMMAND_HANDLER(handle_common_semihosting_cmdline); static const struct command_registration arm_exec_command_handlers[] = { { @@ -1245,26 +1140,32 @@ static const struct command_registration arm_exec_command_handlers[] = { }, { "semihosting", - .handler = handle_arm_semihosting_command, + .handler = handle_common_semihosting_command, .mode = COMMAND_EXEC, .usage = "['enable'|'disable']", .help = "activate support for semihosting operations", }, { "semihosting_cmdline", - .handler = handle_arm_semihosting_cmdline, + .handler = handle_common_semihosting_cmdline, .mode = COMMAND_EXEC, .usage = "arguments", .help = "command line arguments to be passed to program", }, { "semihosting_fileio", - .handler = handle_arm_semihosting_fileio_command, + .handler = handle_common_semihosting_fileio_command, .mode = COMMAND_EXEC, .usage = "['enable'|'disable']", .help = "activate support for semihosting fileio operations", }, - + { + "semihosting_resexit", + .handler = handle_common_semihosting_resumable_exit_command, + .mode = COMMAND_EXEC, + .usage = "['enable'|'disable']", + .help = "activate support for semihosting resumable exit", + }, COMMAND_REGISTRATION_DONE }; const struct command_registration arm_command_handlers[] = { diff --git a/src/target/armv7m.c b/src/target/armv7m.c index 7b7893f..1b4e5b1 100644 --- a/src/target/armv7m.c +++ b/src/target/armv7m.c @@ -11,6 +11,9 @@ * Copyright (C) 2007,2008 Øyvind Harboe * * oyvind.harboe@zylin.com * * * + * Copyright (C) 2018 by Liviu Ionescu * + * <ilg@livius.net> * + * * * 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 2 of the License, or * @@ -37,6 +40,7 @@ #include "armv7m.h" #include "algorithm.h" #include "register.h" +#include "semihosting_common.h" #if 0 #define _DEBUG_INSTRUCTION_EXECUTION_ @@ -537,7 +541,7 @@ int armv7m_arch_state(struct target *target) uint32_t ctrl, sp; /* avoid filling log waiting for fileio reply */ - if (arm->semihosting_hit_fileio) + if (target->semihosting->hit_fileio) return ERROR_OK; ctrl = buf_get_u32(arm->core_cache->reg_list[ARMV7M_CONTROL].value, 0, 32); @@ -552,8 +556,8 @@ int armv7m_arch_state(struct target *target) buf_get_u32(arm->pc->value, 0, 32), (ctrl & 0x02) ? 'p' : 'm', sp, - arm->is_semihosting ? ", semihosting" : "", - arm->is_semihosting_fileio ? " fileio" : ""); + target->semihosting->is_active ? ", semihosting" : "", + target->semihosting->is_fileio ? " fileio" : ""); return ERROR_OK; } diff --git a/src/target/armv8.c b/src/target/armv8.c index 82b2b24..20f2b67 100644 --- a/src/target/armv8.c +++ b/src/target/armv8.c @@ -1,6 +1,9 @@ /*************************************************************************** * Copyright (C) 2015 by David Ung * * * + * Copyright (C) 2018 by Liviu Ionescu * + * <ilg@livius.net> * + * * * 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 2 of the License, or * @@ -36,6 +39,7 @@ #include "armv8_opcodes.h" #include "target.h" #include "target_type.h" +#include "semihosting_common.h" static const char * const armv8_state_strings[] = { "AArch32", "Thumb", "Jazelle", "ThumbEE", "AArch64", @@ -1046,7 +1050,7 @@ int armv8_aarch64_state(struct target *target) armv8_mode_name(arm->core_mode), buf_get_u32(arm->cpsr->value, 0, 32), buf_get_u64(arm->pc->value, 0, 64), - arm->is_semihosting ? ", semihosting" : ""); + target->semihosting->is_active ? ", semihosting" : ""); return ERROR_OK; } diff --git a/src/target/breakpoints.c b/src/target/breakpoints.c index 7cf4a69..58bcc86 100644 --- a/src/target/breakpoints.c +++ b/src/target/breakpoints.c @@ -315,11 +315,8 @@ int breakpoint_remove_internal(struct target *target, target_addr_t address) struct breakpoint *breakpoint = target->breakpoints; while (breakpoint) { - if ((breakpoint->address == address) && (breakpoint->asid == 0)) - break; - else if ((breakpoint->address == 0) && (breakpoint->asid == address)) - break; - else if ((breakpoint->address == address) && (breakpoint->asid != 0)) + if ((breakpoint->address == address) || + (breakpoint->address == 0 && breakpoint->asid == address)) break; breakpoint = breakpoint->next; } diff --git a/src/target/cortex_m.c b/src/target/cortex_m.c index 30439f4..b6043fc 100644 --- a/src/target/cortex_m.c +++ b/src/target/cortex_m.c @@ -1806,11 +1806,11 @@ static int cortex_m_dwt_set_reg(struct reg *reg, uint8_t *buf) struct dwt_reg { uint32_t addr; - char *name; + const char *name; unsigned size; }; -static struct dwt_reg dwt_base_regs[] = { +static const struct dwt_reg dwt_base_regs[] = { { DWT_CTRL, "dwt_ctrl", 32, }, /* NOTE that Erratum 532314 (fixed r2p0) affects CYCCNT: it wrongly * increments while the core is asleep. @@ -1819,7 +1819,7 @@ static struct dwt_reg dwt_base_regs[] = { /* plus some 8 bit counters, useful for profiling with TPIU */ }; -static struct dwt_reg dwt_comp[] = { +static const struct dwt_reg dwt_comp[] = { #define DWT_COMPARATOR(i) \ { DWT_COMP0 + 0x10 * (i), "dwt_" #i "_comp", 32, }, \ { DWT_MASK0 + 0x10 * (i), "dwt_" #i "_mask", 4, }, \ @@ -1848,7 +1848,7 @@ static const struct reg_arch_type dwt_reg_type = { .set = cortex_m_dwt_set_reg, }; -static void cortex_m_dwt_addreg(struct target *t, struct reg *r, struct dwt_reg *d) +static void cortex_m_dwt_addreg(struct target *t, struct reg *r, const struct dwt_reg *d) { struct dwt_reg_state *state; diff --git a/src/target/nds32.c b/src/target/nds32.c index e4bb17f..4115ea4 100644 --- a/src/target/nds32.c +++ b/src/target/nds32.c @@ -2339,63 +2339,66 @@ int nds32_get_gdb_fileio_info(struct target *target, struct gdb_fileio_info *fil fileio_info->identifier = NULL; } + uint32_t reg_r0, reg_r1, reg_r2; + nds32_get_mapped_reg(nds32, R0, ®_r0); + nds32_get_mapped_reg(nds32, R1, ®_r1); + nds32_get_mapped_reg(nds32, R2, ®_r2); + switch (syscall_id) { case NDS32_SYSCALL_EXIT: fileio_info->identifier = malloc(5); sprintf(fileio_info->identifier, "exit"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); + fileio_info->param_1 = reg_r0; break; case NDS32_SYSCALL_OPEN: { uint8_t filename[256]; fileio_info->identifier = malloc(5); sprintf(fileio_info->identifier, "open"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); + fileio_info->param_1 = reg_r0; /* reserve fileio_info->param_2 for length of path */ - nds32_get_mapped_reg(nds32, R1, &(fileio_info->param_3)); - nds32_get_mapped_reg(nds32, R2, &(fileio_info->param_4)); + fileio_info->param_3 = reg_r1; + fileio_info->param_4 = reg_r2; - target->type->read_buffer(target, fileio_info->param_1, - 256, filename); + target->type->read_buffer(target, reg_r0, 256, filename); fileio_info->param_2 = strlen((char *)filename) + 1; } break; case NDS32_SYSCALL_CLOSE: fileio_info->identifier = malloc(6); sprintf(fileio_info->identifier, "close"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); + fileio_info->param_1 = reg_r0; break; case NDS32_SYSCALL_READ: fileio_info->identifier = malloc(5); sprintf(fileio_info->identifier, "read"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); - nds32_get_mapped_reg(nds32, R1, &(fileio_info->param_2)); - nds32_get_mapped_reg(nds32, R2, &(fileio_info->param_3)); + fileio_info->param_1 = reg_r0; + fileio_info->param_2 = reg_r1; + fileio_info->param_3 = reg_r2; break; case NDS32_SYSCALL_WRITE: fileio_info->identifier = malloc(6); sprintf(fileio_info->identifier, "write"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); - nds32_get_mapped_reg(nds32, R1, &(fileio_info->param_2)); - nds32_get_mapped_reg(nds32, R2, &(fileio_info->param_3)); + fileio_info->param_1 = reg_r0; + fileio_info->param_2 = reg_r1; + fileio_info->param_3 = reg_r2; break; case NDS32_SYSCALL_LSEEK: fileio_info->identifier = malloc(6); sprintf(fileio_info->identifier, "lseek"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); - nds32_get_mapped_reg(nds32, R1, &(fileio_info->param_2)); - nds32_get_mapped_reg(nds32, R2, &(fileio_info->param_3)); + fileio_info->param_1 = reg_r0; + fileio_info->param_2 = reg_r1; + fileio_info->param_3 = reg_r2; break; case NDS32_SYSCALL_UNLINK: { uint8_t filename[256]; fileio_info->identifier = malloc(7); sprintf(fileio_info->identifier, "unlink"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); + fileio_info->param_1 = reg_r0; /* reserve fileio_info->param_2 for length of path */ - target->type->read_buffer(target, fileio_info->param_1, - 256, filename); + target->type->read_buffer(target, reg_r0, 256, filename); fileio_info->param_2 = strlen((char *)filename) + 1; } break; @@ -2404,61 +2407,57 @@ int nds32_get_gdb_fileio_info(struct target *target, struct gdb_fileio_info *fil uint8_t filename[256]; fileio_info->identifier = malloc(7); sprintf(fileio_info->identifier, "rename"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); + fileio_info->param_1 = reg_r0; /* reserve fileio_info->param_2 for length of old path */ - nds32_get_mapped_reg(nds32, R1, &(fileio_info->param_3)); + fileio_info->param_3 = reg_r1; /* reserve fileio_info->param_4 for length of new path */ - target->type->read_buffer(target, fileio_info->param_1, - 256, filename); + target->type->read_buffer(target, reg_r0, 256, filename); fileio_info->param_2 = strlen((char *)filename) + 1; - target->type->read_buffer(target, fileio_info->param_3, - 256, filename); + target->type->read_buffer(target, reg_r1, 256, filename); fileio_info->param_4 = strlen((char *)filename) + 1; } break; case NDS32_SYSCALL_FSTAT: fileio_info->identifier = malloc(6); sprintf(fileio_info->identifier, "fstat"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); - nds32_get_mapped_reg(nds32, R1, &(fileio_info->param_2)); + fileio_info->param_1 = reg_r0; + fileio_info->param_2 = reg_r1; break; case NDS32_SYSCALL_STAT: { uint8_t filename[256]; fileio_info->identifier = malloc(5); sprintf(fileio_info->identifier, "stat"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); + fileio_info->param_1 = reg_r0; /* reserve fileio_info->param_2 for length of old path */ - nds32_get_mapped_reg(nds32, R1, &(fileio_info->param_3)); + fileio_info->param_3 = reg_r1; - target->type->read_buffer(target, fileio_info->param_1, - 256, filename); + target->type->read_buffer(target, reg_r0, 256, filename); fileio_info->param_2 = strlen((char *)filename) + 1; } break; case NDS32_SYSCALL_GETTIMEOFDAY: fileio_info->identifier = malloc(13); sprintf(fileio_info->identifier, "gettimeofday"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); - nds32_get_mapped_reg(nds32, R1, &(fileio_info->param_2)); + fileio_info->param_1 = reg_r0; + fileio_info->param_2 = reg_r1; break; case NDS32_SYSCALL_ISATTY: fileio_info->identifier = malloc(7); sprintf(fileio_info->identifier, "isatty"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); + fileio_info->param_1 = reg_r0; break; case NDS32_SYSCALL_SYSTEM: { uint8_t command[256]; fileio_info->identifier = malloc(7); sprintf(fileio_info->identifier, "system"); - nds32_get_mapped_reg(nds32, R0, &(fileio_info->param_1)); + fileio_info->param_1 = reg_r0; /* reserve fileio_info->param_2 for length of old path */ - target->type->read_buffer(target, fileio_info->param_1, - 256, command); + target->type->read_buffer(target, reg_r0, 256, command); fileio_info->param_2 = strlen((char *)command) + 1; } break; diff --git a/src/target/semihosting_common.c b/src/target/semihosting_common.c new file mode 100644 index 0000000..beeb474 --- /dev/null +++ b/src/target/semihosting_common.c @@ -0,0 +1,1579 @@ +/*************************************************************************** + * Copyright (C) 2018 by Liviu Ionescu * + * <ilg@livius.net> * + * * + * Copyright (C) 2018 by Marvell Technology Group Ltd. * + * Written by Nicolas Pitre <nico@marvell.com> * + * * + * Copyright (C) 2010 by Spencer Oliver * + * spen@spen-soft.co.uk * + * * + * Copyright (C) 2016 by Square, Inc. * + * Steven Stallion <stallion@squareup.com> * + * * + * 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 2 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 <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +/** + * @file + * Common ARM semihosting support. + * + * Semihosting enables code running on a target to use some of the I/O + * facilities on the host computer. The target application must be linked + * against a library that forwards operation requests by using an + * instruction trapped by the debugger. + * + * Details can be found in + * "Semihosting for AArch32 and AArch64, Release 2.0" + * https://static.docs.arm.com/100863/0200/semihosting.pdf + * from ARM Ltd. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "target.h" +#include "target_type.h" +#include "semihosting_common.h" + +#include <helper/binarybuffer.h> +#include <helper/log.h> +#include <sys/stat.h> + +static const int open_modeflags[12] = { + O_RDONLY, + O_RDONLY | O_BINARY, + O_RDWR, + O_RDWR | O_BINARY, + O_WRONLY | O_CREAT | O_TRUNC, + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + O_RDWR | O_CREAT | O_TRUNC, + O_RDWR | O_CREAT | O_TRUNC | O_BINARY, + O_WRONLY | O_CREAT | O_APPEND, + O_WRONLY | O_CREAT | O_APPEND | O_BINARY, + O_RDWR | O_CREAT | O_APPEND, + O_RDWR | O_CREAT | O_APPEND | O_BINARY +}; + +static int semihosting_common_fileio_info(struct target *target, + struct gdb_fileio_info *fileio_info); +static int semihosting_common_fileio_end(struct target *target, int result, + int fileio_errno, bool ctrl_c); + +static int semihosting_read_fields(struct target *target, size_t number, + uint8_t *fields); +static int semihosting_write_fields(struct target *target, size_t number, + uint8_t *fields); +static uint64_t semihosting_get_field(struct target *target, size_t index, + uint8_t *fields); +static void semihosting_set_field(struct target *target, uint64_t value, + size_t index, + uint8_t *fields); + +/* Attempts to include gdb_server.h failed. */ +extern int gdb_actual_connections; + +/** + * Initialize common semihosting support. + * + * @param target Pointer to the target to initialize. + * @return An error status if there is a problem during initialization. + */ +int semihosting_common_init(struct target *target, void *setup, + void *post_result) +{ + LOG_DEBUG(" "); + + target->fileio_info = malloc(sizeof(*target->fileio_info)); + if (target->fileio_info == NULL) { + LOG_ERROR("out of memory"); + return ERROR_FAIL; + } + memset(target->fileio_info, 0, sizeof(*target->fileio_info)); + + struct semihosting *semihosting; + semihosting = malloc(sizeof(*target->semihosting)); + if (semihosting == NULL) { + LOG_ERROR("out of memory"); + return ERROR_FAIL; + } + + semihosting->is_active = false; + semihosting->is_fileio = false; + semihosting->hit_fileio = false; + semihosting->is_resumable = false; + semihosting->has_resumable_exit = false; + semihosting->word_size_bytes = 0; + semihosting->op = -1; + semihosting->param = 0; + semihosting->result = -1; + semihosting->sys_errno = -1; + semihosting->cmdline = NULL; + + /* If possible, update it in setup(). */ + semihosting->setup_time = clock(); + + semihosting->setup = setup; + semihosting->post_result = post_result; + + target->semihosting = semihosting; + + target->type->get_gdb_fileio_info = semihosting_common_fileio_info; + target->type->gdb_fileio_end = semihosting_common_fileio_end; + + return ERROR_OK; +} + +/** + * Portable implementation of ARM semihosting calls. + * Performs the currently pending semihosting operation + * encoded in target->semihosting. + */ +int semihosting_common(struct target *target) +{ + struct semihosting *semihosting = target->semihosting; + if (!semihosting) { + /* Silently ignore if the semhosting field was not set. */ + return ERROR_OK; + } + + struct gdb_fileio_info *fileio_info = target->fileio_info; + + /* + * By default return an error. + * The actual result must be set by each function + */ + semihosting->result = -1; + + /* Most operations are resumable, except the two exit calls. */ + semihosting->is_resumable = true; + + int retval; + + /* Enough space to hold 4 long words. */ + uint8_t fields[4*8]; + + LOG_DEBUG("op=0x%x, param=0x%" PRIx64, (int)semihosting->op, + semihosting->param); + + switch (semihosting->op) { + + case SEMIHOSTING_SYS_CLOCK: /* 0x10 */ + /* + * Returns the number of centiseconds (hundredths of a second) + * since the execution started. + * + * Values returned can be of limited use for some benchmarking + * purposes because of communication overhead or other + * agent-specific factors. For example, with a debug hardware + * unit the request is passed back to the host for execution. + * This can lead to unpredictable delays in transmission and + * process scheduling. + * + * Use this function to calculate time intervals, by calculating + * differences between intervals with and without the code + * sequence to be timed. + * + * Entry + * The PARAMETER REGISTER must contain 0. There are no other + * parameters. + * + * Return + * On exit, the RETURN REGISTER contains: + * - The number of centiseconds since some arbitrary start + * point, if the call is successful. + * - –1 if the call is not successful. For example, because + * of a communications error. + */ + { + clock_t delta = clock() - semihosting->setup_time; + + semihosting->result = delta / (CLOCKS_PER_SEC / 100); + } + break; + + case SEMIHOSTING_SYS_CLOSE: /* 0x02 */ + /* + * Closes a file on the host system. The handle must reference + * a file that was opened with SYS_OPEN. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * one-field argument block: + * - field 1 Contains a handle for an open file. + * + * Return + * On exit, the RETURN REGISTER contains: + * - 0 if the call is successful + * - –1 if the call is not successful. + */ + retval = semihosting_read_fields(target, 1, fields); + if (retval != ERROR_OK) + return retval; + else { + int fd = semihosting_get_field(target, 0, fields); + if (semihosting->is_fileio) { + semihosting->hit_fileio = true; + fileio_info->identifier = "close"; + fileio_info->param_1 = fd; + } else { + semihosting->result = close(fd); + semihosting->sys_errno = errno; + + LOG_DEBUG("close(%d)=%d", fd, (int)semihosting->result); + } + } + break; + + case SEMIHOSTING_SYS_ERRNO: /* 0x13 */ + /* + * Returns the value of the C library errno variable that is + * associated with the semihosting implementation. The errno + * variable can be set by a number of C library semihosted + * functions, including: + * - SYS_REMOVE + * - SYS_OPEN + * - SYS_CLOSE + * - SYS_READ + * - SYS_WRITE + * - SYS_SEEK. + * + * Whether errno is set or not, and to what value, is entirely + * host-specific, except where the ISO C standard defines the + * behavior. + * + * Entry + * There are no parameters. The PARAMETER REGISTER must be 0. + * + * Return + * On exit, the RETURN REGISTER contains the value of the C + * library errno variable. + */ + semihosting->result = semihosting->sys_errno; + break; + + case SEMIHOSTING_SYS_EXIT: /* 0x18 */ + /* + * Note: SYS_EXIT was called angel_SWIreason_ReportException in + * previous versions of the documentation. + * + * An application calls this operation to report an exception + * to the debugger directly. The most common use is to report + * that execution has completed, using ADP_Stopped_ApplicationExit. + * + * Note: This semihosting operation provides no means for 32-bit + * callers to indicate an application exit with a specified exit + * code. Semihosting callers may prefer to check for the presence + * of the SH_EXT_EXTENDED_REPORT_EXCEPTION extension and use + * the SYS_REPORT_EXCEPTION_EXTENDED operation instead, if it + * is available. + * + * Entry (32-bit) + * On entry, the PARAMETER register is set to a reason code + * describing the cause of the trap. Not all semihosting client + * implementations will necessarily trap every corresponding + * event. Important reason codes are: + * + * - ADP_Stopped_ApplicationExit 0x20026 + * - ADP_Stopped_RunTimeErrorUnknown 0x20023 + * + * Entry (64-bit) + * On entry, the PARAMETER REGISTER contains a pointer to a + * two-field argument block: + * - field 1 The exception type, which is one of the set of + * reason codes in the above tables. + * - field 2 A subcode, whose meaning depends on the reason + * code in field 1. + * In particular, if field 1 is ADP_Stopped_ApplicationExit + * then field 2 is an exit status code, as passed to the C + * standard library exit() function. A simulator receiving + * this request must notify a connected debugger, if present, + * and then exit with the specified status. + * + * Return + * No return is expected from these calls. However, it is + * possible for the debugger to request that the application + * continues by performing an RDI_Execute request or equivalent. + * In this case, execution continues with the registers as they + * were on entry to the operation, or as subsequently modified + * by the debugger. + */ + if (semihosting->word_size_bytes == 8) { + retval = semihosting_read_fields(target, 2, fields); + if (retval != ERROR_OK) + return retval; + else { + int type = semihosting_get_field(target, 0, fields); + int code = semihosting_get_field(target, 1, fields); + + if (type == ADP_STOPPED_APPLICATION_EXIT) { + if (!gdb_actual_connections) + exit(code); + else { + fprintf(stderr, + "semihosting: *** application exited with %d ***\n", + code); + } + } else { + fprintf(stderr, + "semihosting: application exception %#x\n", + type); + } + } + } else { + if (semihosting->param == ADP_STOPPED_APPLICATION_EXIT) { + if (!gdb_actual_connections) + exit(0); + else { + fprintf(stderr, + "semihosting: *** application exited normally ***\n"); + } + } else if (semihosting->param == ADP_STOPPED_RUN_TIME_ERROR) { + /* Chosen more or less arbitrarly to have a nicer message, + * otherwise all other return the same exit code 1. */ + if (!gdb_actual_connections) + exit(1); + else { + fprintf(stderr, + "semihosting: *** application exited with error ***\n"); + } + } else { + if (!gdb_actual_connections) + exit(1); + else { + fprintf(stderr, + "semihosting: application exception %#x\n", + (unsigned) semihosting->param); + } + } + } + if (!semihosting->has_resumable_exit) { + semihosting->is_resumable = false; + return target_call_event_callbacks(target, TARGET_EVENT_HALTED); + } + break; + + case SEMIHOSTING_SYS_EXIT_EXTENDED: /* 0x20 */ + /* + * This operation is only supported if the semihosting extension + * SH_EXT_EXIT_EXTENDED is implemented. SH_EXT_EXIT_EXTENDED is + * reported using feature byte 0, bit 0. If this extension is + * supported, then the implementation provides a means to + * report a normal exit with a nonzero exit status in both 32-bit + * and 64-bit semihosting APIs. + * + * The implementation must provide the semihosting call + * SYS_EXIT_EXTENDED for both A64 and A32/T32 semihosting APIs. + * + * SYS_EXIT_EXTENDED is used by an application to report an + * exception or exit to the debugger directly. The most common + * use is to report that execution has completed, using + * ADP_Stopped_ApplicationExit. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * two-field argument block: + * - field 1 The exception type, which should be one of the set + * of reason codes that are documented for the SYS_EXIT + * (0x18) call. For example, ADP_Stopped_ApplicationExit. + * - field 2 A subcode, whose meaning depends on the reason + * code in field 1. In particular, if field 1 is + * ADP_Stopped_ApplicationExit then field 2 is an exit status + * code, as passed to the C standard library exit() function. + * A simulator receiving this request must notify a connected + * debugger, if present, and then exit with the specified status. + * + * Return + * No return is expected from these calls. + * + * For the A64 API, this call is identical to the behavior of + * the mandatory SYS_EXIT (0x18) call. If this extension is + * supported, then both calls must be implemented. + */ + retval = semihosting_read_fields(target, 2, fields); + if (retval != ERROR_OK) + return retval; + else { + int type = semihosting_get_field(target, 0, fields); + int code = semihosting_get_field(target, 1, fields); + + if (type == ADP_STOPPED_APPLICATION_EXIT) { + if (!gdb_actual_connections) + exit(code); + else { + fprintf(stderr, + "semihosting: *** application exited with %d ***\n", + code); + } + } else { + fprintf(stderr, "semihosting: exception %#x\n", + type); + } + } + if (!semihosting->has_resumable_exit) { + semihosting->is_resumable = false; + return target_call_event_callbacks(target, TARGET_EVENT_HALTED); + } + break; + + case SEMIHOSTING_SYS_FLEN: /* 0x0C */ + /* + * Returns the length of a specified file. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * one-field argument block: + * - field 1 A handle for a previously opened, seekable file + * object. + * + * Return + * On exit, the RETURN REGISTER contains: + * - The current length of the file object, if the call is + * successful. + * - –1 if an error occurs. + */ + if (semihosting->is_fileio) { + LOG_ERROR("SYS_FLEN not supported by semihosting fileio"); + return ERROR_FAIL; + } + retval = semihosting_read_fields(target, 1, fields); + if (retval != ERROR_OK) + return retval; + else { + int fd = semihosting_get_field(target, 0, fields); + struct stat buf; + semihosting->result = fstat(fd, &buf); + if (semihosting->result == -1) { + semihosting->sys_errno = errno; + LOG_DEBUG("fstat(%d)=%d", fd, (int)semihosting->result); + break; + } + LOG_DEBUG("fstat(%d)=%d", fd, (int)semihosting->result); + semihosting->result = buf.st_size; + } + break; + + case SEMIHOSTING_SYS_GET_CMDLINE: /* 0x15 */ + /* + * Returns the command line that is used for the call to the + * executable, that is, argc and argv. + * + * Entry + * On entry, the PARAMETER REGISTER points to a two-field data + * block to be used for returning the command string and its length: + * - field 1 A pointer to a buffer of at least the size that is + * specified in field 2. + * - field 2 The length of the buffer in bytes. + * + * Return + * On exit: + * If the call is successful, then the RETURN REGISTER contains 0, + * the PARAMETER REGISTER is unchanged, and the data block is + * updated as follows: + * - field 1 A pointer to a null-terminated string of the command + * line. + * - field 2 The length of the string in bytes. + * If the call is not successful, then the RETURN REGISTER + * contains -1. + * + * Note: The semihosting implementation might impose limits on + * the maximum length of the string that can be transferred. + * However, the implementation must be able to support a + * command-line length of at least 80 bytes. + */ + retval = semihosting_read_fields(target, 2, fields); + if (retval != ERROR_OK) + return retval; + else { + uint64_t addr = semihosting_get_field(target, 0, fields); + size_t size = semihosting_get_field(target, 1, fields); + + char *arg = semihosting->cmdline != NULL ? + semihosting->cmdline : ""; + uint32_t len = strlen(arg) + 1; + if (len > size) + semihosting->result = -1; + else { + semihosting_set_field(target, len, 1, fields); + retval = target_write_buffer(target, addr, len, + (uint8_t *)arg); + if (retval != ERROR_OK) + return retval; + semihosting->result = 0; + + retval = semihosting_write_fields(target, 2, fields); + if (retval != ERROR_OK) + return retval; + } + LOG_DEBUG("SYS_GET_CMDLINE=[%s],%d", arg, + (int)semihosting->result); + } + break; + + case SEMIHOSTING_SYS_HEAPINFO: /* 0x16 */ + /* + * Returns the system stack and heap parameters. + * + * Entry + * On entry, the PARAMETER REGISTER contains the address of a + * pointer to a four-field data block. The contents of the data + * block are filled by the function. The following C-like + * pseudocode describes the layout of the block: + * struct block { + * void* heap_base; + * void* heap_limit; + * void* stack_base; + * void* stack_limit; + * }; + * + * Return + * On exit, the PARAMETER REGISTER is unchanged and the data + * block has been updated. + */ + retval = semihosting_read_fields(target, 1, fields); + if (retval != ERROR_OK) + return retval; + else { + uint64_t addr = semihosting_get_field(target, 0, fields); + /* tell the remote we have no idea */ + memset(fields, 0, 4 * semihosting->word_size_bytes); + retval = target_write_memory(target, addr, 4, + semihosting->word_size_bytes, + fields); + if (retval != ERROR_OK) + return retval; + semihosting->result = 0; + } + break; + + case SEMIHOSTING_SYS_ISERROR: /* 0x08 */ + /* + * Determines whether the return code from another semihosting + * call is an error status or not. + * + * This call is passed a parameter block containing the error + * code to examine. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * one-field data block: + * - field 1 The required status word to check. + * + * Return + * On exit, the RETURN REGISTER contains: + * - 0 if the status field is not an error indication + * - A nonzero value if the status field is an error indication. + */ + retval = semihosting_read_fields(target, 1, fields); + if (retval != ERROR_OK) + return retval; + + uint64_t code = semihosting_get_field(target, 0, fields); + semihosting->result = (code != 0); + break; + + case SEMIHOSTING_SYS_ISTTY: /* 0x09 */ + /* + * Checks whether a file is connected to an interactive device. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * one-field argument block: + * field 1 A handle for a previously opened file object. + * + * Return + * On exit, the RETURN REGISTER contains: + * - 1 if the handle identifies an interactive device. + * - 0 if the handle identifies a file. + * - A value other than 1 or 0 if an error occurs. + */ + if (semihosting->is_fileio) { + semihosting->hit_fileio = true; + fileio_info->identifier = "isatty"; + fileio_info->param_1 = semihosting->param; + } else { + retval = semihosting_read_fields(target, 1, fields); + if (retval != ERROR_OK) + return retval; + int fd = semihosting_get_field(target, 0, fields); + semihosting->result = isatty(fd); + LOG_DEBUG("isatty(%d)=%d", fd, (int)semihosting->result); + } + break; + + case SEMIHOSTING_SYS_OPEN: /* 0x01 */ + /* + * Opens a file on the host system. + * + * The file path is specified either as relative to the current + * directory of the host process, or absolute, using the path + * conventions of the host operating system. + * + * Semihosting implementations must support opening the special + * path name :semihosting-features as part of the semihosting + * extensions reporting mechanism. + * + * ARM targets interpret the special path name :tt as meaning + * the console input stream, for an open-read or the console + * output stream, for an open-write. Opening these streams is + * performed as part of the standard startup code for those + * applications that reference the C stdio streams. The + * semihosting extension SH_EXT_STDOUT_STDERR allows the + * semihosting caller to open separate output streams + * corresponding to stdout and stderr. This extension is + * reported using feature byte 0, bit 1. Use SYS_OPEN with + * the special path name :semihosting-features to access the + * feature bits. + * + * If this extension is supported, the implementation must + * support the following additional semantics to SYS_OPEN: + * - If the special path name :tt is opened with an fopen + * mode requesting write access (w, wb, w+, or w+b), then + * this is a request to open stdout. + * - If the special path name :tt is opened with a mode + * requesting append access (a, ab, a+, or a+b), then this is + * a request to open stderr. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * three-field argument block: + * - field 1 A pointer to a null-terminated string containing + * a file or device name. + * - field 2 An integer that specifies the file opening mode. + * - field 3 An integer that gives the length of the string + * pointed to by field 1. + * + * The length does not include the terminating null character + * that must be present. + * + * Return + * On exit, the RETURN REGISTER contains: + * - A nonzero handle if the call is successful. + * - –1 if the call is not successful. + */ + retval = semihosting_read_fields(target, 3, fields); + if (retval != ERROR_OK) + return retval; + else { + uint64_t addr = semihosting_get_field(target, 0, fields); + uint32_t mode = semihosting_get_field(target, 1, fields); + size_t len = semihosting_get_field(target, 2, fields); + + if (mode > 11) { + semihosting->result = -1; + semihosting->sys_errno = EINVAL; + break; + } + uint8_t *fn = malloc(len+1); + if (!fn) { + semihosting->result = -1; + semihosting->sys_errno = ENOMEM; + } else { + retval = target_read_memory(target, addr, 1, len, fn); + if (retval != ERROR_OK) { + free(fn); + return retval; + } + fn[len] = 0; + /* TODO: implement the :semihosting-features special file. + * */ + if (semihosting->is_fileio) { + if (strcmp((char *)fn, ":tt") == 0) + semihosting->result = 0; + else { + semihosting->hit_fileio = true; + fileio_info->identifier = "open"; + fileio_info->param_1 = addr; + fileio_info->param_2 = len; + fileio_info->param_3 = open_modeflags[mode]; + fileio_info->param_4 = 0644; + } + } else { + if (strcmp((char *)fn, ":tt") == 0) { + /* Mode is: + * - 0-3 ("r") for stdin, + * - 4-7 ("w") for stdout, + * - 8-11 ("a") for stderr */ + if (mode < 4) { + semihosting->result = dup( + STDIN_FILENO); + semihosting->sys_errno = errno; + LOG_DEBUG("dup(STDIN)=%d", + (int)semihosting->result); + } else if (mode < 8) { + semihosting->result = dup( + STDOUT_FILENO); + semihosting->sys_errno = errno; + LOG_DEBUG("dup(STDOUT)=%d", + (int)semihosting->result); + } else { + semihosting->result = dup( + STDERR_FILENO); + semihosting->sys_errno = errno; + LOG_DEBUG("dup(STDERR)=%d", + (int)semihosting->result); + } + } else { + /* cygwin requires the permission setting + * otherwise it will fail to reopen a previously + * written file */ + semihosting->result = open((char *)fn, + open_modeflags[mode], + 0644); + semihosting->sys_errno = errno; + LOG_DEBUG("open('%s')=%d", fn, + (int)semihosting->result); + } + } + free(fn); + } + } + break; + + case SEMIHOSTING_SYS_READ: /* 0x06 */ + /* + * Reads the contents of a file into a buffer. The file position + * is specified either: + * - Explicitly by a SYS_SEEK. + * - Implicitly one byte beyond the previous SYS_READ or + * SYS_WRITE request. + * + * The file position is at the start of the file when it is + * opened, and is lost when the file is closed. Perform the + * file operation as a single action whenever possible. For + * example, do not split a read of 16KB into four 4KB chunks + * unless there is no alternative. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * three-field data block: + * - field 1 Contains a handle for a file previously opened + * with SYS_OPEN. + * - field 2 Points to a buffer. + * - field 3 Contains the number of bytes to read to the buffer + * from the file. + * + * Return + * On exit, the RETURN REGISTER contains the number of bytes not + * filled in the buffer (buffer_length - bytes_read) as follows: + * - If the RETURN REGISTER is 0, the entire buffer was + * successfully filled. + * - If the RETURN REGISTER is the same as field 3, no bytes + * were read (EOF can be assumed). + * - If the RETURN REGISTER contains a value smaller than + * field 3, the read succeeded but the buffer was only partly + * filled. For interactive devices, this is the most common + * return value. + */ + retval = semihosting_read_fields(target, 3, fields); + if (retval != ERROR_OK) + return retval; + else { + int fd = semihosting_get_field(target, 0, fields); + uint64_t addr = semihosting_get_field(target, 1, fields); + size_t len = semihosting_get_field(target, 2, fields); + if (semihosting->is_fileio) { + semihosting->hit_fileio = true; + fileio_info->identifier = "read"; + fileio_info->param_1 = fd; + fileio_info->param_2 = addr; + fileio_info->param_3 = len; + } else { + uint8_t *buf = malloc(len); + if (!buf) { + semihosting->result = -1; + semihosting->sys_errno = ENOMEM; + } else { + semihosting->result = read(fd, buf, len); + semihosting->sys_errno = errno; + LOG_DEBUG("read(%d, 0x%" PRIx64 ", %zu)=%d", + fd, + addr, + len, + (int)semihosting->result); + if (semihosting->result >= 0) { + retval = target_write_buffer(target, addr, + semihosting->result, + buf); + if (retval != ERROR_OK) { + free(buf); + return retval; + } + /* the number of bytes NOT filled in */ + semihosting->result = len - + semihosting->result; + } + free(buf); + } + } + } + break; + + case SEMIHOSTING_SYS_READC: /* 0x07 */ + /* + * Reads a byte from the console. + * + * Entry + * The PARAMETER REGISTER must contain 0. There are no other + * parameters or values possible. + * + * Return + * On exit, the RETURN REGISTER contains the byte read from + * the console. + */ + if (semihosting->is_fileio) { + LOG_ERROR("SYS_READC not supported by semihosting fileio"); + return ERROR_FAIL; + } + semihosting->result = getchar(); + LOG_DEBUG("getchar()=%d", (int)semihosting->result); + break; + + case SEMIHOSTING_SYS_REMOVE: /* 0x0E */ + /* + * Deletes a specified file on the host filing system. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * two-field argument block: + * - field 1 Points to a null-terminated string that gives the + * path name of the file to be deleted. + * - field 2 The length of the string. + * + * Return + * On exit, the RETURN REGISTER contains: + * - 0 if the delete is successful + * - A nonzero, host-specific error code if the delete fails. + */ + retval = semihosting_read_fields(target, 2, fields); + if (retval != ERROR_OK) + return retval; + else { + uint64_t addr = semihosting_get_field(target, 0, fields); + size_t len = semihosting_get_field(target, 1, fields); + if (semihosting->is_fileio) { + semihosting->hit_fileio = true; + fileio_info->identifier = "unlink"; + fileio_info->param_1 = addr; + fileio_info->param_2 = len; + } else { + uint8_t *fn = malloc(len+1); + if (!fn) { + semihosting->result = -1; + semihosting->sys_errno = ENOMEM; + } else { + retval = + target_read_memory(target, addr, 1, len, + fn); + if (retval != ERROR_OK) { + free(fn); + return retval; + } + fn[len] = 0; + semihosting->result = remove((char *)fn); + semihosting->sys_errno = errno; + LOG_DEBUG("remove('%s')=%d", fn, + (int)semihosting->result); + + free(fn); + } + } + } + break; + + case SEMIHOSTING_SYS_RENAME: /* 0x0F */ + /* + * Renames a specified file. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * four-field data block: + * - field 1 A pointer to the name of the old file. + * - field 2 The length of the old filename. + * - field 3 A pointer to the new filename. + * - field 4 The length of the new filename. Both strings are + * null-terminated. + * + * Return + * On exit, the RETURN REGISTER contains: + * - 0 if the rename is successful. + * - A nonzero, host-specific error code if the rename fails. + */ + retval = semihosting_read_fields(target, 4, fields); + if (retval != ERROR_OK) + return retval; + else { + uint64_t addr1 = semihosting_get_field(target, 0, fields); + size_t len1 = semihosting_get_field(target, 1, fields); + uint64_t addr2 = semihosting_get_field(target, 2, fields); + size_t len2 = semihosting_get_field(target, 3, fields); + if (semihosting->is_fileio) { + semihosting->hit_fileio = true; + fileio_info->identifier = "rename"; + fileio_info->param_1 = addr1; + fileio_info->param_2 = len1; + fileio_info->param_3 = addr2; + fileio_info->param_4 = len2; + } else { + uint8_t *fn1 = malloc(len1+1); + uint8_t *fn2 = malloc(len2+1); + if (!fn1 || !fn2) { + semihosting->result = -1; + semihosting->sys_errno = ENOMEM; + } else { + retval = target_read_memory(target, addr1, 1, len1, + fn1); + if (retval != ERROR_OK) { + free(fn1); + free(fn2); + return retval; + } + retval = target_read_memory(target, addr2, 1, len2, + fn2); + if (retval != ERROR_OK) { + free(fn1); + free(fn2); + return retval; + } + fn1[len1] = 0; + fn2[len2] = 0; + semihosting->result = rename((char *)fn1, + (char *)fn2); + semihosting->sys_errno = errno; + LOG_DEBUG("rename('%s', '%s')=%d", fn1, fn2, + (int)semihosting->result); + + free(fn1); + free(fn2); + } + } + } + break; + + case SEMIHOSTING_SYS_SEEK: /* 0x0A */ + /* + * Seeks to a specified position in a file using an offset + * specified from the start of the file. The file is assumed + * to be a byte array and the offset is given in bytes. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * two-field data block: + * - field 1 A handle for a seekable file object. + * - field 2 The absolute byte position to seek to. + * + * Return + * On exit, the RETURN REGISTER contains: + * - 0 if the request is successful. + * - A negative value if the request is not successful. + * Use SYS_ERRNO to read the value of the host errno variable + * describing the error. + * + * Note: The effect of seeking outside the current extent of + * the file object is undefined. + */ + retval = semihosting_read_fields(target, 2, fields); + if (retval != ERROR_OK) + return retval; + else { + int fd = semihosting_get_field(target, 0, fields); + off_t pos = semihosting_get_field(target, 1, fields); + if (semihosting->is_fileio) { + semihosting->hit_fileio = true; + fileio_info->identifier = "lseek"; + fileio_info->param_1 = fd; + fileio_info->param_2 = pos; + fileio_info->param_3 = SEEK_SET; + } else { + semihosting->result = lseek(fd, pos, SEEK_SET); + semihosting->sys_errno = errno; + LOG_DEBUG("lseek(%d, %d)=%d", fd, (int)pos, + (int)semihosting->result); + if (semihosting->result == pos) + semihosting->result = 0; + } + } + break; + + case SEMIHOSTING_SYS_SYSTEM: /* 0x12 */ + /* + * Passes a command to the host command-line interpreter. + * This enables you to execute a system command such as dir, + * ls, or pwd. The terminal I/O is on the host, and is not + * visible to the target. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * two-field argument block: + * - field 1 Points to a string to be passed to the host + * command-line interpreter. + * - field 2 The length of the string. + * + * Return + * On exit, the RETURN REGISTER contains the return status. + */ + + /* Provide SYS_SYSTEM functionality. Uses the + * libc system command, there may be a reason *NOT* + * to use this, but as I can't think of one, I + * implemented it this way. + */ + retval = semihosting_read_fields(target, 2, fields); + if (retval != ERROR_OK) + return retval; + else { + uint64_t addr = semihosting_get_field(target, 0, fields); + size_t len = semihosting_get_field(target, 1, fields); + if (semihosting->is_fileio) { + semihosting->hit_fileio = true; + fileio_info->identifier = "system"; + fileio_info->param_1 = addr; + fileio_info->param_2 = len; + } else { + uint8_t *cmd = malloc(len+1); + if (!cmd) { + semihosting->result = -1; + semihosting->sys_errno = ENOMEM; + } else { + retval = target_read_memory(target, + addr, + 1, + len, + cmd); + if (retval != ERROR_OK) { + free(cmd); + return retval; + } else { + cmd[len] = 0; + semihosting->result = system( + (const char *)cmd); + LOG_DEBUG("system('%s')=%d", + cmd, + (int)semihosting->result); + } + + free(cmd); + } + } + } + break; + + case SEMIHOSTING_SYS_TIME: /* 0x11 */ + /* + * Returns the number of seconds since 00:00 January 1, 1970. + * This value is real-world time, regardless of any debug agent + * configuration. + * + * Entry + * There are no parameters. + * + * Return + * On exit, the RETURN REGISTER contains the number of seconds. + */ + semihosting->result = time(NULL); + break; + + case SEMIHOSTING_SYS_WRITE: /* 0x05 */ + /* + * Writes the contents of a buffer to a specified file at the + * current file position. The file position is specified either: + * - Explicitly, by a SYS_SEEK. + * - Implicitly as one byte beyond the previous SYS_READ or + * SYS_WRITE request. + * + * The file position is at the start of the file when the file + * is opened, and is lost when the file is closed. + * + * Perform the file operation as a single action whenever + * possible. For example, do not split a write of 16KB into + * four 4KB chunks unless there is no alternative. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * three-field data block: + * - field 1 Contains a handle for a file previously opened + * with SYS_OPEN. + * - field 2 Points to the memory containing the data to be written. + * - field 3 Contains the number of bytes to be written from + * the buffer to the file. + * + * Return + * On exit, the RETURN REGISTER contains: + * - 0 if the call is successful. + * - The number of bytes that are not written, if there is an error. + */ + retval = semihosting_read_fields(target, 3, fields); + if (retval != ERROR_OK) + return retval; + else { + int fd = semihosting_get_field(target, 0, fields); + uint64_t addr = semihosting_get_field(target, 1, fields); + size_t len = semihosting_get_field(target, 2, fields); + if (semihosting->is_fileio) { + semihosting->hit_fileio = true; + fileio_info->identifier = "write"; + fileio_info->param_1 = fd; + fileio_info->param_2 = addr; + fileio_info->param_3 = len; + } else { + uint8_t *buf = malloc(len); + if (!buf) { + semihosting->result = -1; + semihosting->sys_errno = ENOMEM; + } else { + retval = target_read_buffer(target, addr, len, buf); + if (retval != ERROR_OK) { + free(buf); + return retval; + } + semihosting->result = write(fd, buf, len); + semihosting->sys_errno = errno; + LOG_DEBUG("write(%d, 0x%" PRIx64 ", %zu)=%d", + fd, + addr, + len, + (int)semihosting->result); + if (semihosting->result >= 0) { + /* The number of bytes that are NOT written. + * */ + semihosting->result = len - + semihosting->result; + } + + free(buf); + } + } + } + break; + + case SEMIHOSTING_SYS_WRITEC: /* 0x03 */ + /* + * Writes a character byte, pointed to by the PARAMETER REGISTER, + * to the debug channel. When executed under a semihosting + * debugger, the character appears on the host debugger console. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to the + * character. + * + * Return + * None. The RETURN REGISTER is corrupted. + */ + if (semihosting->is_fileio) { + semihosting->hit_fileio = true; + fileio_info->identifier = "write"; + fileio_info->param_1 = 1; + fileio_info->param_2 = semihosting->param; + fileio_info->param_3 = 1; + } else { + uint64_t addr = semihosting->param; + unsigned char c; + retval = target_read_memory(target, addr, 1, 1, &c); + if (retval != ERROR_OK) + return retval; + putchar(c); + semihosting->result = 0; + } + break; + + case SEMIHOSTING_SYS_WRITE0: /* 0x04 */ + /* + * Writes a null-terminated string to the debug channel. + * When executed under a semihosting debugger, the characters + * appear on the host debugger console. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to the + * first byte of the string. + * + * Return + * None. The RETURN REGISTER is corrupted. + */ + if (semihosting->is_fileio) { + size_t count = 0; + uint64_t addr = semihosting->param; + for (;; addr++) { + unsigned char c; + retval = target_read_memory(target, addr, 1, 1, &c); + if (retval != ERROR_OK) + return retval; + if (c == '\0') + break; + count++; + } + semihosting->hit_fileio = true; + fileio_info->identifier = "write"; + fileio_info->param_1 = 1; + fileio_info->param_2 = semihosting->param; + fileio_info->param_3 = count; + } else { + uint64_t addr = semihosting->param; + do { + unsigned char c; + retval = target_read_memory(target, addr++, 1, 1, &c); + if (retval != ERROR_OK) + return retval; + if (!c) + break; + putchar(c); + } while (1); + semihosting->result = 0; + } + break; + + case SEMIHOSTING_SYS_ELAPSED: /* 0x30 */ + /* + * Returns the number of elapsed target ticks since execution + * started. + * Use SYS_TICKFREQ to determine the tick frequency. + * + * Entry (32-bit) + * On entry, the PARAMETER REGISTER points to a two-field data + * block to be used for returning the number of elapsed ticks: + * - field 1 The least significant field and is at the low address. + * - field 2 The most significant field and is at the high address. + * + * Entry (64-bit) + * On entry the PARAMETER REGISTER points to a one-field data + * block to be used for returning the number of elapsed ticks: + * - field 1 The number of elapsed ticks as a 64-bit value. + * + * Return + * On exit: + * - On success, the RETURN REGISTER contains 0, the PARAMETER + * REGISTER is unchanged, and the data block pointed to by the + * PARAMETER REGISTER is filled in with the number of elapsed + * ticks. + * - On failure, the RETURN REGISTER contains -1, and the + * PARAMETER REGISTER contains -1. + * + * Note: Some semihosting implementations might not support this + * semihosting operation, and they always return -1 in the + * RETURN REGISTER. + */ + + case SEMIHOSTING_SYS_TICKFREQ: /* 0x31 */ + /* + * Returns the tick frequency. + * + * Entry + * The PARAMETER REGISTER must contain 0 on entry to this routine. + * + * Return + * On exit, the RETURN REGISTER contains either: + * - The number of ticks per second. + * - –1 if the target does not know the value of one tick. + * + * Note: Some semihosting implementations might not support + * this semihosting operation, and they always return -1 in the + * RETURN REGISTER. + */ + + case SEMIHOSTING_SYS_TMPNAM: /* 0x0D */ + /* + * Returns a temporary name for a file identified by a system + * file identifier. + * + * Entry + * On entry, the PARAMETER REGISTER contains a pointer to a + * three-word argument block: + * - field 1 A pointer to a buffer. + * - field 2 A target identifier for this filename. Its value + * must be an integer in the range 0-255. + * - field 3 Contains the length of the buffer. The length must + * be at least the value of L_tmpnam on the host system. + * + * Return + * On exit, the RETURN REGISTER contains: + * - 0 if the call is successful. + * - –1 if an error occurs. + * + * The buffer pointed to by the PARAMETER REGISTER contains + * the filename, prefixed with a suitable directory name. + * If you use the same target identifier again, the same + * filename is returned. + * + * Note: The returned string must be null-terminated. + */ + + default: + fprintf(stderr, "semihosting: unsupported call %#x\n", + (unsigned) semihosting->op); + semihosting->result = -1; + semihosting->sys_errno = ENOTSUP; + } + + if (!semihosting->hit_fileio) { + retval = semihosting->post_result(target); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to post semihosting result"); + return retval; + } + } + + return ERROR_OK; +} + +/* ------------------------------------------------------------------------- + * Local functions. */ + +static int semihosting_common_fileio_info(struct target *target, + struct gdb_fileio_info *fileio_info) +{ + struct semihosting *semihosting = target->semihosting; + if (!semihosting) + return ERROR_FAIL; + + /* + * To avoid unnecessary duplication, semihosting prepares the + * fileio_info structure out-of-band when the target halts. See + * do_semihosting for more detail. + */ + if (!semihosting->is_fileio || !semihosting->hit_fileio) + return ERROR_FAIL; + + return ERROR_OK; +} + +static int semihosting_common_fileio_end(struct target *target, int result, + int fileio_errno, bool ctrl_c) +{ + struct gdb_fileio_info *fileio_info = target->fileio_info; + struct semihosting *semihosting = target->semihosting; + if (!semihosting) + return ERROR_FAIL; + + /* clear pending status */ + semihosting->hit_fileio = false; + + semihosting->result = result; + semihosting->sys_errno = fileio_errno; + + /* + * Some fileio results do not match up with what the semihosting + * operation expects; for these operations, we munge the results + * below: + */ + switch (semihosting->op) { + case SEMIHOSTING_SYS_WRITE: /* 0x05 */ + if (result < 0) + semihosting->result = fileio_info->param_3; + else + semihosting->result = 0; + break; + + case SEMIHOSTING_SYS_READ: /* 0x06 */ + if (result == (int)fileio_info->param_3) + semihosting->result = 0; + if (result <= 0) + semihosting->result = fileio_info->param_3; + break; + + case SEMIHOSTING_SYS_SEEK: /* 0x0a */ + if (result > 0) + semihosting->result = 0; + break; + } + + return semihosting->post_result(target); +} + +/** + * Read all fields of a command from target to buffer. + */ +static int semihosting_read_fields(struct target *target, size_t number, + uint8_t *fields) +{ + struct semihosting *semihosting = target->semihosting; + return target_read_memory(target, semihosting->param, + semihosting->word_size_bytes, number, fields); +} + +/** + * Write all fields of a command from buffer to target. + */ +static int semihosting_write_fields(struct target *target, size_t number, + uint8_t *fields) +{ + struct semihosting *semihosting = target->semihosting; + return target_write_memory(target, semihosting->param, + semihosting->word_size_bytes, number, fields); +} + +/** + * Extract a field from the buffer, considering register size and endianness. + */ +static uint64_t semihosting_get_field(struct target *target, size_t index, + uint8_t *fields) +{ + struct semihosting *semihosting = target->semihosting; + if (semihosting->word_size_bytes == 8) + return target_buffer_get_u64(target, fields + (index * 8)); + else + return target_buffer_get_u32(target, fields + (index * 4)); +} + +/** + * Store a field in the buffer, considering register size and endianness. + */ +static void semihosting_set_field(struct target *target, uint64_t value, + size_t index, + uint8_t *fields) +{ + struct semihosting *semihosting = target->semihosting; + if (semihosting->word_size_bytes == 8) + target_buffer_set_u64(target, fields + (index * 8), value); + else + target_buffer_set_u32(target, fields + (index * 4), value); +} + + +/* ------------------------------------------------------------------------- + * Common semihosting commands handlers. */ + +__COMMAND_HANDLER(handle_common_semihosting_command) +{ + struct target *target = get_current_target(CMD_CTX); + + if (target == NULL) { + LOG_ERROR("No target selected"); + return ERROR_FAIL; + } + + struct semihosting *semihosting = target->semihosting; + if (!semihosting) { + command_print(CMD_CTX, "semihosting not supported for current target"); + return ERROR_FAIL; + } + + if (CMD_ARGC > 0) { + int is_active; + + COMMAND_PARSE_ENABLE(CMD_ARGV[0], is_active); + + if (!target_was_examined(target)) { + LOG_ERROR("Target not examined yet"); + return ERROR_FAIL; + } + + if (semihosting && semihosting->setup(target, is_active) != ERROR_OK) { + LOG_ERROR("Failed to Configure semihosting"); + return ERROR_FAIL; + } + + /* FIXME never let that "catch" be dropped! (???) */ + semihosting->is_active = is_active; + } + + command_print(CMD_CTX, "semihosting is %s", + semihosting->is_active + ? "enabled" : "disabled"); + + return ERROR_OK; +} + + +__COMMAND_HANDLER(handle_common_semihosting_fileio_command) +{ + struct target *target = get_current_target(CMD_CTX); + + if (target == NULL) { + LOG_ERROR("No target selected"); + return ERROR_FAIL; + } + + struct semihosting *semihosting = target->semihosting; + if (!semihosting) { + command_print(CMD_CTX, "semihosting not supported for current target"); + return ERROR_FAIL; + } + + if (!semihosting->is_active) { + command_print(CMD_CTX, "semihosting not yet enabled for current target"); + return ERROR_FAIL; + } + + if (CMD_ARGC > 0) + COMMAND_PARSE_ENABLE(CMD_ARGV[0], semihosting->is_fileio); + + command_print(CMD_CTX, "semihosting fileio is %s", + semihosting->is_fileio + ? "enabled" : "disabled"); + + return ERROR_OK; +} + +__COMMAND_HANDLER(handle_common_semihosting_cmdline) +{ + struct target *target = get_current_target(CMD_CTX); + unsigned int i; + + if (target == NULL) { + LOG_ERROR("No target selected"); + return ERROR_FAIL; + } + + struct semihosting *semihosting = target->semihosting; + if (!semihosting) { + command_print(CMD_CTX, "semihosting not supported for current target"); + return ERROR_FAIL; + } + + free(semihosting->cmdline); + semihosting->cmdline = CMD_ARGC > 0 ? strdup(CMD_ARGV[0]) : NULL; + + for (i = 1; i < CMD_ARGC; i++) { + char *cmdline = alloc_printf("%s %s", semihosting->cmdline, CMD_ARGV[i]); + if (cmdline == NULL) + break; + free(semihosting->cmdline); + semihosting->cmdline = cmdline; + } + + command_print(CMD_CTX, "semihosting command line is [%s]", + semihosting->cmdline); + + return ERROR_OK; +} + +__COMMAND_HANDLER(handle_common_semihosting_resumable_exit_command) +{ + struct target *target = get_current_target(CMD_CTX); + + if (target == NULL) { + LOG_ERROR("No target selected"); + return ERROR_FAIL; + } + + struct semihosting *semihosting = target->semihosting; + if (!semihosting) { + command_print(CMD_CTX, "semihosting not supported for current target"); + return ERROR_FAIL; + } + + if (!semihosting->is_active) { + command_print(CMD_CTX, "semihosting not yet enabled for current target"); + return ERROR_FAIL; + } + + if (CMD_ARGC > 0) + COMMAND_PARSE_ENABLE(CMD_ARGV[0], semihosting->has_resumable_exit); + + command_print(CMD_CTX, "semihosting resumable exit is %s", + semihosting->has_resumable_exit + ? "enabled" : "disabled"); + + return ERROR_OK; +} diff --git a/src/target/semihosting_common.h b/src/target/semihosting_common.h new file mode 100644 index 0000000..8fb5e0c --- /dev/null +++ b/src/target/semihosting_common.h @@ -0,0 +1,163 @@ +/*************************************************************************** + * Copyright (C) 2018 by Liviu Ionescu * + * <ilg@livius.net> * + * * + * Copyright (C) 2009 by Marvell Technology Group Ltd. * + * Written by Nicolas Pitre <nico@marvell.com> * + * * + * 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 2 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 <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#ifndef OPENOCD_TARGET_SEMIHOSTING_COMMON_H +#define OPENOCD_TARGET_SEMIHOSTING_COMMON_H + +#include <stdint.h> +#include <stdbool.h> +#include <time.h> + +/* + * According to: + * "Semihosting for AArch32 and AArch64, Release 2.0" + * https://static.docs.arm.com/100863/0200/semihosting.pdf + * from ARM Ltd. + * + * The available semihosting operation numbers passed in R0 are allocated + * as follows: + * - 0x00-0x31 Used by ARM. + * - 0x32-0xFF Reserved for future use by ARM. + * - 0x100-0x1FF Reserved for user applications. These are not used by ARM. + * However, if you are writing your own SVC operations, you are advised + * to use a different SVC number rather than using the semihosted + * SVC number and these operation type numbers. + * - 0x200-0xFFFFFFFF Undefined and currently unused. It is recommended + * that you do not use these. + */ + +enum semihosting_operation_numbers { + /* + * ARM semihosting operations, in lexicographic order. + */ + SEMIHOSTING_ENTER_SVC = 0x17, /* DEPRECATED */ + + SEMIHOSTING_SYS_CLOSE = 0x02, + SEMIHOSTING_SYS_CLOCK = 0x10, + SEMIHOSTING_SYS_ELAPSED = 0x30, + SEMIHOSTING_SYS_ERRNO = 0x13, + SEMIHOSTING_SYS_EXIT = 0x18, + SEMIHOSTING_SYS_EXIT_EXTENDED = 0x20, + SEMIHOSTING_SYS_FLEN = 0x0C, + SEMIHOSTING_SYS_GET_CMDLINE = 0x15, + SEMIHOSTING_SYS_HEAPINFO = 0x16, + SEMIHOSTING_SYS_ISERROR = 0x08, + SEMIHOSTING_SYS_ISTTY = 0x09, + SEMIHOSTING_SYS_OPEN = 0x01, + SEMIHOSTING_SYS_READ = 0x06, + SEMIHOSTING_SYS_READC = 0x07, + SEMIHOSTING_SYS_REMOVE = 0x0E, + SEMIHOSTING_SYS_RENAME = 0x0F, + SEMIHOSTING_SYS_SEEK = 0x0A, + SEMIHOSTING_SYS_SYSTEM = 0x12, + SEMIHOSTING_SYS_TICKFREQ = 0x31, + SEMIHOSTING_SYS_TIME = 0x11, + SEMIHOSTING_SYS_TMPNAM = 0x0D, + SEMIHOSTING_SYS_WRITE = 0x05, + SEMIHOSTING_SYS_WRITEC = 0x03, + SEMIHOSTING_SYS_WRITE0 = 0x04, +}; + +/* + * Codes used by SEMIHOSTING_SYS_EXIT (formerly + * SEMIHOSTING_REPORT_EXCEPTION). + * On 64-bits, the exit code is passed explicitly. + */ +enum semihosting_reported_exceptions { + /* On 32 bits, use it for exit(0) */ + ADP_STOPPED_APPLICATION_EXIT = ((2 << 16) + 38), + /* On 32 bits, use it for exit(1) */ + ADP_STOPPED_RUN_TIME_ERROR = ((2 << 16) + 35), +}; + +struct target; + +/* + * A pointer to this structure was added to the target structure. + */ +struct semihosting { + + /** A flag reporting whether semihosting is active. */ + bool is_active; + + /** A flag reporting whether semihosting fileio is active. */ + bool is_fileio; + + /** A flag reporting whether semihosting fileio operation is active. */ + bool hit_fileio; + + /** Most are resumable, except the two exit calls. */ + bool is_resumable; + + /** + * When SEMIHOSTING_SYS_EXIT is called outside a debug session, + * things are simple, the openocd process calls exit() and passes + * the value returned by the target. + * When SEMIHOSTING_SYS_EXIT is called during a debug session, + * by default execution returns to the debugger, leaving the + * debugger in a HALT state, similar to the state entered when + * encountering a break. + * In some use cases, it is useful to have SEMIHOSTING_SYS_EXIT + * return normally, as any semihosting call, and do not break + * to the debugger. + * The standard allows this to happen, but the condition + * to trigger it is a bit obscure ("by performing an RDI_Execute + * request or equivalent"). + * + * To make the SEMIHOSTING_SYS_EXIT call return normally, enable + * this variable via the dedicated command (default: disabled). + */ + bool has_resumable_exit; + + /** The Target (hart) word size; 8 for 64-bits targets. */ + size_t word_size_bytes; + + /** The current semihosting operation (R0 on ARM). */ + int op; + + /** The current semihosting parameter (R1 or ARM). */ + uint64_t param; + + /** + * The current semihosting result to be returned to the application. + * Usually 0 for success, -1 for error, + * but sometimes a useful value, even a pointer. + */ + int64_t result; + + /** The value to be returned by semihosting SYS_ERRNO request. */ + int sys_errno; + + /** The semihosting command line to be passed to the target. */ + char *cmdline; + + /** The current time when 'execution starts' */ + clock_t setup_time; + + int (*setup)(struct target *target, int enable); + int (*post_result)(struct target *target); +}; + +int semihosting_common_init(struct target *target, void *setup, + void *post_result); +int semihosting_common(struct target *target); + +#endif /* OPENOCD_TARGET_SEMIHOSTING_COMMON_H */ diff --git a/src/target/target.c b/src/target/target.c index 890f727..dc2cedd 100644 --- a/src/target/target.c +++ b/src/target/target.c @@ -1895,6 +1895,9 @@ static void target_destroy(struct target *target) if (target->type->deinit_target) target->type->deinit_target(target); + if (target->semihosting) + free(target->semihosting); + jtag_unregister_event_callback(jtag_enable_callback, target); struct target_event_action *teap = target->event_action; @@ -5448,21 +5451,19 @@ static const struct command_registration target_instance_command_handlers[] = { .mode = COMMAND_EXEC, .jim_handler = jim_target_examine, .help = "used internally for reset processing", - .usage = "arp_examine ['allow-defer']", + .usage = "['allow-defer']", }, { .name = "was_examined", .mode = COMMAND_EXEC, .jim_handler = jim_target_was_examined, .help = "used internally for reset processing", - .usage = "was_examined", }, { .name = "examine_deferred", .mode = COMMAND_EXEC, .jim_handler = jim_target_examine_deferred, .help = "used internally for reset processing", - .usage = "examine_deferred", }, { .name = "arp_halt_gdb", diff --git a/src/target/target.h b/src/target/target.h index c5fb55b..51a5b69 100644 --- a/src/target/target.h +++ b/src/target/target.h @@ -205,6 +205,9 @@ struct target { /* file-I/O information for host to do syscall */ struct gdb_fileio_info *fileio_info; + + /* The semihosting information, extracted from the target. */ + struct semihosting *semihosting; }; struct target_list { @@ -214,10 +217,10 @@ struct target_list { struct gdb_fileio_info { char *identifier; - uint32_t param_1; - uint32_t param_2; - uint32_t param_3; - uint32_t param_4; + uint64_t param_1; + uint64_t param_2; + uint64_t param_3; + uint64_t param_4; }; /** Returns the instance-specific name of the specified target. */ diff --git a/tcl/board/ti_cc3220sf_launchpad.cfg b/tcl/board/ti_cc3220sf_launchpad.cfg new file mode 100644 index 0000000..a3dac62 --- /dev/null +++ b/tcl/board/ti_cc3220sf_launchpad.cfg @@ -0,0 +1,7 @@ +# +# TI CC3220SF-LaunchXL LaunchPad Evaluation Kit +# +source [find interface/xds110.cfg] +adapter_khz 2500 +transport select swd +source [find target/ti_cc3220sf.cfg] diff --git a/tcl/board/ti_cc32xx_launchpad.cfg b/tcl/board/ti_cc32xx_launchpad.cfg new file mode 100644 index 0000000..f657bdf --- /dev/null +++ b/tcl/board/ti_cc32xx_launchpad.cfg @@ -0,0 +1,7 @@ +# +# TI CC32xx-LaunchXL LaunchPad Evaluation Kit +# +source [find interface/xds110.cfg] +adapter_khz 2500 +transport select swd +source [find target/ti_cc32xx.cfg] diff --git a/tcl/interface/xds110.cfg b/tcl/interface/xds110.cfg new file mode 100644 index 0000000..495e202 --- /dev/null +++ b/tcl/interface/xds110.cfg @@ -0,0 +1,12 @@ +# +# Texas Instruments XDS110 +# +# http://processors.wiki.ti.com/index.php/XDS110 +# http://processors.wiki.ti.com/index.php/Emulation_Software_Package#XDS110_Support_Utilities +# + +interface xds110 + +# Use serial number option to use a specific XDS110 +# when more than one are connected to the host. +#xds110_serial 00000000 diff --git a/tcl/target/cc32xx.cfg b/tcl/target/cc32xx.cfg deleted file mode 100755 index dfc4c17..0000000 --- a/tcl/target/cc32xx.cfg +++ /dev/null @@ -1,54 +0,0 @@ -# Config for Texas Instruments SoC CC32xx family - -source [find target/swj-dp.tcl] - -adapter_khz 100 - -source [find target/icepick.cfg] - -if { [info exists CHIPNAME] } { - set _CHIPNAME $CHIPNAME -} else { - set _CHIPNAME cc32xx -} - -# -# Main DAP -# -if { [info exists DAP_TAPID] } { - set _DAP_TAPID $DAP_TAPID -} else { - if {[using_jtag]} { - set _DAP_TAPID 0x4BA00477 - } else { - set _DAP_TAPID 0x2BA01477 - } -} - -if {[using_jtag]} { - jtag newtap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_DAP_TAPID -disable - jtag configure $_CHIPNAME.cpu -event tap-enable "icepick_c_tapenable $_CHIPNAME.jrc 0" -} else { - swj_newdap $_CHIPNAME cpu -expected-id $_DAP_TAPID -} - -# -# ICEpick-C (JTAG route controller) -# -if { [info exists JRC_TAPID] } { - set _JRC_TAPID $JRC_TAPID -} else { - set _JRC_TAPID 0x0B97C02F -} - -if {[using_jtag]} { - jtag newtap $_CHIPNAME jrc -irlen 6 -ircapture 0x1 -irmask 0x3f -expected-id $_JRC_TAPID -ignore-version - jtag configure $_CHIPNAME.jrc -event setup "jtag tapenable $_CHIPNAME.dap" -} - -# -# Cortex-M3 target -# -set _TARGETNAME $_CHIPNAME.cpu -dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu -target create $_TARGETNAME cortex_m -dap $_CHIPNAME.dap diff --git a/tcl/target/psoc5lp.cfg b/tcl/target/psoc5lp.cfg index 230ca07..b4e8d05 100644 --- a/tcl/target/psoc5lp.cfg +++ b/tcl/target/psoc5lp.cfg @@ -28,6 +28,38 @@ dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME cortex_m -dap $_CHIPNAME.dap +if { [info exists WORKAREASIZE] } { + set _WORKAREASIZE $WORKAREASIZE +} else { + set _WORKAREASIZE 0x2000 +} + +$_TARGETNAME configure -work-area-phys [expr 0x20000000 - $_WORKAREASIZE / 2] \ + -work-area-size $_WORKAREASIZE -work-area-backup 0 + +source [find mem_helper.tcl] + +$_TARGETNAME configure -event reset-init { + # Configure Target Device (PSoC 5LP Device Programming Specification 5.2) + + set PANTHER_DBG_CFG 0x4008000C + set PANTHER_DBG_CFG_BYPASS [expr 1 << 1] + mmw $PANTHER_DBG_CFG $PANTHER_DBG_CFG_BYPASS 0 + + set PM_ACT_CFG0 0x400043A0 + mww $PM_ACT_CFG0 0xBF + + set FASTCLK_IMO_CR 0x40004200 + set FASTCLK_IMO_CR_F_RANGE_2 [expr 2 << 0] + set FASTCLK_IMO_CR_F_RANGE_MASK [expr 7 << 0] + mmw $FASTCLK_IMO_CR $FASTCLK_IMO_CR_F_RANGE_2 $FASTCLK_IMO_CR_F_RANGE_MASK +} + +set _FLASHNAME $_CHIPNAME.flash +flash bank $_FLASHNAME psoc5lp 0x00000000 0 0 0 $_TARGETNAME +flash bank $_CHIPNAME.eeprom psoc5lp_eeprom 0x40008000 0 0 0 $_TARGETNAME +flash bank $_CHIPNAME.nvl psoc5lp_nvl 0 0 0 0 $_TARGETNAME + if {![using_hla]} { cortex_m reset_config sysresetreq } diff --git a/tcl/target/ti_cc3220sf.cfg b/tcl/target/ti_cc3220sf.cfg new file mode 100644 index 0000000..f7d9bfe --- /dev/null +++ b/tcl/target/ti_cc3220sf.cfg @@ -0,0 +1,12 @@ +# +# Texas Instruments CC3220SF - ARM Cortex-M4 +# +# http://www.ti.com/CC3220SF +# + +source [find target/swj-dp.tcl] +source [find target/icepick.cfg] +source [find target/ti_cc32xx.cfg] + +set _FLASHNAME $_CHIPNAME.flash +flash bank $_FLASHNAME cc3220sf 0 0 0 0 $_TARGETNAME diff --git a/tcl/target/ti_cc32xx.cfg b/tcl/target/ti_cc32xx.cfg new file mode 100644 index 0000000..bc3038d --- /dev/null +++ b/tcl/target/ti_cc32xx.cfg @@ -0,0 +1,64 @@ +# +# Texas Instruments CC32xx - ARM Cortex-M4 +# +# http://www.ti.com/product/CC3200 +# http://www.ti.com/product/CC3220 +# + +source [find target/swj-dp.tcl] +source [find target/icepick.cfg] + +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME cc32xx +} + +# +# Main DAP +# +if { [info exists DAP_TAPID] } { + set _DAP_TAPID $DAP_TAPID +} else { + if {[using_jtag]} { + set _DAP_TAPID 0x4BA00477 + } else { + set _DAP_TAPID 0x2BA01477 + } +} + +if {[using_jtag]} { + jtag newtap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_DAP_TAPID -disable + jtag configure $_CHIPNAME.cpu -event tap-enable "icepick_c_tapenable $_CHIPNAME.jrc 0" +} else { + swj_newdap $_CHIPNAME cpu -expected-id $_DAP_TAPID +} + +# +# ICEpick-C (JTAG route controller) +# +if { [info exists JRC_TAPID] } { + set _JRC_TAPID $JRC_TAPID +} else { + set _JRC_TAPID 0x0B97C02F +} + +if {[using_jtag]} { + jtag newtap $_CHIPNAME jrc -irlen 6 -ircapture 0x1 -irmask 0x3f -expected-id $_JRC_TAPID -ignore-version + jtag configure $_CHIPNAME.jrc -event setup "jtag tapenable $_CHIPNAME.cpu" +} + +set _TARGETNAME $_CHIPNAME.cpu +dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu +target create $_TARGETNAME cortex_m -dap $_CHIPNAME.dap + +if { [info exists WORKAREASIZE] } { + set _WORKAREASIZE $WORKAREASIZE +} else { + set _WORKAREASIZE 0x2000 +} + +$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 + +reset_config srst_only +adapter_nsrst_delay 1100 |